From a88d6f2eeba2b3355c33fac6d736cf6086342f47 Mon Sep 17 00:00:00 2001
From: hisham
Date: Wed, 1 Apr 2009 17:11:57 +0000
Subject: Import latest revision from CVS at luaforge.net
git-svn-id: http://luarocks.org/svn/luarocks/trunk@1 9ca3f7c1-7366-0410-b1a3-b5c78f85698c
---
COPYING | 32 +++
Makefile | 123 ++++++++
configure | 362 ++++++++++++++++++++++++
install.bat | 312 +++++++++++++++++++++
makedist | 68 +++++
src/bin/luarocks | 19 ++
src/bin/luarocks-admin | 14 +
src/luarocks/build.lua | 276 ++++++++++++++++++
src/luarocks/build/builtin.lua | 152 ++++++++++
src/luarocks/build/cmake.lua | 54 ++++
src/luarocks/build/command.lua | 30 ++
src/luarocks/build/make.lua | 84 ++++++
src/luarocks/cfg.lua | 242 ++++++++++++++++
src/luarocks/command_line.lua | 133 +++++++++
src/luarocks/deps.lua | 618 +++++++++++++++++++++++++++++++++++++++++
src/luarocks/fetch.lua | 284 +++++++++++++++++++
src/luarocks/fetch/cvs.lua | 43 +++
src/luarocks/fetch/git.lua | 53 ++++
src/luarocks/fetch/sscm.lua | 40 +++
src/luarocks/fs.lua | 43 +++
src/luarocks/fs/unix.lua | 519 ++++++++++++++++++++++++++++++++++
src/luarocks/fs/win32.lua | 369 ++++++++++++++++++++++++
src/luarocks/help.lua | 69 +++++
src/luarocks/install.lua | 115 ++++++++
src/luarocks/list.lua | 35 +++
src/luarocks/make.lua | 54 ++++
src/luarocks/make_manifest.lua | 32 +++
src/luarocks/manif.lua | 439 +++++++++++++++++++++++++++++
src/luarocks/pack.lua | 116 ++++++++
src/luarocks/path.lua | 224 +++++++++++++++
src/luarocks/persist.lua | 91 ++++++
src/luarocks/remove.lua | 163 +++++++++++
src/luarocks/rep.lua | 187 +++++++++++++
src/luarocks/require.lua | 263 ++++++++++++++++++
src/luarocks/search.lua | 394 ++++++++++++++++++++++++++
src/luarocks/type_check.lua | 233 ++++++++++++++++
src/luarocks/unpack.lua | 148 ++++++++++
src/luarocks/util.lua | 180 ++++++++++++
test/run_tests.sh | 76 +++++
test/test_deps.lua | 67 +++++
test/test_require.lua | 25 ++
41 files changed, 6781 insertions(+)
create mode 100644 COPYING
create mode 100644 Makefile
create mode 100755 configure
create mode 100644 install.bat
create mode 100755 makedist
create mode 100755 src/bin/luarocks
create mode 100755 src/bin/luarocks-admin
create mode 100644 src/luarocks/build.lua
create mode 100644 src/luarocks/build/builtin.lua
create mode 100644 src/luarocks/build/cmake.lua
create mode 100644 src/luarocks/build/command.lua
create mode 100644 src/luarocks/build/make.lua
create mode 100644 src/luarocks/cfg.lua
create mode 100644 src/luarocks/command_line.lua
create mode 100644 src/luarocks/deps.lua
create mode 100644 src/luarocks/fetch.lua
create mode 100644 src/luarocks/fetch/cvs.lua
create mode 100644 src/luarocks/fetch/git.lua
create mode 100644 src/luarocks/fetch/sscm.lua
create mode 100644 src/luarocks/fs.lua
create mode 100644 src/luarocks/fs/unix.lua
create mode 100644 src/luarocks/fs/win32.lua
create mode 100644 src/luarocks/help.lua
create mode 100644 src/luarocks/install.lua
create mode 100644 src/luarocks/list.lua
create mode 100644 src/luarocks/make.lua
create mode 100644 src/luarocks/make_manifest.lua
create mode 100644 src/luarocks/manif.lua
create mode 100644 src/luarocks/pack.lua
create mode 100644 src/luarocks/path.lua
create mode 100644 src/luarocks/persist.lua
create mode 100644 src/luarocks/remove.lua
create mode 100644 src/luarocks/rep.lua
create mode 100644 src/luarocks/require.lua
create mode 100644 src/luarocks/search.lua
create mode 100644 src/luarocks/type_check.lua
create mode 100644 src/luarocks/unpack.lua
create mode 100644 src/luarocks/util.lua
create mode 100755 test/run_tests.sh
create mode 100644 test/test_deps.lua
create mode 100755 test/test_require.lua
diff --git a/COPYING b/COPYING
new file mode 100644
index 00000000..adec4655
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,32 @@
+LuaRocks is free software: it can be used for both academic and commercial
+purposes at absolutely no cost. There are no royalties or GNU-like "copyleft"
+restrictions. LuaRocks qualifies as Open Source software. Its licenses are
+compatible with the GPL. LuaRocks is not in the public domain and the Kepler
+Project keeps its copyright. The legal details are below.
+
+The spirit of the license is that you are free to use LuaRocks for any purpose
+at no cost without having to ask us. The only requirement is that if you do
+use LuaRocks, then you should give us credit by including the appropriate
+copyright notice somewhere in your product or its documentation.
+
+------------------------------------------------------------------------------
+
+Copyright © 2007 Kepler Project.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..5241eb7a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,123 @@
+# $Id: Makefile,v 1.30 2008/08/18 14:07:35 hisham Exp $
+
+include config.unix
+
+DESTDIR =
+PREFIX ?= /usr/local
+BINDIR ?= $(PREFIX)/bin
+LUADIR ?= $(PREFIX)/share/lua/5.1/
+LUA_DIR ?= /usr/local
+LUA_BINDIR ?= $(LUA_DIR)/bin
+
+BIN_FILES = luarocks luarocks-admin
+LUAROCKS_FILES = build/cmake.lua build/command.lua build.lua build/make.lua \
+command_line.lua cfg.lua deps.lua fetch.lua fs.lua fs/unix.lua \
+fs/win32.lua help.lua install.lua list.lua persist.lua \
+make_manifest.lua pack.lua path.lua rep.lua require.lua search.lua \
+type_check.lua util.lua remove.lua build/builtin.lua make.lua manif.lua unpack.lua \
+fetch/cvs.lua fetch/sscm.lua fetch/git.lua
+
+CONFIG_FILE = $(SYSCONFDIR)/config.lua
+
+all:
+ for f in $(BIN_FILES) ;\
+ do \
+ sed "1d" src/bin/$$f >> src/bin/$$f.bak ;\
+ echo "#!$(LUA_BINDIR)/lua$(LUA_SUFFIX)" > src/bin/$$f ;\
+ echo "package.path = [[$(LUADIR)/?.lua;$(LUADIR)/?/init.lua;]]..package.path" >> src/bin/$$f ;\
+ cat src/bin/$$f.bak >> src/bin/$$f ;\
+ rm src/bin/$$f.bak ;\
+ done
+ cp src/luarocks/cfg.lua src/luarocks/cfg.lua.bak
+ rm src/luarocks/cfg.lua
+ if [ -n "$(PREFIX)" ] ;\
+ then \
+ echo "local LUAROCKS_PREFIX=[[$(PREFIX)]]" >> src/luarocks/cfg.lua ;\
+ fi
+ if [ -n "$(LUA_INCDIR)" ] ;\
+ then \
+ echo "local LUA_INCDIR=[[$(LUA_INCDIR)]]" >> src/luarocks/cfg.lua ;\
+ fi
+ if [ -n "$(LUA_LIBDIR)" ] ;\
+ then \
+ echo "local LUA_LIBDIR=[[$(LUA_LIBDIR)]]" >> src/luarocks/cfg.lua ;\
+ fi
+ if [ -n "$(LUA_BINDIR)" ] ;\
+ then \
+ echo "local LUA_BINDIR=[[$(LUA_BINDIR)]]" >> src/luarocks/cfg.lua ;\
+ fi
+ if [ -n "$(LUA_SUFFIX)" ] ;\
+ then \
+ echo "local LUA_INTERPRETER=[[lua$(LUA_SUFFIX)]]" >> src/luarocks/cfg.lua ;\
+ fi
+ if [ -n "$(SYSCONFDIR)" ] ;\
+ then \
+ echo "local LUAROCKS_SYSCONFIG=[[$(SYSCONFDIR)/config.lua]]" >> src/luarocks/cfg.lua ;\
+ fi
+ if [ -n "$(ROCKS_TREE)" ] ;\
+ then \
+ echo "local LUAROCKS_ROCKS_TREE=[[$(ROCKS_TREE)]]" >> src/luarocks/cfg.lua ;\
+ fi
+ if [ -n "$(FORCE_CONFIG)" ] ;\
+ then \
+ echo "local LUAROCKS_FORCE_CONFIG=true" >> src/luarocks/cfg.lua ;\
+ fi
+ echo "local LUAROCKS_UNAME_S=[[$(LUAROCKS_UNAME_S)]]" >> src/luarocks/cfg.lua
+ echo "local LUAROCKS_UNAME_M=[[$(LUAROCKS_UNAME_M)]]" >> src/luarocks/cfg.lua
+ echo "local LUAROCKS_DOWNLOADER=[[$(LUAROCKS_DOWNLOADER)]]" >> src/luarocks/cfg.lua
+ echo "local LUAROCKS_MD5CHECKER=[[$(LUAROCKS_MD5CHECKER)]]" >> src/luarocks/cfg.lua
+ cat src/luarocks/cfg.lua.bak >> src/luarocks/cfg.lua
+ rm src/luarocks/cfg.lua.bak
+ @echo
+ @echo "Done. Type 'make install' to install into $(PREFIX)."
+ @echo
+
+luadoc:
+ rm -rf doc/luadoc
+ mkdir -p doc/luadoc
+ cd src && luadoc -d ../doc/luadoc --nofiles luarocks/*.lua
+
+check_makefile:
+ echo $(BIN_FILES) | tr " " "\n" | sort > makefile_list.txt
+ ( cd src/bin && ls -d * ) | grep -v "CVS" | sort > luarocks_dir.txt
+ echo $(LUAROCKS_FILES) | tr " " "\n" | sort >> makefile_list.txt
+ ( cd src/luarocks && ls -d *.lua ) | sort >> luarocks_dir.txt
+ diff makefile_list.txt luarocks_dir.txt
+ rm makefile_list.txt luarocks_dir.txt
+ @echo
+ @echo "Makefile is sane."
+ @echo
+
+clean:
+ for f in $(BIN_FILES) ;\
+ do \
+ sed -i.bak "s,^#!.*lua.*,#!/usr/bin/env lua,;/^package.path/d" src/bin/$$f ;\
+ rm src/bin/$$f.bak ;\
+ done
+ sed -i.bak "/^local LUA/d" src/luarocks/cfg.lua
+ rm src/luarocks/cfg.lua.bak
+
+install:
+ mkdir -p "$(DESTDIR)$(BINDIR)"
+ cd src/bin && cp $(BIN_FILES) "$(DESTDIR)$(BINDIR)"
+ mkdir -p "$(DESTDIR)$(LUADIR)/luarocks"
+ cd src/luarocks && for f in $(LUAROCKS_FILES); do d="$(DESTDIR)$(LUADIR)/luarocks"/`dirname "$$f"`; mkdir -p "$$d"; cp "$$f" "$$d"; done
+ mkdir -p "$(DESTDIR)$(ROCKS_TREE)"
+ if [ ! -f "$(DESTDIR)$(CONFIG_FILE)" ] ;\
+ then \
+ mkdir -p `dirname "$(DESTDIR)$(CONFIG_FILE)"` ;\
+ echo 'rocks_servers = {' >> "$(DESTDIR)$(CONFIG_FILE)" ;\
+ echo ' [[http://luarocks.luaforge.net/rocks]]' >> "$(DESTDIR)$(CONFIG_FILE)" ;\
+ echo '}' >> "$(DESTDIR)$(CONFIG_FILE)" ;\
+ echo 'rocks_trees = {' >> "$(DESTDIR)$(CONFIG_FILE)" ;\
+ if [ ! -n "$(FORCE_CONFIG)" ] ;\
+ then \
+ echo ' home..[[/.luarocks]],' >> "$(DESTDIR)$(CONFIG_FILE)" ;\
+ fi ;\
+ echo ' [[$(ROCKS_TREE)]]' >> "$(DESTDIR)$(CONFIG_FILE)" ;\
+ echo '}' >> "$(DESTDIR)$(CONFIG_FILE)" ;\
+ if [ -n "$(SCRIPTS_DIR)" ] ;\
+ then \
+ echo "scripts_dir = [[$(SCRIPTS_DIR)]]" >> "$(DESTDIR)$(CONFIG_FILE)" ;\
+ fi ;\
+ fi
diff --git a/configure b/configure
new file mode 100755
index 00000000..268b7241
--- /dev/null
+++ b/configure
@@ -0,0 +1,362 @@
+#!/bin/sh
+
+# A basic configure script for LuaRocks.
+# Not doing any fancy shell stuff here to keep good compatibility.
+
+# Defaults
+
+PREFIX="/usr/local"
+SYSCONFDIR="$PREFIX/etc/luarocks"
+ROCKS_TREE="$PREFIX/lib/luarocks"
+SCRIPTS_DIR=""
+LUA_SUFFIX=""
+LUA_DIR="/usr"
+LUA_BINDIR="/usr/bin"
+LUA_INCDIR="/usr/include"
+LUA_LIBDIR="/usr/lib"
+
+# ----------------------------------------------------------------------------
+# FUNCTION DEFINITIONS
+# ----------------------------------------------------------------------------
+
+# Help
+
+show_help() {
+cat </dev/null 2>/dev/null`
+ then
+ echo
+ echo '*WARNING*: the "~" sign is not expanded in flags.'
+ echo 'If you mean the home directory, use $HOME instead.'
+ echo
+ fi
+ case "$key" in
+ --help)
+ show_help
+ exit 0
+ ;;
+ --prefix)
+ [ -n "$value" ] || die "Missing value in flag $key."
+ PREFIX="$value"
+ PREFIX_SET=yes
+ ;;
+ --sysconfdir)
+ [ -n "$value" ] || die "Missing value in flag $key."
+ SYSCONFDIR="$value"
+ SYSCONFDIR_SET=yes
+ ;;
+ --rocks-tree)
+ [ -n "$value" ] || die "Missing value in flag $key."
+ ROCKS_TREE="$value"
+ ROCKS_TREE_SET=yes
+ ;;
+ --scripts-dir)
+ [ -n "$value" ] || die "Missing value in flag $key."
+ SCRIPTS_DIR="$value"
+ SCRIPTS_DIR_SET=yes
+ ;;
+ --force-config)
+ FORCE_CONFIG=yes
+ ;;
+ --lua-suffix)
+ [ -n "$value" ] || die "Missing value in flag $key."
+ LUA_SUFFIX="$value"
+ LUA_SUFFIX_SET=yes
+ ;;
+ --with-lua)
+ [ -n "$value" ] || die "Missing value in flag $key."
+ LUA_DIR="$value"
+ LUA_DIR_SET=yes
+ ;;
+ --with-lua-include)
+ [ -n "$value" ] || die "Missing value in flag $key."
+ LUA_INCDIR="$value"
+ LUA_INCDIR_SET=yes
+ ;;
+ --with-lua-lib)
+ [ -n "$value" ] || die "Missing value in flag $key."
+ LUA_LIBDIR="$value"
+ LUA_LIBDIR_SET=yes
+ ;;
+ --with-downloader)
+ [ -n "$value" ] || die "Missing value in flag $key."
+ case "$value" in
+ wget|curl) LUAROCKS_DOWNLOADER="$value" ;;
+ *) echo "Invalid option: $value. See --help." ; exit 1 ;;
+ esac
+ LUAROCKS_DOWNLOADER_SET=yes
+ ;;
+ --with-md5-checker)
+ [ -n "$value" ] || die "Missing value in flag $key."
+ case "$value" in
+ md5sum|openssl|md5) LUAROCKS_MD5CHECKER="$value" ;;
+ *) echo "Invalid option: $value. See --help." ; exit 1 ;;
+ esac
+ LUAROCKS_MD5CHECKER_SET=yes
+ ;;
+ *)
+ echo "Error: Unknown flag: $1"
+ exit 1
+ ;;
+ esac
+ shift
+done
+
+
+if [ "$PREFIX_SET" = "yes" -a ! "$SYSCONFDIR_SET" = "yes" ]
+then
+ if [ "$PREFIX" = "/usr" ]
+ then SYSCONFDIR=/etc/luarocks
+ else SYSCONFDIR=$PREFIX/etc/luarocks
+ fi
+fi
+
+
+if [ "$PREFIX_SET" = "yes" -a ! "$ROCKS_TREE_SET" = "yes" ]
+then
+ ROCKS_TREE=$PREFIX/lib/luarocks
+fi
+
+if [ "$LUA_SUFFIX_SET" != "yes" ]
+then
+ for suffix in "" "5.1" "51" ""
+ do
+ LUA_SUFFIX="$suffix"
+ if [ "$LUA_DIR_SET" = "yes" ]
+ then
+ if [ -f "$LUA_DIR/bin/lua$suffix" ]
+ then
+ find_lua="$LUA_DIR"
+ fi
+ else
+ find_lua=`find_program lua$suffix`
+ fi
+ if [ -n "$find_lua" ]
+ then
+ echo "Lua interpreter found: $find_lua/lua$suffix..."
+ break
+ fi
+ done
+fi
+
+if [ "$LUA_DIR_SET" != "yes" ]
+then
+ echo_n "Looking for Lua... "
+ if [ ! -n "$find_lua" ]
+ then
+ find_lua=`find_program lua$LUA_SUFFIX`
+ fi
+
+ if [ -n "$find_lua" ]
+ then
+ LUA_DIR=`dirname $find_lua`
+ LUA_BINDIR="$find_lua"
+ echo "lua$LUA_SUFFIX found in \$PATH: $find_lua"
+ else
+ echo "lua$LUA_SUFFIX not found in \$PATH."
+ echo "You may want to use the flags --with-lua and/or --lua-suffix. See --help."
+ exit 1
+ fi
+fi
+
+if [ "$LUA_INCDIR_SET" != "yes" ]
+then
+ LUA_INCDIR="$LUA_DIR/include"
+fi
+
+if [ "$LUA_LIBDIR_SET" != "yes" ]
+then
+ LUA_LIBDIR="$LUA_DIR/lib"
+fi
+
+if [ "$LUA_DIR_SET" = "yes" ]
+then
+ LUA_BINDIR="$LUA_DIR/bin"
+fi
+
+echo_n "Checking Lua includes... "
+lua_h="$LUA_INCDIR/lua.h"
+if [ -f "$lua_h" ]
+then
+ echo "lua.h found in $lua_h"
+else
+ echo "lua.h not found (looked in $lua_h)"
+ echo "You may want to use the flag --with-lua-include. See --help."
+ exit 1
+fi
+
+if [ "$LUAROCKS_DOWNLOADER_SET" != "yes" ]
+then
+ find_helper "downloader helper program" wget curl fetch
+ LUAROCKS_DOWNLOADER=$HELPER
+fi
+
+if [ "$LUAROCKS_MD5CHECKER_SET" != "yes" ]
+then
+ find_helper "MD5 checksum calculator" md5sum openssl md5
+ LUAROCKS_MD5CHECKER=$HELPER
+fi
+
+echo_n "Configuring for system... "
+if uname -s
+then
+ LUAROCKS_UNAME_S=`uname -s`
+else
+ echo "Could not determine operating system. 'uname -s' failed."
+ exit 1
+fi
+echo_n "Configuring for architecture... "
+if uname -m
+then
+ LUAROCKS_UNAME_M=`uname -m`
+else
+ echo "Could not determine processor architecture. 'uname -m' failed."
+ exit 1
+fi
+
+if [ -f config.unix ]; then
+ rm -f config.unix
+fi
+
+# Write config
+
+echo "Writing configuration..."
+echo
+
+cat < config.unix
+# This file was automatically generated by the configure script.
+# Run "./configure --help" for details.
+
+PREFIX=$PREFIX
+SYSCONFDIR=$SYSCONFDIR
+ROCKS_TREE=$ROCKS_TREE
+SCRIPTS_DIR=$SCRIPTS_DIR
+LUA_SUFFIX=$LUA_SUFFIX
+LUA_DIR=$LUA_DIR
+LUA_INCDIR=$LUA_INCDIR
+LUA_LIBDIR=$LUA_LIBDIR
+LUA_BINDIR=$LUA_BINDIR
+FORCE_CONFIG=$FORCE_CONFIG
+LUAROCKS_UNAME_M=$LUAROCKS_UNAME_M
+LUAROCKS_UNAME_S=$LUAROCKS_UNAME_S
+LUAROCKS_DOWNLOADER=$LUAROCKS_DOWNLOADER
+LUAROCKS_MD5CHECKER=$LUAROCKS_MD5CHECKER
+
+EOF
+
+echo "Installation prefix: $PREFIX"
+echo "LuaRocks configuration directory: $SYSCONFDIR"
+echo "Using Lua from: $LUA_DIR"
+
+make clean > /dev/null 2> /dev/null
+
+echo
+echo "Done. You can now run 'make' to build."
+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 @@
+@ECHO OFF
+
+REM Boy, it feels like 1994 all over again.
+
+SETLOCAL
+
+SET PREFIX=C:\LuaRocks
+SET VERSION=1.0
+SET SYSCONFDIR=C:\LuaRocks
+SET ROCKS_TREE=C:\LuaRocks
+SET SCRIPTS_DIR=
+SET FORCE=OFF
+SET INSTALL_LUA=OFF
+SET LUA_INTERPRETER=
+SET LUA_PREFIX=
+SET LUA_BINDIR=
+SET LUA_INCDIR=
+SET LUA_LIBDIR=
+SET FORCE_CONFIG=
+SET MKDIR=.\bin\mkdir -p
+
+REM ***********************************************************
+REM Option parser
+REM ***********************************************************
+
+:PARSE_LOOP
+IF [%1]==[] GOTO DONE_PARSING
+IF [%1]==[/?] (
+ ECHO Installs LuaRocks.
+ ECHO.
+ ECHO /P [dir] Where to install.
+ ECHO Default is %PREFIX%
+ ECHO /CONFIG [dir] Location where the config file should be installed.
+ ECHO Default is %SYSCONFDIR%
+ ECHO /TREE [dir] Root of the local tree of installed rocks.
+ ECHO Default is %ROCKS_TREE%
+ ECHO /SCRIPTS [dir] Where to install scripts installed by rocks.
+ ECHO Default is TREE/bin.
+ ECHO.
+ ECHO /L Install LuaRocks' own copy of Lua even if detected.
+ ECHO /LUA [dir] Location where Lua is installed - e.g. c:\lua\5.1\
+ ECHO /INC [dir] Location of Lua includes - e.g. c:\lua\5.1\include
+ ECHO /LIB [dir] Location of Lua libraries -e.g. c:\lua\5.1\lib
+ ECHO /BIN [dir] Location of Lua executables - e.g. c:\lua\5.1\bin
+ ECHO.
+ ECHO /FORCECONFIG Use a single config location. Do not use the
+ ECHO LUAROCKS_CONFIG variable or the user's home directory.
+ ECHO Useful to avoid conflicts when LuaRocks
+ ECHO is embedded within an application.
+ ECHO.
+ ECHO /F Remove installation directory if it already exists.
+ ECHO.
+ GOTO QUIT
+)
+IF /I [%1]==[/P] (
+ SET PREFIX=%2
+ SHIFT /1
+ SHIFT /1
+ GOTO PARSE_LOOP
+)
+IF /I [%1]==[/CONFIG] (
+ SET SYSCONFDIR=%2
+ SHIFT /1
+ SHIFT /1
+ GOTO PARSE_LOOP
+)
+IF /I [%1]==[/TREE] (
+ SET ROCKS_TREE=%2
+ SHIFT /1
+ SHIFT /1
+ GOTO PARSE_LOOP
+)
+IF /I [%1]==[/SCRIPTS] (
+ SET SCRIPTS_DIR=%2
+ SHIFT /1
+ SHIFT /1
+ GOTO PARSE_LOOP
+)
+IF /I [%1]==[/L] (
+ SET INSTALL_LUA=ON
+ SHIFT /1
+ GOTO PARSE_LOOP
+)
+IF /I [%1]==[/LUA] (
+ SET LUA_PREFIX=%2
+ SHIFT /1
+ SHIFT /1
+ GOTO PARSE_LOOP
+)
+IF /I [%1]==[/LIB] (
+ SET LUA_LIBDIR=%2
+ SHIFT /1
+ SHIFT /1
+ GOTO PARSE_LOOP
+)
+IF /I [%1]==[/INC] (
+ SET LUA_INCDIR=%2
+ SHIFT /1
+ SHIFT /1
+ GOTO PARSE_LOOP
+)
+IF /I [%1]==[/BIN] (
+ SET LUA_BINDIR=%2
+ SHIFT /1
+ SHIFT /1
+ GOTO PARSE_LOOP
+)
+IF /I [%1]==[/FORCECONFIG] (
+ SET FORCE_CONFIG=ON
+ SHIFT /1
+ GOTO PARSE_LOOP
+)
+IF /I [%1]==[/F] (
+ SET FORCE=ON
+ SHIFT /1
+ GOTO PARSE_LOOP
+)
+ECHO Unrecognized option: %1
+GOTO ERROR
+:DONE_PARSING
+
+SET FULL_PREFIX=%PREFIX%\%VERSION%
+
+SET BINDIR=%FULL_PREFIX%
+SET LIBDIR=%FULL_PREFIX%
+SET LUADIR=%FULL_PREFIX%\lua
+SET INCDIR=%FULL_PREFIX%\include
+
+REM ***********************************************************
+REM Detect Lua
+REM ***********************************************************
+
+IF [%INSTALL_LUA%]==[ON] GOTO USE_OWN_LUA
+
+FOR %%L IN (%LUA_PREFIX% c:\lua\5.1.2 c:\lua c:\kepler\1.1) DO (
+ SET CURR=%%L
+ IF EXIST "%%L" (
+ IF NOT [%LUA_BINDIR%]==[] (
+ IF EXIST %LUA_BINDIR%\lua5.1.exe (
+ SET LUA_INTERPRETER=%LUA_BINDIR%\lua5.1.exe
+ GOTO INTERPRETER_IS_SET
+ )
+ IF EXIST %LUA_BINDIR%\lua.exe (
+ SET LUA_INTERPRETER=%LUA_BINDIR%\lua.exe
+ GOTO INTERPRETER_IS_SET
+ )
+ ECHO Lua executable lua.exe or lua5.1.exe not found in %LUA_BINDIR%
+ GOTO ERROR
+ )
+ SET CURR=%%L
+ FOR %%E IN (\ \bin\) DO (
+ IF EXIST "%%L%%E\lua5.1.exe" (
+ SET LUA_INTERPRETER=%%L%%E\lua5.1.exe
+ SET LUA_BINDIR=%%L%%E
+ GOTO INTERPRETER_IS_SET
+ )
+ IF EXIST "%%L%%E\lua.exe" (
+ SET LUA_INTERPRETER=%%L%%E\lua.exe
+ SET LUA_BINDIR=%%L%%E
+ GOTO INTERPRETER_IS_SET
+ )
+ )
+ GOTO TRY_NEXT_LUA_DIR
+ :INTERPRETER_IS_SET
+ IF NOT "%LUA_LIBDIR%"=="" (
+ IF EXIST %LUA_LIBDIR%\lua5.1.lib GOTO LIBDIR_IS_SET
+ ECHO lua5.1.lib not found in %LUA_LIBDIR%
+ GOTO ERROR
+ )
+ FOR %%E IN (\ \lib\ \bin\) DO (
+ IF EXIST "%CURR%%%E\lua5.1.lib" (
+ SET LUA_LIBDIR=%CURR%%%E
+ GOTO LIBDIR_IS_SET
+ )
+ )
+ GOTO TRY_NEXT_LUA_DIR
+ :LIBDIR_IS_SET
+ IF NOT [%LUA_INCDIR%]==[] (
+ IF EXIST %LUA_INCDIR%\lua.h GOTO INCDIR_IS_SET
+ ECHO lua.h not found in %LUA_INCDIR%
+ GOTO ERROR
+ )
+ FOR %%E IN (\ \include\) DO (
+ IF EXIST "%CURR%%%E\lua.h" (
+ SET LUA_INCDIR=%CURR%%%E
+ GOTO INCDIR_IS_SET
+ )
+ )
+ GOTO TRY_NEXT_LUA_DIR
+ :INCDIR_IS_SET
+ %LUA_INTERPRETER% -v 2>NUL
+ IF NOT ERRORLEVEL 1 (
+ GOTO LUA_IS_SET
+ )
+ )
+:TRY_NEXT_LUA_DIR
+ REM wtf
+)
+ECHO Could not find Lua. Will install its own copy.
+ECHO See /? for options for specifying the location of Lua.
+:USE_OWN_LUA
+SET INSTALL_LUA=ON
+SET LUA_INTERPRETER=lua5.1
+SET LUA_BINDIR=%BINDIR%
+SET LUA_LIBDIR=%LIBDIR%
+SET LUA_INCDIR=%INCDIR%
+:LUA_IS_SET
+ECHO.
+ECHO Will configure LuaRocks with the following paths:
+ECHO Lua interpreter: %LUA_INTERPRETER%
+ECHO Lua binaries: %LUA_BINDIR%
+ECHO Lua libraries: %LUA_LIBDIR%
+ECHO Lua includes: %LUA_INCDIR%
+ECHO.
+
+REM ***********************************************************
+REM Install LuaRocks files
+REM ***********************************************************
+
+IF [%FORCE%]==[ON] (
+ ECHO Removing %FULL_PREFIX%...
+ RD /S /Q "%FULL_PREFIX%"
+)
+
+IF NOT EXIST "%FULL_PREFIX%" GOTO NOT_EXIST_PREFIX
+ ECHO %FULL_PREFIX% exists. Use /F to force removal and reinstallation.
+ GOTO ERROR
+:NOT_EXIST_PREFIX
+
+ECHO Installing LuaRocks in %FULL_PREFIX%...
+IF NOT EXIST "%BINDIR%" %MKDIR% "%BINDIR%"
+IF ERRORLEVEL 1 GOTO ERROR
+IF [%INSTALL_LUA%]==[ON] (
+ IF NOT EXIST "%LUA_BINDIR%" %MKDIR% "%LUA_BINDIR%"
+ IF NOT EXIST "%LUA_INCDIR%" %MKDIR% "%LUA_INCDIR%"
+ COPY lua5.1\bin\*.* "%LUA_BINDIR%" >NUL
+ COPY lua5.1\include\*.* "%LUA_INCDIR%" >NUL
+)
+COPY bin\*.* "%BINDIR%" >NUL
+IF ERRORLEVEL 1 GOTO ERROR
+COPY src\bin\*.* "%BINDIR%" >NUL
+IF ERRORLEVEL 1 GOTO ERROR
+FOR %%C IN (luarocks luarocks-admin) DO (
+ RENAME "%BINDIR%\%%C" %%C.lua
+ IF ERRORLEVEL 1 GOTO ERROR
+ DEL /F /Q "%BINDIR%\%%C.bat" 2>NUL
+ ECHO @ECHO OFF>> "%BINDIR%\%%C.bat"
+ ECHO SETLOCAL>> "%BINDIR%\%%C.bat"
+ ECHO SET LUA_PATH=%LUADIR%\?.lua;%LUADIR%\?\init.lua;%%LUA_PATH%%>> "%BINDIR%\%%C.bat"
+ ECHO SET PATH=%BINDIR%\;%%PATH%%>> "%BINDIR%\%%C.bat"
+ ECHO "%LUA_INTERPRETER%" "%BINDIR%\%%C.lua" %%*>> "%BINDIR%\%%C.bat"
+ ECHO ENDLOCAL>> "%BINDIR%\%%C.bat"
+)
+IF NOT EXIST "%LUADIR%\luarocks" %MKDIR% "%LUADIR%\luarocks"
+IF ERRORLEVEL 1 GOTO ERROR
+XCOPY /S src\luarocks\*.* "%LUADIR%\luarocks" >NUL
+IF ERRORLEVEL 1 GOTO ERROR
+
+RENAME "%LUADIR%\luarocks\cfg.lua" "cfg.lua.bak"
+ECHO local LUA_INCDIR=[[%LUA_INCDIR%]]>> "%LUADIR%\luarocks\cfg.lua"
+ECHO local LUA_LIBDIR=[[%LUA_LIBDIR%]]>> "%LUADIR%\luarocks\cfg.lua"
+ECHO local LUA_BINDIR=[[%LUA_BINDIR%]]>> "%LUADIR%\luarocks\cfg.lua"
+ECHO local LUA_INTERPRETER=[[%LUA_INTERPRETER%]]>> "%LUADIR%\luarocks\cfg.lua"
+ECHO local LUAROCKS_UNAME_S=[[WindowsNT]]>> "%LUADIR%\luarocks\cfg.lua"
+ECHO local LUAROCKS_UNAME_M=[[x86]]>> "%LUADIR%\luarocks\cfg.lua"
+ECHO local LUAROCKS_SYSCONFIG=[[%SYSCONFDIR%/config.lua]]>> "%LUADIR%\luarocks\cfg.lua"
+ECHO local LUAROCKS_ROCKS_TREE=[[%ROCKS_TREE%]]>> "%LUADIR%\luarocks\cfg.lua"
+ECHO local LUAROCKS_PREFIX=[[%PREFIX%]]>> "%LUADIR%\luarocks\cfg.lua"
+IF NOT [%FORCE_CONFIG%]==[] ECHO local LUAROCKS_FORCE_CONFIG=true>> "%LUADIR%\luarocks\cfg.lua"
+TYPE "%LUADIR%\luarocks\cfg.lua.bak">> "%LUADIR%\luarocks\cfg.lua"
+
+DEL /F /Q "%LUADIR%\luarocks\cfg.lua.bak"
+
+SET CONFIG_FILE=%SYSCONFDIR%\config.lua
+
+IF NOT EXIST "%SYSCONFDIR%" %MKDIR% "%SYSCONFDIR%"
+IF NOT EXIST "%CONFIG_FILE%" (
+ ECHO rocks_servers = {>> "%CONFIG_FILE%"
+ ECHO [[http://luarocks.luaforge.net/rocks]]>> "%CONFIG_FILE%"
+ ECHO }>> "%CONFIG_FILE%"
+ ECHO rocks_trees = {>> "%CONFIG_FILE%"
+ IF [%FORCE_CONFIG%]==[] ECHO home..[[/luarocks]],>> "%CONFIG_FILE%"
+ ECHO [[%ROCKS_TREE%]]>> "%CONFIG_FILE%"
+ ECHO }>> "%CONFIG_FILE%"
+ IF NOT [%SCRIPTS_DIR%]==[] ECHO scripts_dir=[[%SCRIPTS_DIR%]]>> "%CONFIG_FILE%"
+)
+
+IF [%SCRIPTS_DIR%]==[] (
+ %MKDIR% "%ROCKS_TREE%"\bin >NUL
+ COPY lua5.1\bin\*.dll "%ROCKS_TREE%"\bin >NUL
+)
+IF NOT [%SCRIPTS_DIR%]==[] (
+ %MKDIR% "%SCRIPTS_DIR%" >NUL
+ COPY lua5.1\bin\*.dll "%SCRIPTS_DIR%" >NUL
+)
+
+IF NOT EXIST "%ROCKS_TREE%" %MKDIR% "%ROCKS_TREE%"
+IF NOT EXIST "%APPDATA%/luarocks" %MKDIR% "%APPDATA%/luarocks"
+
+REM ***********************************************************
+REM Exit handlers
+REM ***********************************************************
+
+ECHO LuaRocks is installed!
+:QUIT
+ENDLOCAL
+EXIT /B 0
+
+:ERROR
+ECHO Failed installing LuaRocks.
+ENDLOCAL
+EXIT /B 1
diff --git a/makedist b/makedist
new file mode 100755
index 00000000..3e78040a
--- /dev/null
+++ b/makedist
@@ -0,0 +1,68 @@
+#!/bin/sh
+
+cvslist() {
+ if [ "$1" ]
+ then local prefix="$1/"
+ fi
+ cvs list $1 | grep -v "^?" | while read line
+ do
+ local path="$prefix$line"
+ echo "$path"
+ if [ -d "$path" ]
+ then
+ cvslist "$path"
+ fi
+ done
+}
+
+if ! [ "$1" ]
+then
+ echo "usage: $0 "
+ exit 1
+fi
+
+cvs list > /dev/null 2> /dev/null
+if [ $? != 0 ]
+then
+ echo "Your version of CVS may be too old. At least 1.12 is needed."
+ exit 1
+fi
+
+make clean
+
+out="luarocks-$1"
+rm -rf "$out"
+mkdir "$out"
+list=`cvslist`
+rm -f missing_ref
+echo "$list" | while read i
+do
+ if [ -f "$i" ]
+ then
+ dir=`dirname $i`
+ mkdir -p "$out/$dir"
+ cp "$i" "$out/$dir"
+ if echo "$i" | grep -q "^src/"
+ then
+ grep -qw `basename "$i"` Makefile || {
+ echo "Missing ref in makefile: $i"
+ touch missing_ref
+ exit 1
+ }
+ fi
+ fi
+done
+if [ -e missing_ref ]
+then
+ rm -f missing_ref
+ exit 1
+fi
+rm -f "$out-win32.zip" "$out.tar.gz"
+rm "$out/makedist"
+rm "$out/install.bat"
+tar czvpf "$out.tar.gz" "$out"
+cp install.bat "$out"
+cp -a win32/bin "$out"
+cp -a win32/lua5.1 "$out"
+zip -r "$out-win32.zip" "$out"
+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 @@
+#!/usr/bin/env lua
+
+local command_line = require("luarocks.command_line")
+
+program_name = "luarocks"
+program_description = "LuaRocks main command-line interface"
+
+commands = {}
+commands.help = require("luarocks.help")
+commands.pack = require("luarocks.pack")
+commands.unpack = require("luarocks.unpack")
+commands.build = require("luarocks.build")
+commands.install = require("luarocks.install")
+commands.search = require("luarocks.search")
+commands.list = require("luarocks.list")
+commands.remove = require("luarocks.remove")
+commands.make = require("luarocks.make")
+
+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 @@
+#!/usr/bin/env lua
+
+local command_line = require("luarocks.command_line")
+
+program_name = "luarocks-admin"
+program_description = "LuaRocks repository administration interface"
+
+commands = {
+}
+
+commands.help = require("luarocks.help")
+commands.make_manifest = require("luarocks.make_manifest")
+
+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 @@
+
+--- Module implementing the LuaRocks "build" command.
+-- Builds a rock, compiling its C parts if any.
+module("luarocks.build", package.seeall)
+
+local path = require("luarocks.path")
+local util = require("luarocks.util")
+local rep = require("luarocks.rep")
+local fetch = require("luarocks.fetch")
+local fs = require("luarocks.fs")
+local deps = require("luarocks.deps")
+local manif = require("luarocks.manif")
+
+help_summary = "Build/compile a rock."
+help_arguments = "{|| []}"
+help = [[
+Build a rock, compiling its C parts if any.
+Argument may be a rockspec file, a source rock file
+or the name of a rock to be fetched from a repository.
+]]
+
+--- Install files to a given location.
+-- Takes a table where the array part is a list of filenames to be copied.
+-- In the hash part, other keys are identifiers in Lua module format,
+-- to indicate which subdirectory the file should be copied to. For example,
+-- install_files({["foo.bar"] = "src/bar.lua"}, "boo") will copy src/bar.lua
+-- to boo/foo.
+-- @param files table or nil: A table containing a list of files to copy in
+-- the format described above. If nil is passed, this function is a no-op.
+-- Directories should be delimited by forward slashes as in internet URLs.
+-- @param location string: The base directory files should be copied to.
+-- @return boolean or (nil, string): True if succeeded or
+-- nil and an error message.
+local function install_files(files, location)
+ assert(type(files) == "table" or not files)
+ assert(type(location) == "string")
+ if files then
+ for k, file in pairs(files) do
+ local dest = location
+ if type(k) == "string" then
+ dest = fs.make_path(location, path.module_to_path(k))
+ end
+ fs.make_dir(dest)
+ local ok = fs.copy(fs.make_path(file), dest)
+ if not ok then
+ return nil, "Failed copying "..file
+ end
+ end
+ end
+ return true
+end
+
+--- Write to the current directory the contents of a table,
+-- where each key is a file name and its value is the file content.
+-- @param files table: The table of files to be written.
+local function extract_from_rockspec(files)
+ for name, content in pairs(files) do
+ local fd = io.open(fs.make_path(fs.current_dir(), name), "w+")
+ fd:write(content)
+ fd:close()
+ end
+end
+
+--- Applies patches inlined in the build.patches section
+-- and extracts files inlined in the build.extra_files section
+-- of a rockspec.
+-- @param rockspec table: A rockspec table.
+-- @return boolean or (nil, string): True if succeeded or
+-- nil and an error message.
+function apply_patches(rockspec)
+ assert(type(rockspec) == "table")
+
+ local build = rockspec.build
+ if build.extra_files then
+ extract_from_rockspec(build.extra_files)
+ end
+ if build.patches then
+ extract_from_rockspec(build.patches)
+ for patch, _ in util.sortedpairs(build.patches) do
+ print("Applying patch "..patch.."...")
+ local ok, err = fs.patch(tostring(patch))
+ if not ok then
+ return nil, "Failed applying patch "..patch
+ end
+ end
+ end
+ return true
+end
+
+--- Build and install a rock given a rockspec.
+-- @param rockspec_file string: local or remote filename of a rockspec.
+-- @param need_to_fetch boolean: true if sources need to be fetched,
+-- false if the rockspec was obtained from inside a source rock.
+-- @return boolean or (nil, string): True if succeeded or
+-- nil and an error message.
+function build_rockspec(rockspec_file, need_to_fetch, minimal_mode)
+ assert(type(rockspec_file) == "string")
+ assert(type(need_to_fetch) == "boolean")
+
+ local rockspec, err = fetch.load_rockspec(rockspec_file)
+ if err then
+ return nil, err
+ elseif not rockspec.build then
+ return nil, "Rockspec error: build table not specified"
+ elseif not rockspec.build.type then
+ return nil, "Rockspec error: build type not specified"
+ end
+
+ local ok, err = deps.fulfill_dependencies(rockspec)
+ if err then
+ return nil, err
+ end
+ ok, err = deps.check_external_deps(rockspec, "build")
+ if err then
+ return nil, err
+ end
+
+ local name, version = rockspec.name, rockspec.version
+ if rep.is_installed(name, version) then
+ rep.delete_version(name, version)
+ end
+
+ if not minimal_mode then
+ local _, dir
+ if need_to_fetch then
+ ok, dir = fetch.fetch_sources(rockspec, true)
+ if not ok then
+ return nil, dir
+ end
+ fs.change_dir(dir)
+ elseif rockspec.source.file then
+ local ok, err = fs.unpack_archive(rockspec.source.file)
+ if not ok then
+ return nil, err
+ end
+ end
+ fs.change_dir(rockspec.source.dir)
+ end
+
+ local dirs = {
+ lua = path.lua_dir(name, version),
+ lib = path.lib_dir(name, version),
+ conf = path.conf_dir(name, version),
+ bin = path.bin_dir(name, version),
+ }
+
+ for _, dir in pairs(dirs) do
+ fs.make_dir(dir)
+ end
+ local rollback = util.schedule_function(function()
+ fs.delete(path.install_dir(name, version))
+ fs.remove_dir_if_empty(path.versions_dir(name))
+ end)
+
+ local build = rockspec.build
+
+ if not minimal_mode then
+ ok, err = apply_patches(rockspec)
+ if err then
+ return nil, err
+ end
+ end
+
+ if build.type ~= "none" then
+
+ -- Temporary compatibility
+ if build.type == "module" then build.type = "builtin" end
+
+ ok, build_type = pcall(require, "luarocks.build." .. build.type)
+ if not ok or not type(build_type) == "table" then
+ return nil, "Failed initializing build back-end for build type '"..build.type.."'"
+ end
+
+ ok, err = build_type.run(rockspec)
+ if not ok then
+ return nil, "Build error: " .. err
+ end
+ end
+
+ if build.install then
+ for id, dir in pairs(dirs) do
+ ok, err = install_files(build.install[id], dir)
+ if not ok then
+ return nil, err
+ end
+ end
+ end
+
+ local copy_directories = build.copy_directories or {"doc"}
+
+ for _, dir in pairs(copy_directories) do
+ if fs.is_dir(dir) then
+ local dest = fs.make_path(path.install_dir(name, version), dir)
+ fs.make_dir(dest)
+ fs.copy_contents(dir, dest)
+ end
+ end
+
+ for _, dir in pairs(dirs) do
+ fs.remove_dir_if_empty(dir)
+ end
+
+ fs.pop_dir()
+
+ fs.copy(rockspec.local_filename, path.rockspec_file(name, version))
+ if need_to_fetch then
+ fs.pop_dir()
+ end
+
+ ok, err = rep.install_bins(name, version)
+ if not ok then return nil, err end
+
+ ok, err = rep.run_hook(rockspec, "post_install")
+ if err then
+ return nil, err
+ end
+
+ ok, err = manif.update_manifest(name, version)
+ if err then
+ return nil, err
+ end
+ util.remove_scheduled_function(rollback)
+ return true
+end
+
+--- Build and install a rock.
+-- @param rock_file string: local or remote filename of a rock.
+-- @param need_to_fetch boolean: true if sources need to be fetched,
+-- false if the rockspec was obtained from inside a source rock.
+-- @return boolean or (nil, string): True if build was successful,
+-- or false and an error message.
+local function build_rock(rock_file, need_to_fetch)
+ assert(type(rock_file) == "string")
+ assert(type(need_to_fetch) == "boolean")
+
+ local dir, err = fetch.fetch_and_unpack_rock(rock_file)
+ if not dir then
+ return nil, err
+ end
+ local rockspec_file = path.rockspec_name_from_rock(rock_file)
+ fs.change_dir(dir)
+ local ok, err = build_rockspec(rockspec_file, need_to_fetch)
+ fs.pop_dir()
+ return ok, err
+end
+
+--- Driver function for "build" command.
+-- @param name string: A local or remote rockspec or rock file.
+-- If a package name is given, forwards the request to "search" and,
+-- if returned a result, installs the matching rock.
+-- @param version string: When passing a package name, a version number may
+-- also be given.
+-- @return boolean or (nil, string): True if build was successful; nil and an
+-- error message otherwise.
+function run(...)
+ local flags, name, version = util.parse_flags(...)
+ if type(name) ~= "string" then
+ return nil, "Argument missing, see help."
+ end
+ assert(type(version) == "string" or not version)
+
+ if name:match("%.rockspec$") then
+ return build_rockspec(name, true)
+ elseif name:match("%.src%.rock$") then
+ return build_rock(name, false)
+ elseif name:match("%.all%.rock$") then
+ local install = require("luarocks.install")
+ return install.install_binary_rock(name)
+ elseif name:match("%.rock$") then
+ return build_rock(name, true)
+ elseif not name:match(fs.dir_separator) then
+ local search = require("luarocks.search")
+ return search.act_on_src_or_rockspec(run, name, version)
+ end
+ return nil, "Don't know what to do with "..name
+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 @@
+
+--- A builtin build system: back-end to provide a portable way of building C-based Lua modules.
+module("luarocks.build.builtin", package.seeall)
+
+local fs = require("luarocks.fs")
+local path = require("luarocks.path")
+local util = require("luarocks.util")
+local cfg = require("luarocks.cfg")
+
+--- Check if platform was detected
+-- @param query string: The platform name to check.
+-- @return boolean: true if LuaRocks is currently running on queried platform.
+local function is_platform(query)
+ assert(type(query) == "string")
+
+ for _, platform in ipairs(cfg.platforms) do
+ if platform == query then
+ return true
+ end
+ end
+end
+
+--- Run a command displaying its execution on standard output.
+-- @return boolean: true if command succeeds (status code 0), false
+-- otherwise.
+local function execute(...)
+ io.stdout:write(table.concat({...}, " ").."\n")
+ return fs.execute(...)
+end
+
+--- Driver function for the builtin build back-end.
+-- @param rockspec table: the loaded rockspec.
+-- @return boolean or (nil, string): true if no errors ocurred,
+-- nil and an error message otherwise.
+function run(rockspec)
+ assert(type(rockspec) == "table")
+ local compile_object, compile_library
+
+ local build = rockspec.build
+ local variables = rockspec.variables
+
+ local function add_flags(extras, flag, flags)
+ if flags then
+ if type(flags) ~= "table" then
+ flags = { tostring(flags) }
+ end
+ util.variable_substitutions(flags, variables)
+ for _, v in ipairs(flags) do
+ table.insert(extras, flag:format(v))
+ end
+ end
+ end
+
+ if is_platform("win32") then
+ compile_object = function(object, source, defines, incdirs)
+ local extras = {}
+ add_flags(extras, "-D%s", defines)
+ add_flags(extras, "-I%s", incdirs)
+ return execute(variables.CC.." "..variables.CFLAGS, "-c", "-Fo"..object, "-I"..variables.LUA_INCDIR, source, unpack(extras))
+ end
+ compile_library = function(library, objects, libraries, libdirs, name)
+ local extras = { unpack(objects) }
+ add_flags(extras, "-libpath:%s", libdirs)
+ add_flags(extras, "%s.lib", libraries)
+ local basename = fs.base_name(library):gsub(".[^.]*$", "")
+ local deffile = basename .. ".def"
+ local def = io.open(fs.make_path(fs.current_dir(), deffile), "w+")
+ def:write("EXPORTS\n")
+ def:write("luaopen_"..name:gsub("%.", "_").."\n")
+ def:close()
+ local ok = execute(variables.LD, "-dll", "-def:"..deffile, "-out:"..library, fs.make_path(variables.LUA_LIBDIR, "lua5.1.lib"), unpack(extras))
+ local manifestfile = basename..".dll.manifest"
+ if ok and fs.exists(manifestfile) then
+ ok = execute(variables.MT, "-manifest", manifestfile, "-outputresource:"..basename..".dll;2")
+ end
+ return ok
+ end
+ else
+ compile_object = function(object, source, defines, incdirs)
+ local extras = {}
+ add_flags(extras, "-D%s", defines)
+ add_flags(extras, "-I%s", incdirs)
+ return execute(variables.CC.." "..variables.CFLAGS, "-I"..variables.LUA_INCDIR, "-c", source, "-o", object, unpack(extras))
+ end
+ compile_library = function (library, objects, libraries, libdirs)
+ local extras = { unpack(objects) }
+ add_flags(extras, "-L%s", libdirs)
+ add_flags(extras, "-l%s", libraries)
+ if is_platform("cygwin") then
+ add_flags(extras, "-l%s", {"lua"})
+ end
+ return execute(variables.LD.." "..variables.LIBFLAG, "-o", library, unpack(extras))
+ end
+ end
+
+ local ok = true
+ local built_modules = {}
+ local luadir = path.lua_dir(rockspec.name, rockspec.version)
+ local libdir = path.lib_dir(rockspec.name, rockspec.version)
+ local docdir = path.doc_dir(rockspec.name, rockspec.version)
+ for name, info in pairs(build.modules) do
+ local moddir = path.module_to_path(name)
+ if type(info) == "string" then
+ local ext = info:match(".([^.]+)$")
+ if ext == "lua" then
+ local dest = fs.make_path(luadir, moddir)
+ built_modules[info] = dest
+ else
+ info = {info}
+ end
+ end
+ if type(info) == "table" then
+ local objects = {}
+ local sources = info.sources
+ if info[1] then sources = info end
+ if type(sources) == "string" then sources = {sources} end
+ for _, source in ipairs(sources) do
+ local object = source:gsub(".[^.]*$", "."..cfg.obj_extension)
+ if not object then
+ object = source.."."..cfg.obj_extension
+ end
+ ok = compile_object(object, source, info.defines, info.incdirs)
+ if not ok then break end
+ table.insert(objects, object)
+ end
+ if not ok then break end
+ local module_name = fs.make_path(moddir, name:match("([^.]*)$").."."..cfg.lib_extension):gsub("//", "/")
+ if moddir ~= "" then
+ fs.make_dir(moddir)
+ end
+ local dest = fs.make_path(libdir, moddir)
+ built_modules[module_name] = dest
+ ok = compile_library(module_name, objects, info.libraries, info.libdirs, name)
+ if not ok then break end
+ end
+ end
+ for name, dest in pairs(built_modules) do
+ fs.make_dir(dest)
+ ok = fs.copy(name, dest)
+ if not ok then break end
+ end
+ if ok then
+ if fs.is_dir("lua") then
+ fs.copy_contents("lua", luadir)
+ end
+ end
+ if ok then
+ return true
+ else
+ return nil, "Build error"
+ end
+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 @@
+
+--- Build back-end for CMake-based modules.
+module("luarocks.build.cmake", package.seeall)
+
+local fs = require("luarocks.fs")
+local util = require("luarocks.util")
+local cfg = require("luarocks.cfg")
+
+--- Driver function for the "cmake" build back-end.
+-- @param rockspec table: the loaded rockspec.
+-- @return boolean or (nil, string): true if no errors ocurred,
+-- nil and an error message otherwise.
+function run(rockspec)
+ assert(type(rockspec) == "table")
+ local build = rockspec.build
+ local variables = build.variables or {}
+
+ -- Pass Env variables
+ build.variables.CMAKE_MODULE_PATH=os.getenv("CMAKE_MODULE_PATH")
+ build.variables.CMAKE_LIBRARY_PATH=os.getenv("CMAKE_LIBRARY_PATH")
+ build.variables.CMAKE_INCLUDE_PATH=os.getenv("CMAKE_INCLUDE_PATH")
+
+ util.variable_substitutions(variables, rockspec.variables)
+
+ -- If inline cmake is present create CMakeLists.txt from it.
+ if type(build.cmake) == "string" then
+ local cmake = assert(io.open(fs.current_dir().."/CMakeLists.txt", "w"))
+ cmake:write(build.cmake)
+ cmake:close()
+ end
+
+
+ -- Execute cmake with variables.
+ local args = ""
+ if cfg.cmake_generator then
+ args = args .. ' -G"'..cfg.cmake_generator.. '"'
+ end
+ for k,v in pairs(variables) do
+ args = args .. ' -D' ..k.. '="' ..v.. '"'
+ end
+
+ if not fs.execute("cmake . " ..args) then
+ return nil, "Failed cmake."
+ end
+
+ if not fs.execute("make -fMakefile") then
+ return nil, "Failed building."
+ end
+
+ if not fs.execute("make -fMakefile install") then
+ return nil, "Failed installing."
+ end
+ return true
+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 @@
+
+--- Build back-end for raw listing of commands in rockspec files.
+module("luarocks.build.command", package.seeall)
+
+local fs = require("luarocks.fs")
+local util = require("luarocks.util")
+
+--- Driver function for the "command" build back-end.
+-- @param rockspec table: the loaded rockspec.
+-- @return boolean or (nil, string): true if no errors ocurred,
+-- nil and an error message otherwise.
+function run(rockspec)
+ assert(type(rockspec) == "table")
+
+ local build = rockspec.build
+
+ util.variable_substitutions(build, rockspec.variables)
+
+ if build.build_command then
+ if not fs.execute(build.build_command) then
+ return nil, "Failed building."
+ end
+ end
+ if build.install_command then
+ if not fs.execute(build.install_command) then
+ return nil, "Failed installing."
+ end
+ end
+ return true
+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 @@
+
+--- Build back-end for using Makefile-based packages.
+module("luarocks.build.make", package.seeall)
+
+local fs = require("luarocks.fs")
+local util = require("luarocks.util")
+local cfg = require("luarocks.cfg")
+
+--- Call "make" with given target and variables
+-- @param pass boolean: If true, run make; if false, do nothing.
+-- @param target string: The make target; an empty string indicates
+-- the default target.
+-- @param variables table: A table containing string-string key-value
+-- pairs representing variable assignments to be passed to make.
+-- @return boolean: false if any errors occurred, true otherwise.
+local function make_pass(pass, target, variables)
+ assert(type(pass) == "boolean")
+ assert(type(target) == "string")
+ assert(type(variables) == "table")
+
+ local assignments = {}
+ for k,v in pairs(variables) do
+ table.insert(assignments, k.."="..v)
+ end
+ if pass then
+ return fs.execute(cfg.make.." "..target, unpack(assignments))
+ else
+ return true
+ end
+end
+
+--- Driver function for the "make" build back-end.
+-- @param rockspec table: the loaded rockspec.
+-- @return boolean or (nil, string): true if no errors ocurred,
+-- nil and an error message otherwise.
+function run(rockspec)
+ assert(type(rockspec) == "table")
+
+ local build = rockspec.build
+
+ if build.build_pass == nil then build.build_pass = true end
+ if build.install_pass == nil then build.install_pass = true end
+ build.build_variables = build.build_variables or {}
+ build.install_variables = build.install_variables or {}
+ build.build_target = build.build_target or ""
+ build.install_target = build.install_target or "install"
+ local makefile = build.makefile or cfg.makefile
+ if makefile then
+ -- Assumes all make's accept -f. True for POSIX make, GNU make and Microsoft nmake.
+ build.build_target = "-f "..makefile.." "..build.build_target
+ build.install_target = "-f "..makefile.." "..build.install_target
+ end
+
+ if build.variables then
+ for var, val in pairs(build.variables) do
+ build.build_variables[var] = val
+ build.install_variables[var] = val
+ end
+ end
+
+ util.variable_substitutions(build.build_variables, rockspec.variables)
+ util.variable_substitutions(build.install_variables, rockspec.variables)
+
+ local auto_variables = { "CC" }
+
+ for _, variable in pairs(auto_variables) do
+ if not build.build_variables[variable] then
+ build.build_variables[variable] = rockspec.variables[variable]
+ end
+ if not build.install_variables[variable] then
+ build.install_variables[variable] = rockspec.variables[variable]
+ end
+ end
+
+ local ok = make_pass(build.build_pass, build.build_target, build.build_variables)
+ if not ok then
+ return nil, "Failed building."
+ end
+ ok = make_pass(build.install_pass, build.install_target, build.install_variables)
+ if not ok then
+ return nil, "Failed installing."
+ end
+ return true
+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 @@
+
+local rawset, next, table, pairs, print, require, io, os, setmetatable =
+ rawset, next, table, pairs, print, require, io, os, setmetatable
+
+--- Configuration for LuaRocks.
+-- Tries to load the user's configuration file and
+-- defines defaults for unset values. See the
+-- config
+-- file format documentation for details.
+module("luarocks.cfg")
+
+local persist = require("luarocks.persist")
+
+local detected = {}
+local system,proc
+
+-- A proper installation of LuaRocks will hardcode the system
+-- and proc values with LUAROCKS_UNAME_S and LUAROCKS_UNAME_M,
+-- so that this detection does not run every time. When it is
+-- performed, we use the Unix way to identify the system,
+-- even on Windows (assuming UnxUtils or Cygwin).
+system = LUAROCKS_UNAME_S or io.popen("uname -s"):read("*l")
+proc = LUAROCKS_UNAME_M or io.popen("uname -m"):read("*l")
+if proc:match("i[%d]86") then
+ proc = "x86"
+elseif proc:match("amd64") or proc:match("x86_64") then
+ proc = "x86_64"
+elseif proc:match("Power Macintosh") then
+ proc = "powerpc"
+end
+
+if system == "FreeBSD" then
+ detected.unix = true
+ detected.freebsd = true
+elseif system == "Darwin" then
+ detected.unix = true
+ detected.macosx = true
+elseif system == "Linux" then
+ detected.unix = true
+ detected.linux = true
+elseif system and system:match("^CYGWIN") then
+ detected.unix = true
+ detected.cygwin = true
+elseif system and system:match("^Windows") then
+ detected.windows = true
+else
+ detected.unix = true
+ -- Fall back to Unix in unknown systems.
+end
+
+local sys_config_file, home_config_file, home_tree
+if detected.windows then
+ home = os.getenv("APPDATA") or "c:"
+ sys_config_file = "c:/luarocks/config.lua"
+ home_config_file = home.."/luarocks/config.lua"
+ home_tree = home.."/luarocks/"
+else
+ home = os.getenv("HOME") or ""
+ sys_config_file = "/etc/luarocks/config.lua"
+ home_config_file = home.."/.luarocks/config.lua"
+ home_tree = home.."/.luarocks/"
+end
+
+variables = {}
+rocks_trees = {}
+
+persist.load_into_table(LUAROCKS_SYSCONFIG or sys_config_file, _M)
+
+if not LUAROCKS_FORCE_CONFIG then
+ home_config_file = os.getenv("LUAROCKS_CONFIG") or home_config_file
+ local home_overrides = persist.load_into_table(home_config_file, { home = home })
+ if home_overrides then
+ local util = require("luarocks.util")
+ util.deep_merge(_M, home_overrides)
+ end
+end
+
+local defaults = {
+ arch = "unknown",
+ lib_extension = "unknown",
+ obj_extension = "unknown",
+ rocks_servers = {
+ "http://luarocks.luaforge.net/rocks"
+ },
+ lua_extension = "lua",
+ lua_interpreter = LUA_INTERPRETER or "lua",
+ downloader = LUAROCKS_DOWNLOADER or "wget",
+ md5checker = LUAROCKS_MD5CHECKER or "md5sum",
+ variables = {}
+}
+
+defaults.external_deps_subdirs = {
+ bin = "bin",
+ lib = "lib",
+ include = "include"
+}
+defaults.runtime_external_deps_subdirs = defaults.external_deps_subdirs
+
+if detected.windows then
+ home_config_file = home_config_file:gsub("\\","/")
+ defaults.lib_extension = "dll"
+ defaults.obj_extension = "obj"
+ local rootdir = LUAROCKS_ROCKS_TREE or home_tree
+ defaults.root_dir = rootdir
+ defaults.rocks_dir = rootdir.."/rocks/"
+ defaults.scripts_dir = rootdir.."/bin/"
+ defaults.external_deps_dirs = { "c:/external/" }
+ defaults.variables.LUA_BINDIR = LUA_BINDIR and LUA_BINDIR:gsub("\\", "/") or "c:/lua5.1/bin"
+ defaults.variables.LUA_INCDIR = LUA_INCDIR and LUA_INCDIR:gsub("\\", "/") or "c:/lua5.1/include"
+ defaults.variables.LUA_LIBDIR = LUA_LIBDIR and LUA_LIBDIR:gsub("\\", "/") or "c:/lua5.1/lib"
+ defaults.arch = "win32-"..proc
+ defaults.platforms = {"win32", "windows" }
+ defaults.cmake_generator = "MinGW Makefiles"
+ -- TODO: Split Windows flavors between mingw and msvc
+ -- defaults.make = "make"
+ -- defaults.makefile = "Makefile"
+ defaults.make = "nmake"
+ defaults.makefile = "Makefile.win"
+ defaults.variables.CC = "cl"
+ defaults.variables.LD = "link"
+ defaults.variables.MT = "mt"
+ defaults.variables.CFLAGS = "/MD /O2"
+ defaults.variables.LIBFLAG = "/dll"
+ defaults.external_deps_patterns = {
+ bin = { "?.exe", "?.bat" },
+ lib = { "?.lib", "?.dll" },
+ include = { "?.h" }
+ }
+ defaults.runtime_external_deps_patterns = {
+ bin = { "?.exe", "?.bat" },
+ lib = { "?.dll" },
+ include = { "?.h" }
+ }
+end
+
+if detected.unix then
+ defaults.lib_extension = "so"
+ defaults.obj_extension = "o"
+ local rootdir = LUAROCKS_ROCKS_TREE or home_tree
+ defaults.root_dir = rootdir
+ defaults.rocks_dir = rootdir.."/rocks/"
+ defaults.scripts_dir = rootdir.."/bin/"
+ defaults.external_deps_dirs = { "/usr/local", "/usr" }
+ defaults.variables.LUA_BINDIR = LUA_BINDIR or "/usr/local/bin"
+ defaults.variables.LUA_INCDIR = LUA_INCDIR or "/usr/local/include"
+ defaults.variables.LUA_LIBDIR = LUA_LIBDIR or "/usr/local/lib"
+ defaults.variables.CFLAGS = "-O2"
+ defaults.cmake_generator = "Unix Makefiles"
+ defaults.make = "make"
+ defaults.platforms = { "unix" }
+ defaults.variables.CC = "cc"
+ defaults.variables.LD = "ld"
+ defaults.variables.LIBFLAG = "-shared"
+ defaults.external_deps_patterns = {
+ bin = { "?" },
+ lib = { "lib?.a", "lib?.so" },
+ include = { "?.h" }
+ }
+ defaults.runtime_external_deps_patterns = {
+ bin = { "?" },
+ lib = { "lib?.so" },
+ include = { "?.h" }
+ }
+end
+
+if detected.cygwin then
+ defaults.lib_extension = "so" -- can be overridden in the config file for mingw builds
+ defaults.arch = "cygwin-"..proc
+ defaults.platforms = {"unix", "cygwin"}
+ defaults.cmake_generator = "Unix Makefiles"
+ defaults.variables.CC = "echo -llua | xargs gcc"
+ defaults.variables.LD = "echo -llua | xargs gcc"
+ defaults.variables.LIBFLAG = "-shared"
+end
+
+defaults.external_lib_extension = defaults.lib_extension
+
+if detected.macosx then
+ defaults.external_lib_extension = "dylib"
+ defaults.arch = "macosx-"..proc
+ defaults.platforms = {"unix", "macosx"}
+ defaults.variables.CC = "export MACOSX_DEPLOYMENT_TARGET=10.3; gcc"
+ defaults.variables.LD = "export MACOSX_DEPLOYMENT_TARGET=10.3; gcc"
+ defaults.variables.LIBFLAG = "-bundle -undefined dynamic_lookup -all_load"
+end
+
+if detected.linux then
+ defaults.arch = "linux-"..proc
+ defaults.platforms = {"unix", "linux"}
+ defaults.variables.CC = "gcc"
+ defaults.variables.LD = "gcc"
+ defaults.variables.LIBFLAG = "-shared"
+end
+
+if detected.freebsd then
+ defaults.arch = "freebsd-"..proc
+ defaults.make = "gmake"
+ defaults.platforms = {"unix", "freebsd"}
+ defaults.variables.CC = "gcc"
+ defaults.variables.LD = "gcc"
+ defaults.variables.LIBFLAG = "-shared"
+end
+
+if proc == "x86_64" and not defaults.variables.CFLAGS:match("-fPIC") then
+ defaults.variables.CFLAGS = defaults.variables.CFLAGS.." -fPIC"
+end
+
+-- Expose some more values detected by LuaRocks for use by rockspec authors.
+defaults.variables.LUA = defaults.lua_interpreter
+defaults.variables.LIB_EXTENSION = defaults.lib_extension
+defaults.variables.OBJ_EXTENSION = defaults.obj_extension
+defaults.variables.LUAROCKS_PREFIX = LUAROCKS_PREFIX
+
+local cfg_mt = {
+ __index = function(t, k)
+ local default = defaults[k]
+ if default then
+ rawset(t, k, default)
+ end
+ return default
+ end
+}
+
+if not _M.variables then
+ _M.variables = {}
+end
+for k,v in pairs(defaults.variables) do
+ if not _M.variables[k] then
+ _M.variables[k] = v
+ end
+end
+
+setmetatable(_M, cfg_mt)
+
+if not next(rocks_trees) then
+ if home_tree then
+ table.insert(rocks_trees, home_tree)
+ end
+ if LUAROCKS_ROCKS_TREE then
+ table.insert(rocks_trees, LUAROCKS_ROCKS_TREE)
+ end
+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 @@
+
+program_version = "1.0"
+
+--- Functions for command-line scripts.
+module("luarocks.command_line", package.seeall)
+
+local util = require("luarocks.util")
+local cfg = require("luarocks.cfg")
+local fs = require("luarocks.fs")
+
+--- Display an error message and exit.
+-- @param message string: The error message.
+local function die(message)
+ assert(type(message) == "string")
+
+ local ok, err = pcall(util.run_scheduled_functions)
+ if not ok then
+ print("\nLuaRocks "..program_version.." internal bug (please report at luarocks-developers@lists.luaforge.net):\n"..err)
+ end
+ print("\nError: "..message)
+ os.exit(1)
+end
+
+--- Main command-line processor.
+-- Parses input arguments and calls the appropriate driver function
+-- to execute the action requested on the command-line, forwarding
+-- to it any additional arguments passed by the user.
+-- Uses the global table "commands", which contains
+-- the loaded modules representing commands.
+-- @param ... string: Arguments given on the command-line.
+function run_command(...)
+ local args = {...}
+ local cmdline_vars = {}
+ for i = #args, 1, -1 do
+ local arg = args[i]
+ if arg:match("^[^-][^=]*=") then
+ local var, val = arg:match("^([A-Z_][A-Z0-9_]*)=(.*)")
+ if val then
+ cmdline_vars[var] = val
+ table.remove(args, i)
+ else
+ die("Invalid assignment: "..arg)
+ end
+ end
+ end
+ local nonflags = { util.parse_flags(unpack(args)) }
+ local flags = table.remove(nonflags, 1)
+
+ if flags["to"] then
+ if flags["to"] == true then
+ die("Argument error: use --to=")
+ end
+ local root_dir = fs.absolute_name(flags["to"])
+ cfg.root_dir = root_dir
+ cfg.rocks_dir = root_dir.."/rocks"
+ cfg.scripts_dir = root_dir.."/bin"
+ else
+ local trees = cfg.rocks_trees
+ for i = #trees, 1, -1 do
+ local tree = trees[i]
+ if fs.make_dir(tree) and fs.is_writable(tree) then
+ cfg.root_dir = tree
+ cfg.rocks_dir = tree.."/rocks"
+ cfg.scripts_dir = rawget(cfg, "scripts_dir") or tree.."/bin"
+ break
+ end
+ end
+ end
+
+ cfg.root_dir = cfg.root_dir:gsub("/+$", "")
+ cfg.rocks_dir = cfg.rocks_dir:gsub("/+$", "")
+ cfg.scripts_dir = cfg.scripts_dir:gsub("/+$", "")
+
+ cfg.variables.ROCKS_TREE = cfg.root_dir
+ cfg.variables.SCRIPTS_DIR = cfg.scripts_dir
+
+ if flags["from"] then
+ if flags["from"] == true then
+ die("Argument error: use --from=")
+ end
+ local protocol, path = fs.split_url(flags["from"])
+ table.insert(cfg.rocks_servers, 1, protocol.."://"..path)
+ end
+
+ if flags["only-from"] then
+ if flags["only-from"] == true then
+ die("Argument error: use --only-from=")
+ end
+ cfg.rocks_servers = { flags["only-from"] }
+ end
+
+ local command
+
+ if flags["version"] then
+ print(program_name.." "..program_version)
+ print(program_description)
+ print()
+ os.exit(0)
+ elseif flags["help"] or #nonflags == 0 then
+ command = "help"
+ args = nonflags
+ else
+ command = nonflags[1]
+ for i, arg in ipairs(args) do
+ if arg == command then
+ table.remove(args, i)
+ break
+ end
+ end
+ end
+
+ if command ~= "help" then
+ for k, v in pairs(cmdline_vars) do
+ cfg.variables[k] = v
+ end
+ end
+
+ command = command:gsub("-", "_")
+ if commands[command] then
+ local xp, ok, err = xpcall(function() return commands[command].run(unpack(args)) end, function(err)
+ die(debug.traceback("LuaRocks "..program_version
+ .." bug (please report at luarocks-developers@lists.luaforge.net).\n"
+ ..err, 2))
+ end)
+ if xp and (not ok) then
+ die(err)
+ end
+ else
+ die("Unknown command: "..command)
+ end
+
+ util.run_scheduled_functions()
+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 @@
+
+--- Dependency handling functions.
+-- Dependencies are represented in LuaRocks through strings with
+-- a package name followed by a comma-separated list of constraints.
+-- Each constraint consists of an operator and a version number.
+-- In this string format, version numbers are represented as
+-- naturally as possible, like they are used by upstream projects
+-- (e.g. "2.0beta3"). Internally, LuaRocks converts them to a purely
+-- numeric representation, allowing comparison following some
+-- "common sense" heuristics. The precise specification of the
+-- comparison criteria is the source code of this module, but the
+-- test/test_deps.lua file included with LuaRocks provides some
+-- insights on what these criteria are.
+module("luarocks.deps", package.seeall)
+
+local rep = require("luarocks.rep")
+local search = require("luarocks.search")
+local install = require("luarocks.install")
+local cfg = require("luarocks.cfg")
+local manif = require("luarocks.manif")
+local fs = require("luarocks.fs")
+local fetch = require("luarocks.fetch")
+local path = require("luarocks.path")
+
+local operators = {
+ ["=="] = "==",
+ ["~="] = "~=",
+ [">"] = ">",
+ ["<"] = "<",
+ [">="] = ">=",
+ ["<="] = "<=",
+ ["~>"] = "~>",
+ -- plus some convenience translations
+ [""] = "==",
+ ["="] = "==",
+ ["!="] = "~="
+}
+
+local deltas = {
+ scm = 1000,
+ cvs = 1000,
+ rc = -1000,
+ pre = -10000,
+ beta = -100000,
+ alpha = -1000000
+}
+
+local version_mt = {
+ --- Equality comparison for versions.
+ -- All version numbers must be equal.
+ -- If both versions have revision numbers, they must be equal;
+ -- otherwise the revision number is ignored.
+ -- @param v1 table: version table to compare.
+ -- @param v2 table: version table to compare.
+ -- @return boolean: true if they are considered equivalent.
+ __eq = function(v1, v2)
+ if #v1 ~= #v2 then
+ return false
+ end
+ for i = 1, #v1 do
+ if v1[i] ~= v2[i] then
+ return false
+ end
+ end
+ if v1.revision and v2.revision then
+ return (v1.revision == v2.revision)
+ end
+ return true
+ end,
+ --- Size comparison for versions.
+ -- All version numbers are compared.
+ -- If both versions have revision numbers, they are compared;
+ -- otherwise the revision number is ignored.
+ -- @param v1 table: version table to compare.
+ -- @param v2 table: version table to compare.
+ -- @return boolean: true if v1 is considered lower than v2.
+ __lt = function(v1, v2)
+ for i = 1, math.max(#v1, #v2) do
+ local v1i, v2i = v1[i] or 0, v2[i] or 0
+ if v1i ~= v2i then
+ return (v1i < v2i)
+ end
+ end
+ if v1.revision and v2.revision then
+ return (v1.revision < v2.revision)
+ end
+ return false
+ end
+}
+
+local version_cache = {}
+setmetatable(version_cache, {
+ __mode = "kv"
+})
+
+--- Parse a version string, converting to table format.
+-- A version table contains all components of the version string
+-- converted to numeric format, stored in the array part of the table.
+-- If the version contains a revision, it is stored numerically
+-- in the 'revision' field. The original string representation of
+-- the string is preserved in the 'string' field.
+-- Returned version tables use a metatable
+-- allowing later comparison through relational operators.
+-- @param vstring string: A version number in string format.
+-- @return table or nil: A version table or nil
+-- if the input string contains invalid characters.
+function parse_version(vstring)
+ if not vstring then return nil end
+ assert(type(vstring) == "string")
+
+ local cached = version_cache[vstring]
+ if cached then
+ return cached
+ end
+
+ local version = {}
+ local i = 1
+
+ local function add_token(number)
+ version[i] = version[i] and version[i] + number/100000 or number
+ i = i + 1
+ end
+
+ -- trim leading and trailing spaces
+ vstring = vstring:match("^%s*(.*)%s*$")
+ version.string = vstring
+ -- store revision separately if any
+ local main, revision = vstring:match("(.*)%-(%d+)$")
+ if revision then
+ vstring = main
+ version.revision = tonumber(revision)
+ end
+ while #vstring > 0 do
+ -- extract a number
+ local token, rest = vstring:match("^(%d+)[%.%-%_]*(.*)")
+ if token then
+ add_token(tonumber(token))
+ else
+ -- extract a word
+ token, rest = vstring:match("^(%a+)[%.%-%_]*(.*)")
+ if not token then
+ return nil
+ end
+ local last = #version
+ version[i] = deltas[token] or (token:byte() / 1000)
+ end
+ vstring = rest
+ end
+ setmetatable(version, version_mt)
+ version_cache[vstring] = version
+ return version
+end
+
+--- Utility function to compare version numbers given as strings.
+-- @param a string: one version.
+-- @param b string: another version.
+-- @return boolean: True if a > b.
+function compare_versions(a, b)
+ return parse_version(a) > parse_version(b)
+end
+
+--- Consumes a constraint from a string, converting it to table format.
+-- For example, a string ">= 1.0, > 2.0" is converted to a table in the
+-- format {op = ">=", version={1,0}} and the rest, "> 2.0", is returned
+-- back to the caller.
+-- @param input string: A list of constraints in string format.
+-- @return (table, string) or nil: A table representing the same
+-- constraints and the string with the unused input, or nil if the
+-- input string is invalid.
+local function parse_constraint(input)
+ assert(type(input) == "string")
+
+ local no_upgrade, op, version, rest = input:match("^(@?)([<>=~!]*)%s*([%w%.%_%-]+)[%s,]*(.*)")
+ op = operators[op]
+ version = parse_version(version)
+ if not op or not version then return nil end
+ return { op = op, version = version, no_upgrade = no_upgrade=="@" and true or nil }, rest
+end
+
+--- Convert a list of constraints from string to table format.
+-- For example, a string ">= 1.0, < 2.0" is converted to a table in the format
+-- {{op = ">=", version={1,0}}, {op = "<", version={2,0}}}.
+-- Version tables use a metatable allowing later comparison through
+-- relational operators.
+-- @param input string: A list of constraints in string format.
+-- @return table or nil: A table representing the same constraints,
+-- or nil if the input string is invalid.
+function parse_constraints(input)
+ assert(type(input) == "string")
+
+ local constraints, constraint = {}, nil
+ while #input > 0 do
+ constraint, input = parse_constraint(input)
+ if constraint then
+ table.insert(constraints, constraint)
+ else
+ return nil
+ end
+ end
+ return constraints
+end
+
+--- Convert a dependency from string to table format.
+-- For example, a string "foo >= 1.0, < 2.0"
+-- is converted to a table in the format
+-- {name = "foo", constraints = {{op = ">=", version={1,0}},
+-- {op = "<", version={2,0}}}}. Version tables use a metatable
+-- allowing later comparison through relational operators.
+-- @param dep string: A dependency in string format
+-- as entered in rockspec files.
+-- @return table or nil: A table representing the same dependency relation,
+-- or nil if the input string is invalid.
+function parse_dep(dep)
+ assert(type(dep) == "string")
+
+ local name, rest = dep:match("^%s*(%a[%w%-]*%w)%s*(.*)")
+ if not name then return nil end
+ local constraints = parse_constraints(rest)
+ if not constraints then return nil end
+ return { name = name, constraints = constraints }
+end
+
+--- Convert a version table to a string.
+-- @param v table: The version table
+-- @param internal boolean or nil: Whether to display versions in their
+-- internal representation format or how they were specified.
+-- @return string: The dependency information pretty-printed as a string.
+function show_version(v, internal)
+ assert(type(v) == "table")
+ assert(type(internal) == "boolean" or not internal)
+
+ return (internal
+ and table.concat(v, ":")..(v.revision and tostring(v.revision) or "")
+ or v.string)
+end
+
+--- Convert a dependency in table format to a string.
+-- @param dep table: The dependency in table format
+-- @param internal boolean or nil: Whether to display versions in their
+-- internal representation format or how they were specified.
+-- @return string: The dependency information pretty-printed as a string.
+function show_dep(dep, internal)
+ assert(type(dep) == "table")
+ assert(type(internal) == "boolean" or not internal)
+
+ local pretty = {}
+ for _, c in ipairs(dep.constraints) do
+ table.insert(pretty, c.op .. " " .. show_version(c.version, internal))
+ end
+ return dep.name.." "..table.concat(pretty, ", ")
+end
+
+--- A more lenient check for equivalence between versions.
+-- This returns true if the requested components of a version
+-- match and ignore the ones that were not given. For example,
+-- when requesting "2", then "2", "2.1", "2.3.5-9"... all match.
+-- When requesting "2.1", then "2.1", "2.1.3" match, but "2.2"
+-- doesn't.
+-- @param version string or table: Version to be tested; may be
+-- in string format or already parsed into a table.
+-- @param requested string or table: Version requested; may be
+-- in string format or already parsed into a table.
+-- @return boolean: True if the tested version matches the requested
+-- version, false otherwise.
+local function partial_match(version, requested)
+ assert(type(version) == "string" or type(version) == "table")
+ assert(type(requested) == "string" or type(version) == "table")
+
+ if type(version) ~= "table" then version = parse_version(version) end
+ if type(requested) ~= "table" then requested = parse_version(requested) end
+ if not version or not requested then return false end
+
+ for i, ri in ipairs(requested) do
+ local vi = version[i] or 0
+ if ri ~= vi then return false end
+ end
+ if requested.revision then
+ return requested.revision == version.revision
+ end
+ return true
+end
+
+--- Check if a version satisfies a set of constraints.
+-- @param version table: A version in table format
+-- @param constraints table: An array of constraints in table format.
+-- @return boolean: True if version satisfies all constraints,
+-- false otherwise.
+function match_constraints(version, constraints)
+ assert(type(version) == "table")
+ assert(type(constraints) == "table")
+ local ok = true
+ setmetatable(version, version_mt)
+ for _, constr in pairs(constraints) do
+ local constr_version = constr.version
+ setmetatable(constr.version, version_mt)
+ if constr.op == "==" then ok = version == constr_version
+ elseif constr.op == "~=" then ok = version ~= constr_version
+ elseif constr.op == ">" then ok = version > constr_version
+ elseif constr.op == "<" then ok = version < constr_version
+ elseif constr.op == ">=" then ok = version >= constr_version
+ elseif constr.op == "<=" then ok = version <= constr_version
+ elseif constr.op == "~>" then ok = partial_match(version, constr_version)
+ end
+ if not ok then break end
+ end
+ return ok
+end
+
+--- Attempt to match a dependency to an installed rock.
+-- @param dep table: A dependency parsed in table format.
+-- @param blacklist table: Versions that can't be accepted. Table where keys
+-- are program versions and values are 'true'.
+-- @return table or nil: A table containing fields 'name' and 'version'
+-- representing an installed rock which matches the given dependency,
+-- or nil if it could not be matched.
+local function match_dep(dep, blacklist)
+ assert(type(dep) == "table")
+
+ local versions
+ if dep.name == "lua" then
+ versions = { "5.1" }
+ else
+ versions = manif.get_versions(dep.name)
+ end
+ if not versions then
+ return nil
+ end
+ if blacklist then
+ local i = 1
+ while versions[i] do
+ if blacklist[versions[i]] then
+ table.remove(versions, i)
+ else
+ i = i + 1
+ end
+ end
+ end
+ local candidates = {}
+ for _, vstring in ipairs(versions) do
+ local version = parse_version(vstring)
+ if match_constraints(version, dep.constraints) then
+ table.insert(candidates, version)
+ end
+ end
+ if #candidates == 0 then
+ return nil
+ else
+ table.sort(candidates)
+ return {
+ name = dep.name,
+ version = candidates[#candidates].string
+ }
+ end
+end
+
+--- Attempt to match dependencies of a rockspec to installed rocks.
+-- @param rockspec table: The rockspec loaded as a table.
+-- @param blacklist table or nil: Program versions to not use as valid matches.
+-- Table where keys are program names and values are tables where keys
+-- are program versions and values are 'true'.
+-- @return table, table: A table where keys are dependencies parsed
+-- in table format and values are tables containing fields 'name' and
+-- version' representing matches, and a table of missing dependencies
+-- parsed as tables.
+function match_deps(rockspec, blacklist)
+ assert(type(rockspec) == "table")
+ assert(type(blacklist) == "table" or not blacklist)
+ local matched, missing, no_upgrade = {}, {}, {}
+
+ for _, dep in ipairs(rockspec.dependencies) do
+ local found = match_dep(dep, blacklist and blacklist[dep.name] or nil)
+ if found then
+ if dep.name ~= "lua" then
+ matched[dep] = found
+ end
+ else
+ if dep.constraints[1] and dep.constraints[1].no_upgrade then
+ no_upgrade[dep.name] = dep
+ else
+ missing[dep.name] = dep
+ end
+ end
+ end
+ return matched, missing, no_upgrade
+end
+
+--- Return a set of values of a table.
+-- @param tbl table: The input table.
+-- @return table: The array of keys.
+local function values_set(tbl)
+ local set = {}
+ for _, v in pairs(tbl) do
+ set[v] = true
+ end
+ return set
+end
+
+--- Check dependencies of a rock and attempt to install any missing ones.
+-- Packages are installed using the LuaRocks "install" command.
+-- Aborts the program if a dependency could not be fulfilled.
+-- @param rockspec table: A rockspec in table format.
+-- @return boolean or (nil, string): True if no errors occurred, or
+-- nil and an error message if any test failed.
+function fulfill_dependencies(rockspec)
+
+ if rockspec.supported_platforms then
+ if not platforms_set then
+ platforms_set = values_set(cfg.platforms)
+ end
+ local supported = nil
+ for _, plat in pairs(rockspec.supported_platforms) do
+ local neg, plat = plat:match("^(!?)(.*)")
+ if neg == "!" then
+ if platforms_set[plat] then
+ return nil, "This rockspec for "..rockspec.package.." does not support "..plat.." platforms."
+ end
+ else
+ if platforms_set[plat] then
+ supported = true
+ else
+ if supported == nil then
+ supported = false
+ end
+ end
+ end
+ end
+ if supported == false then
+ local plats = table.concat(cfg.platforms, ", ")
+ return nil, "This rockspec for "..rockspec.package.." does not support "..plats.." platforms."
+ end
+ end
+
+ local matched, missing, no_upgrade = match_deps(rockspec)
+
+ if next(no_upgrade) then
+ print("Missing dependencies for "..rockspec.name.." "..rockspec.version..":")
+ for _, dep in pairs(no_upgrade) do
+ print(show_dep(dep))
+ end
+ if next(missing) then
+ for _, dep in pairs(missing) do
+ print(show_dep(dep))
+ end
+ end
+ print()
+ for _, dep in pairs(no_upgrade) do
+ print("This version of "..rockspec.name.." is designed for use with")
+ print(show_dep(dep)..", but is configured to avoid upgrading it")
+ print("automatically. Please upgrade "..dep.name.." with")
+ print(" luarocks install "..dep.name)
+ print("or choose an older version of "..rockspec.name.." with")
+ print(" luarocks search "..rockspec.name)
+ end
+ return nil, "Failed matching dependencies."
+ end
+
+ if next(missing) then
+ print()
+ print("Missing dependencies for "..rockspec.name..":")
+ for _, dep in pairs(missing) do
+ print(show_dep(dep))
+ end
+ print()
+
+ for _, dep in pairs(missing) do
+ -- Double-check in case dependency was filled during recursion.
+ if not match_dep(dep) then
+ local rock = search.find_suitable_rock(dep)
+ if not rock then
+ return nil, "Could not find a rock to satisfy dependency: "..show_dep(dep)
+ end
+ local ok, err = install.run(rock)
+ if not ok then
+ return nil, "Failed installing dependency: "..rock.." - "..err
+ end
+ end
+ end
+ end
+ return true
+end
+
+--- Set up path-related variables for external dependencies.
+-- For each key in the external_dependencies table in the
+-- rockspec file, four variables are created: _DIR, _BINDIR,
+-- _INCDIR and _LIBDIR. These are not overwritten
+-- if already set (e.g. by the LuaRocks config file or through the
+-- command-line). Values in the external_dependencies table
+-- are tables that may contain a "header" or a "library" field,
+-- with filenames to be tested for existence.
+-- @param rockspec table: The rockspec table.
+-- @param mode string: if "build" is given, checks all files;
+-- if "install" is given, do not scan for headers.
+-- @return boolean or (nil, string): True if no errors occurred, or
+-- nil and an error message if any test failed.
+function check_external_deps(rockspec, mode)
+ assert(type(rockspec) == "table")
+
+ local vars = rockspec.variables
+ local patterns = cfg.external_deps_patterns
+ local subdirs = cfg.external_deps_subdirs
+ if mode == "install" then
+ patterns = cfg.runtime_external_deps_patterns
+ subdirs = cfg.runtime_external_deps_subdirs
+ end
+ local dirs = {
+ BINDIR = { subdir = subdirs.bin, testfile = "program", pattern = patterns.bin },
+ INCDIR = { subdir = subdirs.include, testfile = "header", pattern = patterns.include },
+ LIBDIR = { subdir = subdirs.lib, testfile = "library", pattern = patterns.lib }
+ }
+ if mode == "install" then
+ dirs.INCDIR = nil
+ end
+ if rockspec.external_dependencies then
+ for name, files in pairs(rockspec.external_dependencies) do
+ local ok = true
+ local failed_file = nil
+ for _, extdir in ipairs(cfg.external_deps_dirs) do
+ ok = true
+ local prefix = vars[name.."_DIR"]
+ if not prefix then
+ prefix = extdir
+ end
+ for dirname, dirdata in pairs(dirs) do
+ dirdata.dir = vars[name.."_"..dirname] or fs.make_path(prefix, dirdata.subdir)
+ local file = files[dirdata.testfile]
+ if file then
+ local files = {}
+ if not file:match("%.") then
+ for _, pattern in ipairs(dirdata.pattern) do
+ table.insert(files, pattern:gsub("?", file))
+ end
+ else
+ table.insert(files, file)
+ end
+ local found = false
+ failed_file = nil
+ for _, f in pairs(files) do
+ if f:match("%.so$") or f:match("%.dylib$") or f:match("%.dll$") then
+ f = f:gsub("%.[^.]+$", "."..cfg.external_lib_extension)
+ end
+ local testfile = fs.make_path(dirdata.dir, f)
+ if fs.exists(testfile) then
+ found = true
+ break
+ else
+ if failed_file then
+ failed_file = failed_file .. ", or " .. f
+ else
+ failed_file = f
+ end
+ end
+ end
+ if not found then
+ ok = false
+ break
+ end
+ end
+ end
+ if ok then
+ for dirname, dirdata in pairs(dirs) do
+ vars[name.."_"..dirname] = dirdata.dir
+ end
+ vars[name.."_DIR"] = prefix
+ break
+ end
+ end
+ if not ok then
+ 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"
+ end
+ end
+ end
+ return true
+end
+
+--- Recursively scan dependencies, to build a transitive closure of all
+-- dependent packages.
+-- @param results table: The results table being built.
+-- @param name string: Package name.
+-- @param version string: Package version.
+-- @return (table, table): The results and a table of missing dependencies.
+function scan_deps(results, missing, manifest, name, version)
+ assert(type(results) == "table")
+ assert(type(missing) == "table")
+ assert(type(name) == "string")
+ assert(type(version) == "string")
+
+ local err
+ if results[name] then
+ return results, missing
+ end
+ if not manifest.dependencies then manifest.dependencies = {} end
+ local dependencies = manifest.dependencies
+ if not dependencies[name] then dependencies[name] = {} end
+ local dependencies_name = dependencies[name]
+ local deplist = dependencies_name[version]
+ local rockspec, err
+ if not deplist then
+ rockspec, err = fetch.load_local_rockspec(path.rockspec_file(name, version))
+ if err then
+ missing[name.." "..version] = true
+ return results, missing
+ end
+ dependencies_name[version] = rockspec.dependencies
+ else
+ rockspec = { dependencies = deplist }
+ end
+ local matched, failures = match_deps(rockspec)
+ for _, match in pairs(matched) do
+ results, missing = scan_deps(results, missing, manifest, match.name, match.version)
+ end
+ if next(failures) then
+ for _, failure in pairs(failures) do
+ missing[show_dep(failure)] = true
+ end
+ end
+ results[name] = version
+ return results, missing
+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 @@
+
+--- Functions related to fetching and loading local and remote files.
+module("luarocks.fetch", package.seeall)
+
+local fs = require("luarocks.fs")
+local type_check = require("luarocks.type_check")
+local path = require("luarocks.path")
+local deps = require("luarocks.deps")
+local persist = require("luarocks.persist")
+local util = require("luarocks.util")
+
+--- Fetch a local or remote file.
+-- Make a remote or local URL/pathname local, fetching the file if necessary.
+-- Other "fetch" and "load" functions use this function to obtain files.
+-- If a local pathname is given, it is returned as a result.
+-- @param url string: a local pathname or a remote URL.
+-- @param filename string or nil: this function attempts to detect the
+-- resulting local filename of the remote file as the basename of the URL;
+-- if that is not correct (due to a redirection, for example), the local
+-- filename can be given explicitly as this second argument.
+-- @return string or (nil, string): the absolute local pathname for the
+-- fetched file, or nil and a message in case of errors.
+function fetch_url(url, filename)
+ assert(type(url) == "string")
+ assert(type(filename) == "string" or not filename)
+
+ local protocol, pathname = fs.split_url(url)
+ if protocol == "file" then
+ return fs.absolute_name(pathname)
+ elseif protocol == "http" or protocol == "ftp" or protocol == "https" then
+ local ok = fs.download(url)
+ if not ok then
+ return nil, "Failed downloading "..url
+ end
+ return fs.make_path(fs.current_dir(), filename or fs.base_name(url))
+ else
+ return nil, "Unsupported protocol "..protocol
+ end
+end
+
+--- For remote URLs, create a temporary directory and download URL inside it.
+-- This temporary directory will be deleted on program termination.
+-- For local URLs, just return the local pathname and its directory.
+-- @param url string: URL to be downloaded
+-- @param tmpname string: name pattern to use for avoiding conflicts
+-- when creating temporary directory.
+-- @param filename string or nil: local filename of URL to be downloaded,
+-- in case it can't be inferred from the URL.
+-- @return (string, string) or (nil, string): absolute local pathname of
+-- the fetched file and temporary directory name; or nil and an error message.
+function fetch_url_at_temp_dir(url, tmpname, filename)
+ assert(type(url) == "string")
+ assert(type(tmpname) == "string")
+ assert(type(filename) == "string" or not filename)
+ filename = filename or fs.base_name(url)
+
+ local protocol, pathname = fs.split_url(url)
+ if protocol == "file" then
+ return pathname, fs.dir_name(pathname)
+ else
+ local dir = fs.make_temp_dir(tmpname)
+ if not dir then
+ return nil, "Failed creating temporary directory."
+ end
+ util.schedule_function(fs.delete, dir)
+ fs.change_dir(dir)
+ local file, err = fetch_url(url, filename)
+ if not file then
+ return nil, "Error fetching file: "..err
+ end
+ fs.pop_dir()
+ return file, dir
+ end
+end
+
+--- Obtain a rock and unpack it.
+-- If a directory is not given, a temporary directory will be created,
+-- which will be deleted on program termination.
+-- @param rock_file string: URL or filename of the rock.
+-- @param dest string or nil: if given, directory will be used as
+-- a permanent destination.
+-- @return string or (nil, string): the directory containing the contents
+-- of the unpacked rock.
+function fetch_and_unpack_rock(rock_file, dest)
+ assert(type(rock_file) == "string")
+ assert(type(dest) == "string" or not dest)
+
+ local name = fs.base_name(rock_file):match("(.*)%.[^.]*%.rock")
+
+ local rock_file, err = fetch_url_at_temp_dir(rock_file,"luarocks-rock-"..name)
+ if not rock_file then
+ return nil, "Could not fetch rock file: " .. err
+ end
+
+ rock_file = fs.absolute_name(rock_file)
+ local dir
+ if dest then
+ dir = dest
+ fs.make_dir(dir)
+ else
+ dir = fs.make_temp_dir(name)
+ end
+ if not dest then
+ util.schedule_function(fs.delete, dir)
+ end
+ fs.change_dir(dir)
+ local ok = fs.unzip(rock_file)
+ if not ok then
+ return nil, "Failed unpacking rock file: " .. rock_file
+ end
+ fs.pop_dir()
+ return dir
+end
+
+--- Back-end function that actually loads the local rockspec.
+-- Performs some validation and postprocessing of the rockspec contents.
+-- @param file string: The local filename of the rockspec file.
+-- @return table or (nil, string): A table representing the rockspec
+-- or nil followed by an error message.
+function load_local_rockspec(filename)
+ assert(type(filename) == "string")
+
+ local rockspec, err = persist.load_into_table(filename)
+ if not rockspec then
+ return nil, "Could not load rockspec file "..filename.." ("..err..")"
+ end
+
+ local ok, err = type_check.type_check_rockspec(rockspec)
+ if not ok then
+ return nil, filename..": "..err
+ end
+
+ if rockspec.rockspec_format then
+ if deps.compare_versions(rockspec.rockspec_format, type_check.rockspec_format) then
+ return nil, "Rockspec format "..rockspec.rockspec_format.." is not supported, please upgrade LuaRocks."
+ end
+ end
+
+ util.platform_overrides(rockspec.build)
+ util.platform_overrides(rockspec.dependencies)
+ util.platform_overrides(rockspec.external_dependencies)
+ util.platform_overrides(rockspec.source)
+ util.platform_overrides(rockspec.hooks)
+
+ local basename = fs.base_name(filename)
+ rockspec.name = basename:match("(.*)-[^-]*-[0-9]*")
+ if not rockspec.name then
+ return nil, "Expected filename in format 'name-version-revision.rockspec'."
+ end
+
+ local protocol, pathname = fs.split_url(rockspec.source.url)
+ if protocol == "http" or protocol == "https" or protocol == "ftp" or protocol == "file" then
+ rockspec.source.file = rockspec.source.file or fs.base_name(rockspec.source.url)
+ end
+ rockspec.source.protocol, rockspec.source.pathname = protocol, pathname
+
+ -- Temporary compatibility
+ if not rockspec.source.module then
+ rockspec.source.module = rockspec.source.cvs_module
+ rockspec.source.tag = rockspec.source.cvs_tag
+ end
+
+ local name_version = rockspec.package:lower() .. "-" .. rockspec.version
+ if basename ~= name_version .. ".rockspec" then
+ return nil, "Inconsistency between rockspec filename ("..basename..") and its contents ("..name_version..".rockspec)."
+ end
+
+ rockspec.local_filename = filename
+ local filebase = rockspec.source.file or rockspec.source.url
+ local base = fs.base_name(filebase)
+ base = base:gsub("%.[^.]*$", ""):gsub("%.tar$", "")
+ rockspec.source.dir = rockspec.source.dir
+ or rockspec.source.module
+ or ((filebase:match(".lua$") or filebase:match(".c$")) and ".")
+ or base
+ if rockspec.dependencies then
+ for i = 1, #rockspec.dependencies do
+ local parsed = deps.parse_dep(rockspec.dependencies[i])
+ if not parsed then
+ return nil, "Parse error processing dependency '"..rockspec.dependencies[i].."'"
+ end
+ rockspec.dependencies[i] = parsed
+ end
+ else
+ rockspec.dependencies = {}
+ end
+ local ok, err = path.configure_paths(rockspec)
+ if err then
+ return nil, "Error verifying paths: "..err
+ end
+
+ return rockspec
+end
+
+--- Load a local or remote rockspec into a table.
+-- This is the entry point for the LuaRocks tools.
+-- Only the LuaRocks runtime loader should use
+-- load_local_rockspec directly.
+-- @param filename string: Local or remote filename of a rockspec.
+-- @return table or (nil, string): A table representing the rockspec
+-- or nil followed by an error message.
+function load_rockspec(filename)
+ assert(type(filename) == "string")
+
+ local name = fs.base_name(filename):match("(.*)%.rockspec")
+ if not name then
+ return nil, "Filename '"..filename.."' does not look like a rockspec."
+ end
+
+ local filename, err = fetch_url_at_temp_dir(filename,"luarocks-rockspec-"..name)
+ if not filename then
+ return nil, err
+ end
+
+ return load_local_rockspec(filename)
+end
+
+--- Download sources for building a rock using the basic URL downloader.
+-- @param rockspec table: The rockspec table
+-- @param extract boolean: Whether to extract the sources from
+-- the fetched source tarball or not.
+-- @param dest_dir string or nil: If set, will extract to the given directory.
+-- @return (string, string) or (nil, string): The absolute pathname of
+-- the fetched source tarball and the temporary directory created to
+-- store it; or nil and an error message.
+function get_sources(rockspec, extract, dest_dir)
+ assert(type(rockspec) == "table")
+ assert(type(extract) == "boolean")
+ assert(type(dest_dir) == "string" or not dest_dir)
+
+ local url = rockspec.source.url
+ local name = rockspec.name.."-"..rockspec.version
+ local filename = rockspec.source.file
+ local source_file, dir, err
+ if dest_dir then
+ fs.change_dir(dest_dir)
+ source_file, err = fetch_url(url, filename)
+ fs.pop_dir()
+ dir = dest_dir
+ else
+ source_file, dir = fetch_url_at_temp_dir(url, "luarocks-source-"..name, filename)
+ end
+ if not source_file then
+ return nil, err or dir
+ end
+ if rockspec.source.md5 then
+ if not fs.check_md5(source_file, rockspec.source.md5) then
+ return nil, "MD5 check for "..filename.." has failed."
+ end
+ end
+ if extract then
+ fs.change_dir(dir)
+ fs.unpack_archive(rockspec.source.file)
+ fs.pop_dir()
+ end
+ return source_file, dir
+end
+
+--- Download sources for building a rock, calling the appropriate protocol method.
+-- @param rockspec table: The rockspec table
+-- @param extract boolean: When downloading compressed formats, whether to extract
+-- the sources from the fetched archive or not.
+-- @param dest_dir string or nil: If set, will extract to the given directory.
+-- @return (string, string) or (nil, string): The absolute pathname of
+-- the fetched source tarball and the temporary directory created to
+-- store it; or nil and an error message.
+function fetch_sources(rockspec, extract, dest_dir)
+ assert(type(rockspec) == "table")
+ assert(type(extract) == "boolean")
+ assert(type(dest_dir) == "string" or not dest_dir)
+
+ local protocol = rockspec.source.protocol
+ local proto
+ if protocol == "http" or protocol == "https" or protocol == "ftp" or protocol == "file" then
+ proto = require("luarocks.fetch")
+ else
+ ok, proto = pcall(require, "luarocks.fetch."..protocol)
+ if not ok then
+ return nil, "Unknown protocol "..protocol
+ end
+ end
+
+ return proto.get_sources(rockspec, extract, dest_dir)
+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 @@
+
+--- Fetch back-end for retrieving sources from CVS.
+module("luarocks.fetch.cvs", package.seeall)
+
+local fs = require("luarocks.fs")
+local util = require("luarocks.util")
+
+--- Download sources for building a rock, using CVS.
+-- @param rockspec table: The rockspec table
+-- @param extract boolean: Unused in this module (required for API purposes.)
+-- @param dest_dir string or nil: If set, will extract to the given directory.
+-- @return (string, string) or (nil, string): The absolute pathname of
+-- the fetched source tarball and the temporary directory created to
+-- store it; or nil and an error message.
+function get_sources(rockspec, extract, dest_dir)
+ assert(type(rockspec) == "table")
+ assert(type(dest_dir) == "string" or not dest_dir)
+
+ local name_version = rockspec.name .. "-" .. rockspec.version
+ local module = rockspec.source.module or fs.base_name(rockspec.source.url)
+ local command = {"cvs", "-d"..rockspec.source.pathname, "export", module}
+ if rockspec.source.tag then
+ table.insert(command, 4, "-r")
+ table.insert(command, 5, rockspec.source.tag)
+ end
+ local dir
+ if not dest_dir then
+ dir = fs.make_temp_dir(name_version)
+ if not dir then
+ return nil, "Failed creating temporary directory."
+ end
+ util.schedule_function(fs.delete, dir)
+ else
+ dir = dest_dir
+ end
+ fs.change_dir(dir)
+ if not fs.execute(unpack(command)) then
+ return nil, "Failed fetching files from CVS."
+ end
+ fs.pop_dir()
+ return module, dir
+end
+
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 @@
+
+--- Fetch back-end for retrieving sources from GIT.
+module("luarocks.fetch.git", package.seeall)
+
+local fs = require("luarocks.fs")
+local util = require("luarocks.util")
+
+--- Download sources for building a rock, using git.
+-- @param rockspec table: The rockspec table
+-- @param extract boolean: Unused in this module (required for API purposes.)
+-- @param dest_dir string or nil: If set, will extract to the given directory.
+-- @return (string, string) or (nil, string): The absolute pathname of
+-- the fetched source tarball and the temporary directory created to
+-- store it; or nil and an error message.
+function get_sources(rockspec, extract, dest_dir)
+ assert(type(rockspec) == "table")
+ assert(type(dest_dir) == "string" or not dest_dir)
+
+ local name_version = rockspec.name .. "-" .. rockspec.version
+ local module = fs.base_name(rockspec.source.url)
+ -- Strip off .git from base name if present
+ module = module:gsub("%.git$", "")
+ local command = {"git", "clone", rockspec.source.url, module}
+ local checkout_command
+ local tag_or_branch = rockspec.source.tag or rockspec.source.branch
+ if tag_or_branch then
+ checkout_command = {"git", "checkout", tag_or_branch}
+ end
+ local dir
+ if not dest_dir then
+ dir = fs.make_temp_dir(name_version)
+ if not dir then
+ return nil, "Failed creating temporary directory."
+ end
+ util.schedule_function(fs.delete, dir)
+ else
+ dir = dest_dir
+ end
+ fs.change_dir(dir)
+ if not fs.execute(unpack(command)) then
+ return nil, "Failed fetching files from GIT while cloning."
+ end
+ if checkout_command then
+ fs.change_dir(module)
+ if not fs.execute(unpack(checkout_command)) then
+ return nil, "Failed fetching files from GIT while getting tag/branch."
+ end
+ fs.pop_dir()
+ end
+ fs.pop_dir()
+ return module, dir
+end
+
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 @@
+
+--- Fetch back-end for retrieving sources from Surround SCM Server
+module("luarocks.fetch.sscm", package.seeall)
+
+local fs = require("luarocks.fs")
+
+--- Download sources via Surround SCM Server for building a rock.
+-- @param rockspec table: The rockspec table
+-- @param extract boolean: Unused in this module (required for API purposes.)
+-- @param dest_dir string or nil: If set, will extract to the given directory.
+-- @return (string, string) or (nil, string): The absolute pathname of
+-- the fetched source tarball and the temporary directory created to
+-- store it; or nil and an error message.
+function get_sources(rockspec, extract, dest_dir)
+ assert(type(rockspec) == "table")
+ assert(type(dest_dir) == "string" or not dest_dir)
+
+ local module = rockspec.source.module or fs.base_name(rockspec.source.url)
+ local branch, repository = string.match(rockspec.source.pathname, "^([^/]*)/(.*)")
+ if not branch or not repository then
+ return nil, "Error retrieving branch and repository from rockspec."
+ end
+ -- Search for working directory.
+ local working_dir
+ local tmp = io.popen(string.format([[sscm property "/" -d -b%s -p%s]], branch, repository))
+ for line in tmp:lines() do
+ --%c because a chr(13) comes in the end.
+ working_dir = string.match(line, "Working directory:[%s]*(.*)%c$")
+ if working_dir then break end
+ end
+ tmp:close()
+ if not working_dir then
+ return nil, "Error retrieving working directory from SSCM."
+ end
+ if not fs.execute([["sscm"]], "get", "*", "-e" , "-r", "-b"..branch, "-p"..repository, "-tmodify", "-wreplace") then
+ return nil, "Failed fetching files from SSCM."
+ end
+ -- FIXME: This function does not honor the dest_dir parameter.
+ return module, working_dir
+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 @@
+
+local rawset = rawset
+
+--- Proxy module for filesystem and platform abstractions.
+-- All code using "fs" code should require "luarocks.fs",
+-- and not the various platform-specific implementations.
+-- However, see the documentation of the implementation
+-- for the API reference.
+module("luarocks.fs", package.seeall)
+
+local cfg = require("luarocks.cfg")
+
+local fs_impl = nil
+for _, platform in ipairs(cfg.platforms) do
+ local ok, result = pcall(require, "luarocks.fs."..platform)
+ if ok then
+ fs_impl = result
+ if fs_impl then
+ break
+ end
+ end
+end
+
+local fs_unix = require("luarocks.fs.unix")
+
+local fs_mt = {
+ __index = function(t, k)
+ local impl = fs_impl and fs_impl[k]
+ if not impl then
+ impl = fs_unix[k]
+ end
+ rawset(t, k, impl)
+ return impl
+ end
+}
+
+setmetatable(luarocks.fs, fs_mt)
+
+fs_unix.init_fs_functions(luarocks.fs)
+if fs_impl then
+ fs_impl.init_fs_functions(luarocks.fs)
+end
+
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 @@
+
+local assert, type, table, io, package, math, os, ipairs =
+ assert, type, table, io, package, math, os, ipairs
+
+--- Unix implementation of filesystem and platform abstractions.
+module("luarocks.fs.unix", package.seeall)
+
+local cfg = require("luarocks.cfg")
+
+math.randomseed(os.time())
+
+dir_stack = {}
+
+local fs_absolute_name,
+ fs_base_name,
+ fs_copy,
+ fs_current_dir,
+ fs_dir_stack,
+ fs_execute,
+ fs_execute_string,
+ fs_is_dir,
+ fs_make_dir,
+ fs_make_path,
+ fs_exists,
+ fs_find,
+ fs_Q
+
+function init_fs_functions(impl)
+ fs_absolute_name = impl.absolute_name
+ fs_base_name = impl.base_name
+ fs_copy = impl.copy
+ fs_current_dir = impl.current_dir
+ fs_dir_stack = impl.dir_stack
+ fs_execute = impl.execute
+ fs_execute_string = impl.execute_string
+ fs_is_dir = impl.is_dir
+ fs_make_dir = impl.make_dir
+ fs_make_path = impl.make_path
+ fs_exists = impl.exists
+ fs_find = impl.find
+ fs_Q = impl.Q
+end
+
+dir_separator = "/"
+
+--- Quote argument for shell processing.
+-- Adds single quotes and escapes.
+-- @param arg string: Unquoted argument.
+-- @return string: Quoted argument.
+function Q(arg)
+ assert(type(arg) == "string")
+
+ return "'" .. arg:gsub("\\", "\\\\"):gsub("'", "'\\''") .. "'"
+end
+
+--- Obtain current directory.
+-- Uses the module's internal dir stack.
+-- @return string: the absolute pathname of the current directory.
+function current_dir()
+ local current = os.getenv("PWD")
+ if not current then
+ local pipe = io.popen("pwd")
+ current = pipe:read("*l")
+ pipe:close()
+ end
+ for _, dir in ipairs(fs_dir_stack) do
+ current = fs_absolute_name(dir, current)
+ end
+ return current
+end
+
+--- Run the given command.
+-- The command is executed in the current directory in the dir stack.
+-- @param cmd string: No quoting/escaping is applied to the command.
+-- @return boolean: true if command succeeds (status code 0), false
+-- otherwise.
+function execute_string(cmd)
+ if os.execute("cd " .. fs_Q(fs_current_dir()) .. " && " .. cmd) == 0 then
+ return true
+ else
+ return false
+ end
+end
+
+--- Run the given command, quoting its arguments.
+-- The command is executed in the current directory in the dir stack.
+-- @param command string: The command to be executed. No quoting/escaping
+-- is applied.
+-- @param ... Strings containing additional arguments, which are quoted.
+-- @return boolean: true if command succeeds (status code 0), false
+-- otherwise.
+function execute(command, ...)
+ assert(type(command) == "string")
+
+ for _, arg in ipairs({...}) do
+ assert(type(arg) == "string")
+ command = command .. " " .. fs_Q(arg)
+ end
+ return fs_execute_string(command)
+end
+
+--- Change the current directory.
+-- Uses the module's internal dir stack. This does not have exact
+-- semantics of chdir, as it does not handle errors the same way,
+-- but works well for our purposes for now.
+-- @param dir string: The directory to switch to.
+function change_dir(dir)
+ assert(type(dir) == "string")
+ table.insert(fs_dir_stack, dir)
+end
+
+--- Change directory to root.
+-- Allows leaving a directory (e.g. for deleting it) in
+-- a crossplatform way.
+function change_dir_to_root()
+ table.insert(fs_dir_stack, "/")
+end
+
+--- Change working directory to the previous in the dir stack.
+function pop_dir()
+ table.remove(fs_dir_stack)
+end
+
+--- Create a directory if it does not already exist.
+-- If any of the higher levels in the path name does not exist
+-- too, they are created as well.
+-- @param dir string: pathname of directory to create.
+-- @return boolean: true on success, false on failure.
+function make_dir(dir)
+ assert(dir)
+ return fs_execute("mkdir -p", dir)
+end
+
+--- Remove a directory if it is empty.
+-- Does not return errors (for example, if directory is not empty or
+-- if already does not exist)
+-- @param dir string: pathname of directory to remove.
+function remove_dir_if_empty(dir)
+ assert(dir)
+ fs_execute_string("rmdir "..fs_Q(dir).." 1> /dev/null 2> /dev/null")
+end
+
+--- Copy a file.
+-- @param src string: Pathname of source
+-- @param dest string: Pathname of destination
+-- @return boolean or (boolean, string): true on success, false on failure,
+-- plus an error message.
+function copy(src, dest)
+ assert(src and dest)
+ if fs_execute("cp", src, dest) then
+ return true
+ else
+ return false, "Failed copying "..src.." to "..dest
+ end
+end
+
+--- Recursively copy the contents of a directory.
+-- @param src string: Pathname of source
+-- @param dest string: Pathname of destination
+-- @return boolean or (boolean, string): true on success, false on failure,
+-- plus an error message.
+function copy_contents(src, dest)
+ assert(src and dest)
+ if fs_execute_string("cp -a "..fs_Q(src).."/* "..fs_Q(dest).." 1> /dev/null 2>/dev/null") then
+ return true
+ else
+ return false, "Failed copying "..src.." to "..dest
+ end
+end
+
+--- Delete a file or a directory and all its contents.
+-- For safety, this only accepts absolute paths.
+-- @param arg string: Pathname of source
+-- @return boolean: true on success, false on failure.
+function delete(arg)
+ assert(arg)
+ assert(arg:sub(1,1) == "/")
+ return fs_execute_string("rm -rf " .. fs_Q(arg) .. " 1> /dev/null 2>/dev/null")
+end
+
+--- List the contents of a directory.
+-- @param at string or nil: directory to list (will be the current
+-- directory if none is given).
+-- @return table: an array of strings with the filenames representing
+-- the contents of a directory.
+function dir(at)
+ assert(type(at) == "string" or not at)
+ if not at then
+ at = fs_current_dir()
+ end
+ if not fs_is_dir(at) then
+ return {}
+ end
+ local result = {}
+ local pipe = io.popen("cd "..fs_Q(at).." && ls")
+ for file in pipe:lines() do
+ table.insert(result, file)
+ end
+ pipe:close()
+ return result
+end
+
+--- Recursively scan the contents of a directory.
+-- @param at string or nil: directory to scan (will be the current
+-- directory if none is given).
+-- @return table: an array of strings with the filenames representing
+-- the contents of a directory.
+function find(at)
+ assert(type(at) == "string" or not at)
+ if not at then
+ at = fs_current_dir()
+ end
+ if not fs_is_dir(at) then
+ return {}
+ end
+ local result = {}
+ local pipe = io.popen("cd "..fs_Q(at).." && find * 2>/dev/null")
+ for file in pipe:lines() do
+ table.insert(result, file)
+ end
+ pipe:close()
+ return result
+end
+
+--- Compress files in a .zip archive.
+-- @param zipfile string: pathname of .zip archive to be created.
+-- @param ... Filenames to be stored in the archive are given as
+-- additional arguments.
+-- @return boolean: true on success, false on failure.
+function zip(zipfile, ...)
+ return fs_execute("zip -r", zipfile, ...)
+end
+
+--- Uncompress files from a .zip archive.
+-- @param zipfile string: pathname of .zip archive to be extracted.
+-- @return boolean: true on success, false on failure.
+function unzip(zipfile)
+ assert(zipfile)
+ return fs_execute("unzip", zipfile)
+end
+
+--- Test for existance of a file.
+-- @param file string: filename to test
+-- @return boolean: true if file exists, false otherwise.
+function exists(file)
+ assert(file)
+ return fs_execute("test -r", file)
+end
+
+--- Test is file/dir is writable.
+-- @param file string: filename to test
+-- @return boolean: true if file exists, false otherwise.
+function is_writable(file)
+ assert(file)
+ return fs_execute("test -w", file)
+end
+
+--- Test is pathname is a directory.
+-- @param file string: pathname to test
+-- @return boolean: true if it is a directory, false otherwise.
+function is_dir(file)
+ assert(file)
+ return fs_execute("test -d", file)
+end
+
+--- Download a remote file.
+-- @param url string: URL to be fetched.
+-- @param filename string or nil: this function attempts to detect the
+-- resulting local filename of the remote file as the basename of the URL;
+-- if that is not correct (due to a redirection, for example), the local
+-- filename can be given explicitly as this second argument.
+-- @return boolean: true on success, false on failure.
+function download(url, filename)
+ assert(type(url) == "string")
+ assert(type(filename) == "string" or not filename)
+
+ if cfg.downloader == "wget" then
+ if filename then
+ return fs_execute("wget --quiet --continue --output-document ", filename, url)
+ else
+ return fs_execute("wget --quiet --continue ", url)
+ end
+ elseif cfg.downloader == "curl" then
+ filename = filename or fs_base_name(url)
+ return fs_execute_string("curl "..fs_Q(url).." 2> /dev/null 1> "..fs_Q(filename))
+ end
+end
+
+--- Strip the path off a path+filename.
+-- @param pathname string: A path+name, such as "/a/b/c".
+-- @return string: The filename without its path, such as "c".
+function base_name(pathname)
+ assert(type(pathname) == "string")
+
+ local base = pathname:match(".*/([^/]*)")
+ return base or pathname
+end
+
+--- Strip the name off a path+filename.
+-- @param pathname string: A path+name, such as "/a/b/c".
+-- @return string: The filename without its path, such as "/a/b/".
+-- For entries such as "/a/b/", "/a/" is returned. If there are
+-- no directory separators in input, "" is returned.
+function dir_name(pathname)
+ assert(type(pathname) == "string")
+
+ local dir = pathname:gsub("/*$", ""):match("(.*/)[^/]*")
+ return dir or ""
+end
+
+--- Create a temporary directory.
+-- @param name string: name pattern to use for avoiding conflicts
+-- when creating temporary directory.
+-- @return string or nil: name of temporary directory or nil on failure.
+function make_temp_dir(name)
+ assert(type(name) == "string")
+
+ local temp_dir = (os.getenv("TMP") or "/tmp") .. "/" .. name .. "-" .. tostring(math.floor(math.random() * 10000))
+ if fs_make_dir(temp_dir) then
+ return temp_dir
+ else
+ return nil
+ end
+end
+
+--- Apply a patch.
+-- @param patchname string: The filename of the patch.
+function patch(patchname)
+ return fs_execute("patch -p1 -f -i ", patchname)
+end
+
+--- Unpack an archive.
+-- Extract the contents of an archive, detecting its format by
+-- filename extension.
+-- @param archive string: Filename of archive.
+-- @return boolean or (boolean, string): true on success, false and an error message on failure.
+function unpack_archive(archive)
+ assert(type(archive) == "string")
+
+ local ok
+ if archive:match("%.tar%.gz$") or archive:match("%.tgz$") then
+ -- ok = fs_execute("tar zxvpf ", archive)
+ ok = fs_execute_string("gunzip -c "..archive.."|tar -xf -")
+ elseif archive:match("%.tar%.bz2$") then
+ -- ok = fs_execute("tar jxvpf ", archive)
+ ok = fs_execute_string("bunzip2 -c "..archive.."|tar -xf -")
+ elseif archive:match("%.zip$") then
+ ok = fs_execute("unzip ", archive)
+ elseif archive:match("%.lua$") or archive:match("%.c$") then
+ -- Ignore .lua and .c files; they don't need to be extracted.
+ return true
+ else
+ local ext = archive:match(".*(%..*)")
+ return false, "Unrecognized filename extension "..(ext or "")
+ end
+ if not ok then
+ return false, "Failed extracting "..archive
+ end
+ return true
+end
+
+--- Check the MD5 checksum for a file.
+-- @param file string: The file to be checked.
+-- @param md5sum string: The string with the expected MD5 checksum.
+-- @return boolean: true if the MD5 checksum for 'file' equals 'md5sum', false if not
+-- or if it could not perform the check for any reason.
+function check_md5(file, md5sum)
+ file = fs_absolute_name(file)
+ local computed
+ if cfg.md5checker == "md5sum" then
+ local pipe = io.popen("md5sum "..file)
+ computed = pipe:read("*l"):gsub("[^%x]+", "")
+ pipe:close()
+ if computed then
+ computed = computed:sub(1,32)
+ end
+ elseif cfg.md5checker == "openssl" then
+ local pipe = io.popen("openssl md5 "..file)
+ computed = pipe:read("*l")
+ pipe:close()
+ if computed then
+ computed = computed:sub(-32)
+ end
+ elseif cfg.md5checker == "md5" then
+ local pipe = io.popen("md5 "..file)
+ computed = pipe:read("*l")
+ pipe:close()
+ if computed then
+ computed = computed:sub(-32)
+ end
+ end
+ if not computed then
+ return false
+ end
+ if computed:match("^"..md5sum) then
+ return true
+ else
+ return false
+ end
+end
+
+--- Return an absolute pathname from a potentially relative one.
+-- @param pathname string: pathname to convert.
+-- @param relative_to string or nil: path to prepend when making
+-- pathname absolute, or the current dir in the dir stack if
+-- not given.
+-- @return string: The pathname converted to absolute.
+function absolute_name(pathname, relative_to)
+ assert(type(pathname) == "string")
+ assert(type(relative_to) == "string" or not relative_to)
+
+ relative_to = relative_to or fs_current_dir()
+ if pathname:sub(1,1) == "/" then
+ return pathname
+ else
+ return relative_to .. "/" .. pathname
+ end
+end
+
+--- Describe a path in a cross-platform way.
+-- Use this function to avoid platform-specific directory
+-- separators in other modules. If the first item contains a
+-- protocol descriptor (e.g. "http:"), paths are always constituted
+-- with forward slashes.
+-- @param ... strings representing directories
+-- @return string: a string with a platform-specific representation
+-- of the path.
+function make_path(...)
+ local items = {...}
+ local i = 1
+ while items[i] do
+ if items[i] == "" then
+ table.remove(items, i)
+ else
+ i = i + 1
+ end
+ end
+ return table.concat(items, "/")
+end
+
+--- Split protocol and path from an URL or local pathname.
+-- URLs should be in the "protocol://path" format.
+-- For local pathnames, "file" is returned as the protocol.
+-- @param url string: an URL or a local pathname.
+-- @return string, string: the protocol, and the absolute pathname without the protocol.
+function split_url(url)
+ assert(type(url) == "string")
+
+ local protocol, pathname = url:match("^([^:]*)://(.*)")
+ if not protocol then
+ protocol = "file"
+ pathname = url
+ end
+ if protocol == "file" then
+ pathname = fs_absolute_name(pathname)
+ end
+ return protocol, pathname
+end
+
+--- Create a wrapper to make a script executable from the command-line.
+-- @param file string: Pathname of script to be made executable.
+-- @param dest string: Directory where to put the wrapper.
+-- @return boolean or (nil, string): True if succeeded, or nil and
+-- an error message.
+function wrap_script(file, dest)
+ assert(type(file) == "string")
+ assert(type(dest) == "string")
+
+ local base = fs_base_name(file)
+ local wrapname = dest.."/"..base
+ local wrapper = io.open(wrapname, "w")
+ if not wrapper then
+ return nil, "Could not open "..wrapname.." for writing."
+ end
+ wrapper:write("#!/bin/sh\n\n")
+ wrapper:write('LUA_PATH="'..package.path..';$LUA_PATH"\n')
+ wrapper:write('LUA_CPATH="'..package.cpath..';$LUA_CPATH"\n')
+ wrapper:write('export LUA_PATH LUA_CPATH\n')
+ wrapper:write('exec "'..fs_make_path(cfg.variables["LUA_BINDIR"], cfg.lua_interpreter)..'" -lluarocks.require "'..file..'" "$@"\n')
+ wrapper:close()
+ if fs_execute("chmod +x",wrapname) then
+ return true
+ else
+ return nil, "Could not make "..wrapname.." executable."
+ end
+end
+
+--- Check if a file (typically inside path.bin_dir) is an actual binary
+-- or a Lua wrapper.
+-- @param filename string: the file name with full path.
+-- @return boolean: returns true if file is an actual binary
+-- (or if it couldn't check) or false if it is a Lua wrapper.
+local function is_actual_binary(filename)
+ local file = io.open(filename)
+ if file then
+ local found = false
+ if file:read():match("#!/bin/sh") then
+ local line = file:read()
+ line = file:read()
+ if not(line and line:match("LUA_PATH")) then
+ found = true
+ end
+ end
+ file:close()
+ if found then
+ return false
+ else
+ return true
+ end
+ else
+ return true
+ end
+ return false
+end
+
+function copy_binary(filename, dest)
+ return fs_copy(filename, dest)
+end
+
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 @@
+--- Windows implementation of filesystem and platform abstractions.
+-- Download http://unxutils.sourceforge.net/ for Windows GNU utilities
+-- used by this module.
+module("luarocks.fs.win32", package.seeall)
+
+local cfg = require("luarocks.cfg")
+
+local fs_base_name,
+ fs_copy,
+ fs_current_dir,
+ fs_execute,
+ fs_execute_string,
+ fs_is_dir,
+ fs_make_path,
+ fs_Q
+
+function init_fs_functions(impl)
+ fs_base_name = impl.base_name
+ fs_copy = impl.copy
+ fs_current_dir = impl.current_dir
+ fs_execute = impl.execute
+ fs_execute_string = impl.execute_string
+ fs_is_dir = impl.is_dir
+ fs_make_path = impl.make_path
+ fs_Q = impl.Q
+end
+
+--- Quote argument for shell processing. Fixes paths on Windows.
+-- Adds single quotes and escapes.
+-- @param arg string: Unquoted argument.
+-- @return string: Quoted argument.
+function Q(arg)
+ assert(type(arg) == "string")
+ -- Quote DIR for Windows
+ if arg:match("^[\.a-zA-Z]?:?[\\/]") then
+ return '"' .. arg:gsub("/", "\\"):gsub('"', '\\"') .. '"'
+ end
+ -- URLs and anything else
+ return '"' .. arg:gsub('"', '\\"') .. '"'
+end
+
+local function command_at(dir, cmd)
+ local drive = dir:match("^([A-Za-z]:)")
+ cmd = "cd " .. fs_Q(dir) .. " & " .. cmd
+ if drive then
+ cmd = drive .. " & " .. cmd
+ end
+ return cmd
+end
+
+--- Run the given command.
+-- The command is executed in the current directory in the dir stack.
+-- @param cmd string: No quoting/escaping is applied to the command.
+-- @return boolean: true if command succeeds (status code 0), false
+-- otherwise.
+function execute_string(cmd)
+ if os.execute(command_at(fs_current_dir(), cmd)) == 0 then
+ return true
+ else
+ return false
+ end
+end
+
+--- Test for existance of a file.
+-- @param file string: filename to test
+-- @return boolean: true if file exists, false otherwise.
+function exists(file)
+ assert(file)
+ return fs_execute("if not exist " .. fs_Q(file) ..
+ " invalidcommandname 2>NUL 1>NUL")
+end
+
+--- Test is pathname is a directory.
+-- @param file string: pathname to test
+-- @return boolean: true if it is a directory, false otherwise.
+function is_dir(file)
+ assert(file)
+ return fs_execute("chdir /D " .. fs_Q(file) .. " 2>NUL 1>NUL")
+end
+
+--- Test is file/dir is writable.
+-- @param file string: filename to test
+-- @return boolean: true if file exists, false otherwise.
+function is_writable(file)
+ assert(file)
+ local result
+ if is_dir(file) then
+ local file2 = file .. '/.tmpluarockstestwritable'
+ local fh = io.open(file2, 'w')
+ result = fh ~= nil
+ if fh then fh:close() end
+ os.remove(file2)
+ else
+ local fh = io.open(file, 'r+')
+ result = fh ~= nil
+ if fh then fh:close() end
+ end
+ return result
+end
+
+
+--- Create a directory if it does not already exist.
+-- If any of the higher levels in the path name does not exist
+-- too, they are created as well.
+-- @param dir string: pathname of directory to create.
+-- @return boolean: true on success, false on failure.
+function make_dir(dir)
+ assert(dir)
+ fs_execute("mkdir "..fs_Q(dir).." 1> NUL 2> NUL")
+ return 1
+end
+
+--- Remove a directory if it is empty.
+-- Does not return errors (for example, if directory is not empty or
+-- if already does not exist)
+-- @param dir string: pathname of directory to remove.
+function remove_dir_if_empty(dir)
+ assert(dir)
+ fs_execute_string("rmdir "..fs_Q(dir).." 1> NUL 2> NUL")
+end
+
+--- Copy a file.
+-- @param src string: Pathname of source
+-- @param dest string: Pathname of destination
+-- @return boolean or (boolean, string): true on success, false on failure,
+-- plus an error message.
+function copy(src, dest)
+ assert(src and dest)
+ if dest:match("[/\\]$") then dest = dest:sub(1, -2) end
+ if fs_execute("cp", src, dest) then
+ return true
+ else
+ return false, "Failed copying "..src.." to "..dest
+ end
+end
+
+--- Recursively copy the contents of a directory.
+-- @param src string: Pathname of source
+-- @param dest string: Pathname of destination
+-- @return boolean or (boolean, string): true on success, false on failure,
+-- plus an error message.
+function copy_contents(src, dest)
+ assert(src and dest)
+ if fs_execute_string("cp -a "..src.."\\*.* "..fs_Q(dest).." 1> NUL 2> NUL") then
+ return true
+ else
+ return false, "Failed copying "..src.." to "..dest
+ end
+end
+
+--- Delete a file or a directory and all its contents.
+-- For safety, this only accepts absolute paths.
+-- @param arg string: Pathname of source
+-- @return boolean: true on success, false on failure.
+function delete(arg)
+ assert(arg)
+ assert(arg:match("^[\a-zA-Z]?:?[\\/]"))
+ fs_execute("chmod a+rw -R ", arg)
+ return fs_execute_string("rm -rf " .. fs_Q(arg) .. " 1> NUL 2> NUL")
+end
+
+--- List the contents of a directory.
+-- @param at string or nil: directory to list (will be the current
+-- directory if none is given).
+-- @return table: an array of strings with the filenames representing
+-- the contents of a directory.
+function dir(at)
+ assert(type(at) == "string" or not at)
+ if not at then
+ at = fs_current_dir()
+ end
+ if not fs_is_dir(at) then
+ return {}
+ end
+ local result = {}
+ local pipe = io.popen(command_at(at, "ls"))
+ for file in pipe:lines() do
+ table.insert(result, file)
+ end
+ pipe:close()
+
+ return result
+end
+
+--- Recursively scan the contents of a directory.
+-- @param at string or nil: directory to scan (will be the current
+-- directory if none is given).
+-- @return table: an array of strings with the filenames representing
+-- the contents of a directory. Paths are returned with forward slashes.
+function find(at)
+ assert(type(at) == "string" or not at)
+ if not at then
+ at = fs_current_dir()
+ end
+ if not fs_is_dir(at) then
+ return {}
+ end
+ local result = {}
+ local pipe = io.popen(command_at(at, "find 2> NUL"))
+ for file in pipe:lines() do
+ -- Windows find is a bit different
+ if file:sub(1,2)==".\\" then file=file:sub(3) end
+ if file ~= "." then
+ table.insert(result, (file:gsub("\\", "/")))
+ end
+ end
+ return result
+end
+
+
+--- Download a remote file.
+-- @param url string: URL to be fetched.
+-- @param filename string or nil: this function attempts to detect the
+-- resulting local filename of the remote file as the basename of the URL;
+-- if that is not correct (due to a redirection, for example), the local
+-- filename can be given explicitly as this second argument.
+-- @return boolean: true on success, false on failure.
+function download(url, filename)
+ assert(type(url) == "string")
+ assert(type(filename) == "string" or not filename)
+
+ if filename then
+ return fs_execute("wget --quiet --continue --output-document ", filename, url)
+ else
+ return fs_execute("wget --quiet --continue ", url)
+ end
+end
+
+--- Strip the path off a path+filename.
+-- @param pathname string: A path+name, such as "/a/b/c".
+-- @return string: The filename without its path, such as "c".
+function base_name(pathname)
+ assert(type(pathname) == "string")
+
+ local base = pathname:match(".*[/\\]([^/\\]*)")
+ return base or pathname
+end
+
+--- Strip the last extension of a filename.
+-- Example: "foo.tar.gz" becomes "foo.tar".
+-- If filename has no dots, returns it unchanged.
+-- @param filename string: The file name to strip.
+-- @return string: The stripped name.
+local function strip_extension(filename)
+ assert(type(filename) == "string")
+
+ return (filename:gsub("%.[^.]+$", "")) or filename
+end
+
+--- Uncompress gzip file.
+-- @param archive string: Filename of archive.
+-- @return boolean : success status
+local function gunzip(archive)
+ local cmd = fs_execute("gunzip -h 1>NUL 2>NUL") and 'gunzip' or
+ fs_execute("gzip -h 1>NUL 2>NUL") and 'gzip -d'
+ local ok = fs_execute(cmd, archive)
+ return ok
+end
+
+--- Unpack an archive.
+-- Extract the contents of an archive, detecting its format by
+-- filename extension.
+-- @param archive string: Filename of archive.
+-- @return boolean or (boolean, string): true on success, false and an error message on failure.
+function unpack_archive(archive)
+ assert(type(archive) == "string")
+
+ local ok
+ if archive:match("%.tar%.gz$") then
+ ok = gunzip(archive)
+ if ok then
+ ok = fs_execute("tar -xf ", strip_extension(archive))
+ end
+ elseif archive:match("%.tgz$") then
+ ok = gunzip(archive)
+ if ok then
+ ok = fs_execute("tar -xf ", strip_extension(archive)..".tar")
+ end
+ elseif archive:match("%.tar%.bz2$") then
+ ok = fs_execute("bunzip2 ", archive)
+ if ok then
+ ok = fs_execute("tar -xf ", strip_extension(archive))
+ end
+ elseif archive:match("%.zip$") then
+ ok = fs_execute("unzip ", archive)
+ elseif archive:match("%.lua$") or archive:match("%.c$") then
+ -- Ignore .lua and .c files; they don't need to be extracted.
+ return true
+ else
+ local ext = archive:match(".*(%..*)")
+ return false, "Unrecognized filename extension "..(ext or "")
+ end
+ if not ok then
+ return false, "Failed extracting "..archive
+ end
+ return true
+end
+
+--- Return an absolute pathname from a potentially relative one.
+-- @param pathname string: pathname to convert.
+-- @param relative_to string or nil: path to prepend when making
+-- pathname absolute, or the current dir in the dir stack if
+-- not given.
+-- @return string: The pathname converted to absolute.
+function absolute_name(pathname, relative_to)
+ assert(type(pathname) == "string")
+ assert(type(relative_to) == "string" or not relative_to)
+
+ relative_to = relative_to or fs_current_dir()
+ if pathname:match("^[\.a-zA-Z]?:?[\\/]") then
+ return pathname
+ else
+ return relative_to .. "/" .. pathname
+ end
+end
+
+--- Create a wrapper to make a script executable from the command-line.
+-- @param file string: Pathname of script to be made executable.
+-- @param dest string: Directory where to put the wrapper.
+-- @return boolean or (nil, string): True if succeeded, or nil and
+-- an error message.
+function wrap_script(file, dest)
+ assert(type(file) == "string")
+ assert(type(dest) == "string")
+
+ local base = fs_base_name(file)
+ local wrapname = dest.."/"..base..".bat"
+ local wrapper = io.open(wrapname, "w")
+ if not wrapper then
+ return nil, "Could not open "..wrapname.." for writing."
+ end
+ wrapper:write("@echo off\n")
+ wrapper:write("setlocal\n")
+ wrapper:write('set LUA_PATH='..package.path..";%LUA_PATH%\n")
+ wrapper:write('set LUA_CPATH='..package.cpath..";%LUA_CPATH%\n")
+ wrapper:write('"'..fs_make_path(cfg.variables["LUA_BINDIR"], cfg.lua_interpreter)..'" -lluarocks.require "'..file..'" %*\n')
+ wrapper:write("endlocal\n")
+ wrapper:close()
+ return true
+end
+
+function is_actual_binary(name)
+ name = name:lower()
+ if name:match("%.bat$") or name:match("%.exe$") then
+ return true
+ end
+ return false
+end
+
+function copy_binary(filename, dest)
+ local ok, err = fs_copy(filename, dest)
+ if not ok then
+ return nil, err
+ end
+ local exe_pattern = "%.[Ee][Xx][Ee]$"
+ local base = fs_base_name(filename)
+ if base:match(exe_pattern) then
+ base = base:gsub(exe_pattern, ".lua")
+ local helpname = dest.."/"..base
+ local helper = io.open(helpname, "w")
+ if not helper then
+ return nil, "Could not open "..helpname.." for writing."
+ end
+ helper:write('package.path=\"'..package.path:gsub("\\","\\\\")..';\"..package.path\n')
+ helper:write('package.cpath=\"'..package.path:gsub("\\","\\\\")..';\"..package.cpath\n')
+ helper:close()
+ end
+ return true
+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 @@
+
+--- Module implementing the LuaRocks "help" command.
+-- This is a generic help display module, which
+-- uses a global table called "commands" to find commands
+-- to show help for; each command should be represented by a
+-- table containing "help" and "help_summary" fields.
+module("luarocks.help", package.seeall)
+
+local util = require("luarocks.util")
+
+help_summary = "Help on commands."
+
+help_arguments = "[]"
+help = [[
+ is the command to show help for.
+]]
+
+--- Driver function for the "help" command.
+-- @param command string or nil: command to show help for; if not
+-- given, help summaries for all commands are shown.
+-- @return boolean or (nil, string): true if there were no errors
+-- or nil and an error message if an invalid command was requested.
+function run(...)
+ local flags, command = util.parse_flags(...)
+
+ if not command then
+ print([[
+LuaRocks ]]..program_version..[[, a module deployment system for Lua
+
+]]..program_name..[[ - ]]..program_description..[[
+
+usage: ]]..program_name..[[ [--from= | --only-from=] [--to=] [VAR=VALUE]... []
+
+Variables from the "variables" table of the configuration file
+can be overriden with VAR=VALUE assignments.
+
+--from= Fetch rocks/rockspecs from this server
+ (takes priority over config file)
+--only-from= Fetch rocks/rockspecs from this server only
+ (overrides any entries in the config file)
+--to= Which tree to operate on.
+
+Supported commands:
+]])
+ local names = {}
+ for name, command in pairs(commands) do
+ table.insert(names, name)
+ end
+ table.sort(names)
+ for _, name in ipairs(names) do
+ local command = commands[name]
+ print(name, command.help_summary)
+ end
+ else
+ command = command:gsub("-", "_")
+ if commands[command] then
+ local arguments = commands[command].help_arguments or ""
+ print()
+ print(program_name.." "..command.." "..arguments)
+ print()
+ print(command.." - "..commands[command].help_summary)
+ print()
+ print(commands[command].help)
+ else
+ return nil, "Unknown command '"..command.."'"
+ end
+ end
+ return true
+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 @@
+
+--- Module implementing the LuaRocks "install" command.
+-- Installs binary rocks.
+module("luarocks.install", package.seeall)
+
+local path = require("luarocks.path")
+local rep = require("luarocks.rep")
+local fetch = require("luarocks.fetch")
+local util = require("luarocks.util")
+local fs = require("luarocks.fs")
+local deps = require("luarocks.deps")
+local manif = require("luarocks.manif")
+local cfg = require("luarocks.cfg")
+
+help_summary = "Install a rock."
+
+help_arguments = "{| []}"
+
+help = [[
+Argument may be the name of a rock to be fetched from a repository
+or a filename of a locally available rock.
+]]
+
+--- Install a binary rock.
+-- @param rock_file string: local or remote filename of a rock.
+-- @return boolean or (nil, string): True if succeeded or
+-- nil and an error message.
+function install_binary_rock(rock_file)
+ local name, version, arch = path.parse_rock_name(rock_file)
+ if not name then
+ return nil, "Filename "..rock_file.." does not match format 'name-version-revision.arch.rock'."
+ end
+ if arch ~= "all" and arch ~= cfg.arch then
+ return nil, "Incompatible architecture "..arch
+ end
+ if rep.is_installed(name, version) then
+ rep.delete_version(name, version)
+ end
+ local rollback = util.schedule_function(function()
+ fs.delete(path.install_dir(name, version))
+ fs.remove_dir_if_empty(path.versions_dir(name))
+ end)
+ local ok, err = fetch.fetch_and_unpack_rock(rock_file, path.install_dir(name, version))
+ if not ok then return nil, err end
+ ok, err = rep.install_bins(name, version)
+
+ local rockspec, err = fetch.load_rockspec(path.rockspec_file(name, version))
+ if err then
+ return nil, "Failed loading rockspec for installed package: "..err
+ end
+
+ ok, err = deps.check_external_deps(rockspec, "install")
+ if err then
+ return nil, err
+ end
+
+ ok, err = deps.fulfill_dependencies(rockspec)
+ if err then
+ return nil, err
+ end
+
+ ok, err = rep.run_hook(rockspec, "post_install")
+ if err then
+ return nil, err
+ end
+
+ ok, err = manif.update_manifest(name, version)
+ if err then
+ return nil, err
+ end
+ util.remove_scheduled_function(rollback)
+ return true
+end
+
+--- Driver function for the "install" command.
+-- @param name string: name of a binary rock. If an URL or pathname
+-- to a binary rock is given, fetches and installs it. If a rockspec or a
+-- source rock is given, forwards the request to the "build" command.
+-- If a package name is given, forwards the request to "search" and,
+-- if returned a result, installs the matching rock.
+-- @param version string: When passing a package name, a version number
+-- may also be given.
+-- @return boolean or (nil, string): True if installation was
+-- successful, nil and an error message otherwise.
+function run(...)
+ local flags, name, version = util.parse_flags(...)
+ if type(name) ~= "string" then
+ return nil, "Argument missing, see help."
+ end
+
+ if name:match("%.rockspec$") or name:match("%.src%.rock$") then
+ local build = require("luarocks.build")
+ return build.run(name)
+ elseif name:match("%.rock$") then
+ return install_binary_rock(name)
+ else
+ local search = require("luarocks.search")
+ local results, err = search.find_suitable_rock(search.make_query(name, version))
+ if err then
+ return nil, err
+ elseif type(results) == "string" then
+ local url = results
+ print("Installing "..url.."...")
+ return run(url)
+ else
+ print()
+ print("Could not determine which rock to install.")
+ print()
+ print("Search results:")
+ print("---------------")
+ search.print_results(results)
+ return nil, (next(results) and "Please narrow your query." or "No results found.")
+ end
+ end
+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 @@
+
+--- Module implementing the LuaRocks "list" command.
+-- Lists currently installed rocks.
+module("luarocks.list", package.seeall)
+
+local search = require("luarocks.search")
+local cfg = require("luarocks.cfg")
+local util = require("luarocks.util")
+local fs = require("luarocks.fs")
+
+help_summary = "Lists currently installed rocks."
+
+help = [[
+ is a substring of a rock name to filter by.
+]]
+
+--- Driver function for "list" command.
+-- @param filter string or nil: A substring of a rock name to filter by.
+-- @param version string or nil: a version may also be passed.
+-- @return boolean: True if succeeded, nil on errors.
+function run(...)
+ local flags, filter, version = util.parse_flags(...)
+ local results = {}
+ local query = search.make_query(filter or "", version)
+ query.exact_name = false
+ for _, tree in ipairs(cfg.rocks_trees) do
+ search.manifest_search(results, fs.make_path(tree, "rocks"), query)
+ end
+ print()
+ print("Installed rocks:")
+ print("----------------")
+ print()
+ search.print_results(results, false)
+ return true
+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 @@
+
+--- Module implementing the LuaRocks "make" command.
+-- Builds sources in the current directory, but unlike "build",
+-- it does not fetch sources, etc., assuming everything is
+-- available in the current directory.
+module("luarocks.make", package.seeall)
+
+local build = require("luarocks.build")
+local fs = require("luarocks.fs")
+local util = require("luarocks.util")
+
+help_summary = "Compile package in current directory using a rockspec."
+help_arguments = "[]"
+help = [[
+Builds sources in the current directory, but unlike "build",
+it does not fetch sources, etc., assuming everything is
+available in the current directory. If no argument is given,
+look for a rockspec in the current directory. If more than one
+is found, you must specify which to use, through the command-line.
+
+This command is useful as a tool for debugging rockspecs.
+To install rocks, you'll normally want to use the "install" and
+"build" commands. See the help on those for details.
+]]
+
+--- Driver function for "make" command.
+-- @param name string: A local rockspec.
+-- @return boolean or (nil, string): True if build was successful; nil and an
+-- error message otherwise.
+function run(...)
+ local flags, rockspec = util.parse_flags(...)
+ assert(type(rockspec) == "string" or not rockspec)
+
+ if not rockspec then
+ local files = fs.dir(fs.current_dir())
+ for _, file in pairs(files) do
+ if file:match(".rockspec$") then
+ if rockspec then
+ return nil, "Please specify which rockspec file to use."
+ else
+ rockspec = file
+ end
+ end
+ end
+ if not rockspec then
+ return nil, "Argument missing: please specify a rockspec to use on current directory."
+ end
+ end
+ if not rockspec:match("%.rockspec$") then
+ return nil, "Invalid argument: 'make' takes a rockspec as a parameter. See help."
+ end
+
+ return build.build_rockspec(rockspec, false, true)
+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 @@
+
+--- Module implementing the luarocks-admin "make_manifest" command.
+-- Compile a manifest file for a repository.
+module("luarocks.make_manifest", package.seeall)
+
+local manif = require("luarocks.manif")
+local cfg = require("luarocks.cfg")
+
+help_summary = "Compile a manifest file for a repository."
+
+help = [[
+, if given, is a local repository pathname.
+]]
+
+--- Driver function for "make_manifest" command.
+-- @param repo string or nil: Pathname of a local repository. If not given,
+-- the default local repository configured as cfg.rocks_dir is used.
+-- @return boolean or (nil, string): True if manifest was generated,
+-- or nil and an error message.
+function run(repo)
+ assert(type(repo) == "string" or not repo)
+ repo = repo or cfg.rocks_dir
+
+ print("Making manifest for "..repo)
+
+ ok = manif.make_manifest(repo)
+ if ok then
+ print("Generating index.html for "..repo)
+ manif.make_index(repo)
+ end
+ return ok
+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 @@
+
+--- Functions for querying and manipulating manifest files.
+module("luarocks.manif", package.seeall)
+
+local util = require("luarocks.util")
+local fs = require("luarocks.fs")
+local search = require("luarocks.search")
+local rep = require("luarocks.rep")
+local deps = require("luarocks.deps")
+local cfg = require("luarocks.cfg")
+local persist = require("luarocks.persist")
+local fetch = require("luarocks.fetch")
+local type_check = require("luarocks.type_check")
+
+manifest_cache = {}
+
+--- Get all versions of a package listed in a manifest file.
+-- @param name string: a package name.
+-- @param manifest table or nil: a manifest table; if not given, the
+-- default local manifest table is used.
+-- @return table: An array of strings listing installed
+-- versions of a package.
+function get_versions(name, manifest)
+ assert(type(name) == "string")
+ assert(type(manifest) == "table" or not manifest)
+
+ if not manifest then
+ manifest = load_local_manifest(cfg.rocks_dir)
+ if not manifest then
+ return {}
+ end
+ end
+
+ local item = manifest.repository[name]
+ if item then
+ return util.keys(item)
+ end
+ return {}
+end
+
+--- Back-end function that actually loads the manifest
+-- and stores it in the manifest cache.
+-- @param file string: The local filename of the manifest file.
+-- @param repo_url string: The repository identifier.
+local function manifest_loader(file, repo_url, quick)
+ local manifest = persist.load_into_table(file)
+ if not manifest then
+ return nil, "Failed loading manifest for "..repo_url
+ end
+ if not quick then
+ local ok, err = type_check.type_check_manifest(manifest)
+ if not ok then
+ return nil, "Error checking manifest: "..err
+ end
+ end
+
+ manifest_cache[repo_url] = manifest
+ return manifest
+end
+
+--- Load a local or remote manifest describing a repository.
+-- All functions that use manifest tables assume they were obtained
+-- through either this function or load_local_manifest.
+-- @param repo_url string: URL or pathname for the repository.
+-- @return table or (nil, string): A table representing the manifest,
+-- or nil followed by an error message.
+function load_manifest(repo_url)
+ assert(type(repo_url) == "string")
+
+ if manifest_cache[repo_url] then
+ return manifest_cache[repo_url]
+ end
+
+ local protocol, pathname = fs.split_url(repo_url)
+ if protocol == "file" then
+ pathname = fs.make_path(pathname, "manifest")
+ else
+ local url = fs.make_path(repo_url, "manifest")
+ local name = repo_url:gsub("[/:]","_")
+ local file, dir = fetch.fetch_url_at_temp_dir(url, "luarocks-manifest-"..name)
+ if not file then
+ return nil, "Failed fetching manifest for "..repo_url
+ end
+ pathname = file
+ end
+ return manifest_loader(pathname, repo_url)
+end
+
+--- Load a local manifest describing a repository.
+-- All functions that use manifest tables assume they were obtained
+-- through either this function or load_manifest.
+-- @param repo_url string: URL or pathname for the repository.
+-- @return table or (nil, string): A table representing the manifest,
+-- or nil followed by an error message.
+function load_local_manifest(repo_url)
+ assert(type(repo_url) == "string")
+
+ if manifest_cache[repo_url] then
+ return manifest_cache[repo_url]
+ end
+
+ local pathname = fs.make_path(repo_url, "manifest")
+
+ return manifest_loader(pathname, repo_url, true)
+end
+
+--- Sort function for ordering rock identifiers in a manifest's
+-- modules table. Rocks are ordered alphabetically by name, and then
+-- by version which greater first.
+-- @param a string: Version to compare.
+-- @param b string: Version to compare.
+-- @return boolean: The comparison result, according to the
+-- rule outlined above.
+local function sort_pkgs(a, b)
+ assert(type(a) == "string")
+ assert(type(b) == "string")
+
+ local na, va = a:match("(.*)/(.*)$")
+ local nb, vb = b:match("(.*)/(.*)$")
+
+ return (na == nb) and deps.compare_versions(va, vb) or na < nb
+end
+
+--- Output a table listing items of a package.
+-- @param itemsfn function: a function for obtaining items of a package.
+-- pkg and version will be passed to it; it should return a table with
+-- items as keys.
+-- @param pkg string: package name
+-- @param version string: package version
+-- @param tbl table: the package matching table: keys should be item names
+-- and values arrays of strings with packages names in "name/version" format.
+local function store_package_items(itemsfn, pkg, version, tbl)
+ assert(type(itemsfn) == "function")
+ assert(type(pkg) == "string")
+ assert(type(version) == "string")
+ assert(type(tbl) == "table")
+
+ local path = pkg.."/"..version
+ local result = {}
+ for item, _ in pairs(itemsfn(pkg, version)) do
+ table.insert(result, item)
+ if not tbl[item] then
+ tbl[item] = {}
+ end
+ table.insert(tbl[item], path)
+ end
+ return result
+end
+
+--- Sort items of a package matching table by version number (higher versions first).
+-- @param tbl table: the package matching table: keys should be strings
+-- and values arrays of strings with packages names in "name/version" format.
+local function sort_package_matching_table(tbl)
+ assert(type(tbl) == "table")
+
+ if next(tbl) then
+ for item, pkgs in pairs(tbl) do
+ if #pkgs > 1 then
+ table.sort(pkgs, sort_pkgs)
+ -- Remove duplicates from the sorted array.
+ local prev = nil
+ local i = 1
+ while pkgs[i] do
+ local curr = pkgs[i]
+ if curr == prev then
+ table.remove(pkgs, i)
+ else
+ prev = curr
+ i = i + 1
+ end
+ end
+ end
+ end
+ end
+end
+
+--- Commit manifest to disk in given local repository.
+-- @param repo string: The directory of the local repository.
+-- @param manifest table: The manifest table
+-- @return boolean or (nil, string): true if successful, or nil and a
+-- message in case of errors.
+local function save_manifest(repo, manifest)
+ assert(type(repo) == "string")
+ assert(type(manifest) == "table")
+
+ local filename = fs.make_path(repo, "manifest")
+ return persist.save_from_table(filename, manifest)
+end
+
+--- Process the dependencies of a package to determine its dependency
+-- chain for loading modules.
+-- @param name string: Package name.
+-- @param version string: Package version.
+-- @return (table, table): A table listing dependencies as string-string pairs
+-- of names and versions, and a similar table of missing dependencies.
+local function update_dependencies(manifest)
+ for pkg, versions in pairs(manifest.repository) do
+ for version, repos in pairs(versions) do
+ local current = pkg.." "..version
+ for _, repo in ipairs(repos) do
+ if repo.arch == "installed" then
+ local missing
+ repo.dependencies, missing = deps.scan_deps({}, {}, manifest, pkg, version)
+ repo.dependencies[pkg] = nil
+ if missing then
+ for miss, _ in pairs(missing) do
+ if miss == current then
+ print("Tree inconsistency detected: "..current.." has no rockspec.")
+ else
+ print("Missing dependency for "..pkg.." "..version..": "..miss)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+--- Store search results in a manifest table.
+-- @param results table: The search results as returned by search.disk_search.
+-- @param manifest table: A manifest table (must contain repository, modules, commands tables).
+local function store_results(results, manifest)
+ assert(type(results) == "table")
+ assert(type(manifest) == "table")
+
+ for pkg, versions in pairs(results) do
+ local pkgtable = manifest.repository[pkg] or {}
+ for version, repos in pairs(versions) do
+ local versiontable = {}
+ for _, repo in ipairs(repos) do
+ local repotable = {}
+ repotable.arch = repo.arch
+ if repo.arch == "installed" then
+ repotable.modules = store_package_items(rep.package_modules, pkg, version, manifest.modules)
+ repotable.commands = store_package_items(rep.package_commands, pkg, version, manifest.commands)
+ end
+ table.insert(versiontable, repotable)
+ end
+ pkgtable[version] = versiontable
+ end
+ manifest.repository[pkg] = pkgtable
+ end
+ update_dependencies(manifest)
+ sort_package_matching_table(manifest.modules)
+ sort_package_matching_table(manifest.commands)
+end
+
+--- Load a manifest file from a local repository and add to the repository
+-- information with regard to the given name and version.
+-- A file called 'manifest' will be written in the root of the given
+-- repository directory.
+-- @param name string: Name of a package from the repository.
+-- @param version string: Version of a package from the repository.
+-- @param repo string or nil: Pathname of a local repository. If not given,
+-- the default local repository configured as cfg.rocks_dir is used.
+-- @return boolean or (nil, string): True if manifest was generated,
+-- or nil and an error message.
+function update_manifest(name, version, repo)
+ assert(type(name) == "string")
+ assert(type(version) == "string")
+ assert(type(repo) == "string" or not repo)
+ repo = repo or cfg.rocks_dir
+
+ print("Updating manifest for "..repo)
+
+ local manifest, err = load_manifest(repo)
+ if not manifest then
+ print("No existing manifest. Attempting to rebuild...")
+ local ok, err = make_manifest(repo)
+ if not ok then
+ return nil, err
+ end
+ manifest, err = load_manifest(repo)
+ if not manifest then
+ return nil, err
+ end
+ end
+
+ local results = {[name] = {[version] = {{arch = "installed", repo = repo}}}}
+
+ store_results(results, manifest)
+ return save_manifest(repo, manifest)
+end
+
+--- Scan a LuaRocks repository and output a manifest file.
+-- A file called 'manifest' will be written in the root of the given
+-- repository directory.
+-- @param repo A local repository directory.
+-- @return boolean or (nil, string): True if manifest was generated,
+-- or nil and an error message.
+function make_manifest(repo)
+ assert(type(repo) == "string")
+
+ if not fs.is_dir(repo) then
+ return nil, "Cannot access repository at "..repo
+ end
+
+ local query = search.make_query("")
+ query.exact_name = false
+ query.arch = "any"
+ local results = search.disk_search(repo, query)
+
+ local manifest = { repository = {}, modules = {}, commands = {} }
+ manifest_cache[repo] = manifest
+ store_results(results, manifest)
+ return save_manifest(repo, manifest)
+end
+
+local index_header = [[
+
+
+
+Available rocks
+
+
+
+
+Available rocks
+
+Lua modules avaliable from this location for use with LuaRocks:
+
+
+]]
+
+local index_package_start = [[
+
+ $package - $summary
+ $detailed
+latest sources | project homepage | License: $license
+
|
+
+]]
+
+local index_package_end = [[
+ |
+ |
+]]
+
+local index_footer = [[
+
+
+manifest file
+
+
+
+]]
+
+function make_index(repo)
+ if not fs.is_dir(repo) then
+ return nil, "Cannot access repository at "..repo
+ end
+ local manifest = load_manifest(repo)
+ files = fs.find(repo)
+ local out = io.open(fs.make_path(repo, "index.html"), "w")
+ out:write(index_header)
+ for package, version_list in util.sortedpairs(manifest.repository) do
+ local latest_rockspec = nil
+ local output = index_package_start
+ for version, data in util.sortedpairs(version_list, deps.compare_versions) do
+ local out_versions = {}
+ local arches = 0
+ output = output..version
+ local sep = ': '
+ for _, item in ipairs(data) do
+ output = output .. sep .. ''..item.arch..''
+ sep = ', '
+ if item.arch == 'rockspec' then
+ local rs = ("%s-%s.rockspec"):format(package, version)
+ if not latest_rockspec then latest_rockspec = rs end
+ output = output:gsub("$url", rs)
+ else
+ output = output:gsub("$url", ("%s-%s.%s.rock"):format(package, version, item.arch))
+ end
+ end
+ output = output .. '
'
+ output = output:gsub("$na", arches)
+ end
+ output = output .. index_package_end
+ if latest_rockspec then
+ local rockspec = persist.load_into_table(fs.make_path(repo, latest_rockspec))
+ local vars = {
+ anchor = package,
+ package = rockspec.package,
+ original = rockspec.source.url,
+ summary = rockspec.description.summary or "",
+ detailed = rockspec.description.detailed or "",
+ license = rockspec.description.license or "N/A",
+ homepage = rockspec.description.homepage or ""
+ }
+ vars.detailed = vars.detailed:gsub("\n\n", "
"):gsub("%s+", " ")
+ output = output:gsub("$(%w+)", vars)
+ else
+ output = output:gsub("$anchor", package)
+ output = output:gsub("$package", package)
+ output = output:gsub("$(%w+)", "")
+ end
+ out:write(output)
+ end
+ out:write(index_footer)
+ out:close()
+end
+
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 @@
+
+--- Module implementing the LuaRocks "pack" command.
+-- Creates a rock, packing sources or binaries.
+module("luarocks.pack", package.seeall)
+
+local path = require("luarocks.path")
+local rep = require("luarocks.rep")
+local fetch = require("luarocks.fetch")
+local fs = require("luarocks.fs")
+local cfg = require("luarocks.cfg")
+local util = require("luarocks.util")
+
+help_summary = "Create a rock, packing sources or binaries."
+help_arguments = "{| []}"
+help = [[
+Argument may be a rockspec file, for creating a source rock,
+or the name of an installed package, for creating a binary rock.
+In the latter case, the app version may be given as a second
+argument.
+]]
+
+--- Create a source rock.
+-- Packages a rockspec and its required source files in a rock
+-- file with the .src.rock extension, which can later be built and
+-- installed with the "build" command.
+-- @param rockspec_file string: An URL or pathname for a rockspec file.
+-- @return string or (nil, string): The filename of the resulting
+-- .src.rock file; or nil and an error message.
+local function pack_source_rock(rockspec_file)
+ assert(type(rockspec_file) == "string")
+
+ rockspec_file = fs.absolute_name(rockspec_file)
+ local rockspec, err = fetch.load_rockspec(rockspec_file)
+ if err then
+ return nil, "Error loading rockspec: "..err
+ end
+
+ local name_version = rockspec.name .. "-" .. rockspec.version
+ local rock_file = fs.absolute_name(name_version .. ".src.rock")
+
+ local source_file, dir = fetch.fetch_sources(rockspec, false)
+ if not source_file then
+ return nil, dir
+ end
+ fs.change_dir(dir)
+
+ fs.delete(rock_file)
+ fs.copy(rockspec_file, dir)
+ if not fs.zip(rock_file, fs.base_name(rockspec_file), fs.base_name(source_file)) then
+ return nil, "Failed packing "..rock_file
+ end
+ fs.pop_dir()
+
+ return rock_file
+end
+
+-- @param name string: Name of package to pack.
+-- @param version string or nil: A version number may also be passed.
+-- @return string or (nil, string): The filename of the resulting
+-- .src.rock file; or nil and an error message.
+local function pack_binary_rock(name, version)
+ assert(type(name) == "string")
+ assert(type(version) == "string" or not version)
+
+ local versions = rep.get_versions(name)
+
+ if not versions then
+ return nil, "'"..name.."' does not seem to be an installed rock."
+ end
+ if not version then
+ if #versions > 1 then
+ return nil, "Please specify which version of '"..name.."' to pack."
+ end
+ version = versions[1]
+ end
+ if not version:match("[^-]+%-%d+") then
+ return nil, "Expected version "..version.." in version-revision format."
+ end
+ local prefix = path.install_dir(name, version)
+ if not fs.exists(prefix) then
+ return nil, "'"..name.." "..version.."' does not seem to be an installed rock."
+ end
+ local name_version = name .. "-" .. version
+ local rock_file = fs.absolute_name(name_version .. "."..cfg.arch..".rock")
+ fs.change_dir(prefix)
+ if not rep.is_binary_rock(name, version) then
+ rock_file = rock_file:gsub("%."..cfg.arch:gsub("%-","%%-").."%.", ".all.")
+ end
+ fs.delete(rock_file)
+ if not fs.zip(rock_file, unpack(fs.dir())) then
+ return nil, "Failed packing "..rock_file
+ end
+ fs.pop_dir()
+ return rock_file
+end
+
+--- Driver function for the "pack" command.
+-- @param arg string: may be a rockspec file, for creating a source rock,
+-- or the name of an installed package, for creating a binary rock.
+-- @param version string or nil: if the name of a package is given, a
+-- version may also be passed.
+-- @return boolean or (nil, string): true if successful or nil followed
+-- by an error message.
+function run(...)
+ local flags, arg, version = util.parse_flags(...)
+ assert(type(version) == "string" or not version)
+ if type(arg) ~= "string" then
+ return nil, "Argument missing, see help."
+ end
+
+ if arg:match(".*%.rockspec") then
+ return pack_source_rock(arg)
+ else
+ return pack_binary_rock(arg, version)
+ end
+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 @@
+
+--- Path and filename handling functions.
+-- All paths are configured in this module, making it a single
+-- point where the layout of the local installation is defined in LuaRocks.
+module("luarocks.path", package.seeall)
+
+local fs = require("luarocks.fs")
+local cfg = require("luarocks.cfg")
+
+--- Infer rockspec filename from a rock filename.
+-- @param rock_name string: Pathname of a rock file.
+-- @return string: Filename of the rockspec, without path.
+function rockspec_name_from_rock(rock_name)
+ assert(type(rock_name) == "string")
+ local base_name = fs.base_name(rock_name)
+ return base_name:match("(.*)%.[^.]*.rock") .. ".rockspec"
+end
+
+--- Get the repository directory for all versions of a package.
+-- @param name string: The package name.
+-- @return string: The resulting path -- does not guarantee that
+-- @param repo string or nil: If given, specifies the local repository to use.
+-- the package (and by extension, the path) exists.
+function versions_dir(name, repo)
+ assert(type(name) == "string")
+ assert(not repo or type(repo) == "string")
+
+ return fs.make_path(repo or cfg.rocks_dir, name)
+end
+
+--- Get the local installation directory (prefix) for a package.
+-- @param name string: The package name.
+-- @param version string: The package version.
+-- @param repo string or nil: If given, specifies the local repository to use.
+-- @return string: The resulting path -- does not guarantee that
+-- the package (and by extension, the path) exists.
+function install_dir(name, version, repo)
+ assert(type(name) == "string")
+ assert(type(version) == "string")
+ assert(not repo or type(repo) == "string")
+
+ return fs.make_path(repo or cfg.rocks_dir, name, version)
+end
+
+--- Get the local filename of the rockspec of an installed rock.
+-- @param name string: The package name.
+-- @param version string: The package version.
+-- @param repo string or nil: If given, specifies the local repository to use.
+-- @return string: The resulting path -- does not guarantee that
+-- the package (and by extension, the file) exists.
+function rockspec_file(name, version, repo)
+ assert(type(name) == "string")
+ assert(type(version) == "string")
+ assert(not repo or type(repo) == "string")
+
+ return fs.make_path(repo or cfg.rocks_dir, name, version, name.."-"..version..".rockspec")
+end
+
+--- Get the local installation directory for C libraries of a package.
+-- @param name string: The package name.
+-- @param version string: The package version.
+-- @param repo string or nil: If given, specifies the local repository to use.
+-- @return string: The resulting path -- does not guarantee that
+-- the package (and by extension, the path) exists.
+function lib_dir(name, version, repo)
+ assert(type(name) == "string")
+ assert(type(version) == "string")
+ assert(not repo or type(repo) == "string")
+
+ return fs.make_path(repo or cfg.rocks_dir, name, version, "lib")
+end
+
+--- Get the local installation directory for Lua modules of a package.
+-- @param name string: The package name.
+-- @param version string: The package version.
+-- @param repo string or nil: If given, specifies the local repository to use.
+-- @return string: The resulting path -- does not guarantee that
+-- the package (and by extension, the path) exists.
+function lua_dir(name, version, repo)
+ assert(type(name) == "string")
+ assert(type(version) == "string")
+ assert(not repo or type(repo) == "string")
+
+ return fs.make_path(repo or cfg.rocks_dir, name, version, "lua")
+end
+
+--- Get the local installation directory for documentation of a package.
+-- @param name string: The package name.
+-- @param version string: The package version.
+-- @param repo string or nil: If given, specifies the local repository to use.
+-- @return string: The resulting path -- does not guarantee that
+-- the package (and by extension, the path) exists.
+function doc_dir(name, version, repo)
+ assert(type(name) == "string")
+ assert(type(version) == "string")
+ assert(not repo or type(repo) == "string")
+
+ return fs.make_path(repo or cfg.rocks_dir, name, version, "doc")
+end
+
+--- Get the local installation directory for configuration files of a package.
+-- @param name string: The package name.
+-- @param version string: The package version.
+-- @param repo string or nil: If given, specifies the local repository to use.
+-- @return string: The resulting path -- does not guarantee that
+-- the package (and by extension, the path) exists.
+function conf_dir(name, version, repo)
+ assert(type(name) == "string")
+ assert(type(version) == "string")
+ assert(not repo or type(repo) == "string")
+
+ return fs.make_path(repo or cfg.rocks_dir, name, version, "conf")
+end
+
+--- Get the local installation directory for command-line scripts
+-- of a package.
+-- @param name string: The package name.
+-- @param version string: The package version.
+-- @param repo string or nil: If given, specifies the local repository to use.
+-- @return string: The resulting path -- does not guarantee that
+-- the package (and by extension, the path) exists.
+function bin_dir(name, version, repo)
+ assert(type(name) == "string")
+ assert(type(version) == "string")
+ assert(not repo or type(repo) == "string")
+
+ return fs.make_path(repo or cfg.rocks_dir, name, version, "bin")
+end
+
+--- Extract name, version and arch of a rock filename.
+-- @param rock_file string: pathname of a rock
+-- @return (string, string, string) or nil: name, version and arch
+-- of rock, or nil if name could not be parsed
+function parse_rock_name(rock_file)
+ assert(type(rock_file) == "string")
+ return fs.base_name(rock_file):match("(.*)-([^-]+-%d+)%.([^.]+)%.rock$")
+end
+
+--- Extract name and version of a rockspec filename.
+-- @param rockspec_file string: pathname of a rockspec
+-- @return (string, string) or nil: name and version
+-- of rockspec, or nil if name could not be parsed
+function parse_rockspec_name(rockspec_file)
+ assert(type(rockspec_file) == "string")
+ return fs.base_name(rockspec_file):match("(.*)-([^-]+-%d+)%.(rockspec)")
+end
+
+--- Make a rockspec or rock URL.
+-- @param pathname string: Base URL or pathname.
+-- @param name string: Package name.
+-- @param version string: Package version.
+-- @param arch string: Architecture identifier, or "rockspec" or "installed".
+-- @return string: A URL or pathname following LuaRocks naming conventions.
+function make_url(pathname, name, version, arch)
+ assert(type(pathname) == "string")
+ assert(type(name) == "string")
+ assert(type(version) == "string")
+ assert(type(arch) == "string")
+
+ local filename = name.."-"..version
+ if arch == "installed" then
+ filename = fs.make_path(name, version, filename..".rockspec")
+ elseif arch == "rockspec" then
+ filename = filename..".rockspec"
+ else
+ filename = filename.."."..arch..".rock"
+ end
+ return fs.make_path(pathname, filename)
+end
+
+--- Convert a pathname to a module identifier.
+-- In Unix, for example, a path "foo/bar/baz.lua" is converted to
+-- "foo.bar.baz"; "bla/init.lua" returns "bla"; "foo.so" returns "foo".
+-- @param file string: Pathname of module
+-- @return string: The module identifier, or nil if given path is
+-- not a conformant module path (the function does not check if the
+-- path actually exists).
+function path_to_module(file)
+ assert(type(file) == "string")
+
+ local name = file:match("(.*)%."..cfg.lua_extension.."$")
+ if name then
+ name = name:gsub(fs.dir_separator, ".")
+ local init = name:match("(.*)%.init$")
+ if init then
+ name = init
+ end
+ else
+ name = file:match("(.*)%."..cfg.lib_extension.."$")
+ if name then
+ name = name:gsub(fs.dir_separator, ".")
+ end
+ end
+ return name
+end
+
+--- Obtain the directory name where a module should be stored.
+-- For example, on Unix, "foo.bar.baz" will return "foo/bar".
+-- @param mod string: A module name in Lua dot-separated format.
+-- @return string: A directory name using the platform's separator.
+function module_to_path(mod)
+ assert(type(mod) == "string")
+ return (mod:gsub("[^.]*$", ""):gsub("%.", fs.dir_separator))
+end
+
+--- Set up path-related variables for a given rock.
+-- Create a "variables" table in the rockspec table, containing
+-- adjusted variables according to the configuration file.
+-- @param rockspec table: The rockspec table.
+function configure_paths(rockspec)
+ assert(type(rockspec) == "table")
+ local vars = {}
+ for k,v in pairs(cfg.variables) do
+ vars[k] = v
+ end
+ local name, version = rockspec.name, rockspec.version
+ vars.PREFIX = install_dir(name, version)
+ vars.LUADIR = lua_dir(name, version)
+ vars.LIBDIR = lib_dir(name, version)
+ vars.CONFDIR = conf_dir(name, version)
+ vars.BINDIR = bin_dir(name, version)
+ vars.DOCDIR = doc_dir(name, version)
+ rockspec.variables = vars
+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 @@
+
+--- Utility module for loading files into tables and
+-- saving tables into files.
+-- Implemented separately to avoid interdependencies,
+-- as it is used in the bootstrapping stage of the cfg module.
+module("luarocks.persist", package.seeall)
+
+--- Load a Lua file containing assignments, storing them in a table.
+-- The global environment is not propagated to the loaded file.
+-- @param filename string: the name of the file.
+-- @param tbl table or nil: if given, this table is used to store
+-- loaded values.
+-- @return table or (nil, string): a table with the file's assignments
+-- as fields, or nil and a message in case of errors.
+function load_into_table(filename, tbl)
+ assert(type(filename) == "string")
+ assert(type(tbl) == "table" or not tbl)
+
+ local chunk, err = loadfile(filename)
+ if not chunk then
+ return nil, err
+ end
+ local result = {}
+ if tbl then result = tbl end
+ setfenv(chunk, result)
+ chunk()
+ return result
+end
+
+--- Write a table as Lua code representing a table to disk
+-- (that is, in curly brackets notation).
+-- This function handles only numbers, strings and tables
+-- are keys (tables are handled recursively).
+-- @param out userdata: a file object, open for writing.
+-- @param tbl table: the table to be written.
+local function write_table(out, tbl)
+ out:write("{")
+ local size = table.getn(tbl)
+ local sep = ""
+ local i = 1
+ for k, v in pairs(tbl) do
+ out:write(sep)
+ if type(k) == "number" then
+ if k ~= i then
+ out:write(tostring(k).."=")
+ else
+ i = i + 1
+ end
+ elseif type(k) == "table" then
+ out:write("[")
+ write_table(out, k)
+ out:write("]=")
+ else
+ if k:match("^[a-z_]+$") then
+ out:write(k.."=")
+ else
+ out:write("['"..k:gsub("'", "\\'").."']=")
+ end
+ end
+ local typ = type(v)
+ if typ == "table" then
+ write_table(out, v)
+ elseif typ == "string" then
+ out:write("'"..v:gsub("'", "\\'").."'")
+ else
+ out:write(tostring(v))
+ end
+ sep = ", "
+ end
+ out:write("}\n")
+end
+
+--- Save the contents of a table in a file.
+-- Each element of the table is saved as a global assignment.
+-- Only numbers, strings and tables (containing numbers, strings
+-- or other recursively processed tables) are supported.
+-- @return boolean or (nil, string): true if successful, or nil and a
+-- message in case of errors.
+function save_from_table(filename, tbl)
+ local out = io.open(filename, "w")
+ if not out then
+ return nil, "Cannot create file at "..filename
+ end
+ for k, v in pairs(tbl) do
+ out:write(k.." = ")
+ write_table(out, v)
+ out:write("\n")
+ end
+ out:close()
+ return true
+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 @@
+
+--- Module implementing the LuaRocks "remove" command.
+-- Uninstalls rocks.
+module("luarocks.remove", package.seeall)
+
+local search = require("luarocks.search")
+local deps = require("luarocks.deps")
+local fetch = require("luarocks.fetch")
+local rep = require("luarocks.rep")
+local path = require("luarocks.path")
+local util = require("luarocks.util")
+local cfg = require("luarocks.cfg")
+local manif = require("luarocks.manif")
+
+help_summary = "Uninstall a rock."
+help_arguments = "[--force] []"
+help = [[
+Argument is the name of a rock to be uninstalled.
+If a version is not given, try to remove all versions at once.
+Will only perform the removal if it does not break dependencies.
+To override this check and force the removal, use --force.
+]]
+
+--- Obtain a list of packages that depend on the given set of packages
+-- (where all packages of the set are versions of one program).
+-- @param name string: the name of a program
+-- @param versions array of string: the versions to be deleted.
+-- @return array of string: an empty table if no packages depend on any
+-- of the given list, or an array of strings in "name/version" format.
+local function check_dependents(name, versions)
+ local dependents = {}
+ local blacklist = {}
+ blacklist[name] = {}
+ for version, _ in pairs(versions) do
+ blacklist[name][version] = true
+ end
+ local local_rocks = {}
+ local query_all = search.make_query("")
+ query_all.exact_name = false
+ search.manifest_search(local_rocks, cfg.rocks_dir, query_all)
+ local_rocks[name] = nil
+ for rock_name, rock_versions in pairs(local_rocks) do
+ for rock_version, _ in pairs(rock_versions) do
+ local rockspec, err = fetch.load_rockspec(path.rockspec_file(rock_name, rock_version))
+ if rockspec then
+ local _, missing = deps.match_deps(rockspec, blacklist)
+ if missing[name] then
+ table.insert(dependents, { name = rock_name, version = rock_version })
+ end
+ end
+ end
+ end
+ return dependents
+end
+
+--- Delete given versions of a program.
+-- @param name string: the name of a program
+-- @param versions array of string: the versions to be deleted.
+-- @return boolean or (nil, string): true on success or nil and an error message.
+local function delete_versions(name, versions)
+
+ local manifest, err = manif.load_manifest(cfg.rocks_dir)
+ if not manifest then
+ return nil, err
+ end
+
+ local commands = {}
+ for version, data in pairs(versions) do
+ for _, command in pairs(manifest.repository[name][version][1].commands) do
+ if not commands[command] then commands[command] = {} end
+ table.insert(commands[command], name.."/"..version)
+ end
+ end
+
+ if next(commands) then
+ for command, users in pairs(commands) do
+ local providers_of_command = manifest.commands[command]
+ for _, user in ipairs(users) do
+ for i, value in ipairs(providers_of_command) do
+ if providers_of_command[i] == user then
+ table.remove(providers_of_command, i)
+ break
+ end
+ end
+ end
+ local remaining = next(providers_of_command)
+ if remaining then
+ local name, version = remaining:match("^([^/]*)/(.*)$")
+ rep.install_bins(name, version, command)
+ else
+ rep.delete_bin(command)
+ end
+ end
+ end
+
+ for version, _ in pairs(versions) do
+ print("Removing "..name.." "..version.."...")
+ rep.delete_version(name, version)
+ end
+
+ return true
+end
+
+--- Driver function for the "install" command.
+-- @param name string: name of a rock. If a version is given, refer to
+-- a specific version; otherwise, try to remove all versions.
+-- @param version string: When passing a package name, a version number
+-- may also be given.
+-- @return boolean or (nil, string): True if removal was
+-- successful, nil and an error message otherwise.
+function run(...)
+ local flags, name, version = util.parse_flags(...)
+
+ if type(name) ~= "string" then
+ return nil, "Argument missing, see help."
+ end
+ local results = {}
+ search.manifest_search(results, cfg.rocks_dir, search.make_query(name, version))
+
+ local versions = results[name]
+ if not versions then
+ return nil, "Could not find rock '"..name..(version and " "..version or "").."' in local tree."
+ else
+ local version = next(versions)
+ local second = next(versions, version)
+
+ print("Checking stability of dependencies on the absence of")
+ print(name.." "..table.concat(util.keys(versions), ", ").."...")
+ print()
+
+ local dependents = check_dependents(name, versions)
+
+ if #dependents == 0 or flags["force"] then
+ if #dependents > 0 then
+ print("The following packages may be broken by this forced removal:")
+ for _, dependent in ipairs(dependents) do
+ print(dependent.name.." "..dependent.version)
+ end
+ print()
+ end
+ local ok, err1 = delete_versions(name, versions)
+ local ok, err2 = manif.make_manifest(cfg.rocks_dir)
+ if err1 or err2 then
+ return nil, err1 or err2
+ end
+ else
+ if not second then
+ print("Will not remove "..name.." "..version..".")
+ print("Removing it would break dependencies for: ")
+ else
+ print("Will not remove all versions of "..name..".")
+ print("Removing them would break dependencies for: ")
+ end
+ for _, dependent in ipairs(dependents) do
+ print(dependent.name.." "..dependent.version)
+ end
+ print()
+ print("Use --force to force removal (warning: this may break modules).")
+ return nil, "Failed removing."
+ end
+ end
+ return true
+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 @@
+
+--- Functions for managing the repository on disk.
+module("luarocks.rep", package.seeall)
+
+local fs = require("luarocks.fs")
+local path = require("luarocks.path")
+local cfg = require("luarocks.cfg")
+local util = require("luarocks.util")
+
+--- Get all installed versions of a package.
+-- @param name string: a package name.
+-- @return table or nil: An array of strings listing installed
+-- versions of a package, or nil if none is available.
+function get_versions(name)
+ assert(type(name) == "string")
+
+ local dirs = fs.dir(path.versions_dir(name))
+ return (dirs and #dirs > 0) and dirs or nil
+end
+
+--- Check if a package exists in a local repository.
+-- Version numbers are compared as exact string comparison.
+-- @param name string: name of package
+-- @param version string: package version in string format
+-- @return boolean: true if a package is installed,
+-- false otherwise.
+function is_installed(name, version)
+ assert(type(name) == "string")
+ assert(type(version) == "string")
+
+ return fs.is_dir(path.install_dir(name, version))
+end
+
+--- Delete a package from the local repository.
+-- Version numbers are compared as exact string comparison.
+-- @param name string: name of package
+-- @param version string: package version in string format
+function delete_version(name, version)
+ assert(type(name) == "string")
+ assert(type(version) == "string")
+
+ fs.delete(path.install_dir(name, version))
+ if not get_versions(name) then
+ fs.delete(fs.make_path(cfg.rocks_dir, name))
+ end
+end
+
+--- Delete a command-line item from the bin directory.
+-- @param command string: name of script
+function delete_bin(command)
+ assert(type(command) == "string")
+
+ fs.delete(fs.make_path(cfg.scripts_dir, command))
+end
+
+--- Install bin entries in the repository bin dir.
+-- @param name string: name of package
+-- @param version string: package version in string format
+-- @param single_file string or nil: optional parameter, indicating the name
+-- of a single file to install; if not given, all bin files from the package
+-- are installed.
+-- @return boolean or (nil, string): True if succeeded or nil and
+-- and error message.
+function install_bins(name, version, single_file)
+ assert(type(name) == "string")
+ assert(type(version) == "string")
+
+ local bindir = path.bin_dir(name, version)
+ if fs.exists(bindir) then
+ local ok, err = fs.make_dir(cfg.scripts_dir)
+ if not ok then
+ return nil, "Could not create "..cfg.scripts_dir
+ end
+ local files = single_file and {single_file} or fs.dir(bindir)
+ for _, file in pairs(files) do
+ local fullname = fs.make_path(bindir, file)
+ local match = file:match("%.lua$")
+ local file
+ if not match then
+ file = io.open(fullname)
+ end
+ if match or (file and file:read():match("#!.*lua.*")) then
+ ok, err = fs.wrap_script(fullname, cfg.scripts_dir)
+ else
+ ok, err = fs.copy_binary(fullname, cfg.scripts_dir)
+ end
+ if file then file:close() end
+ if not ok then
+ return nil, err
+ end
+ end
+ end
+ return true
+end
+
+--- Obtain a list of modules within an installed package.
+-- @param package string: The package name; for example "luasocket"
+-- @param version string: The exact version number including revision;
+-- for example "2.0.1-1".
+-- @return table: A table of modules where keys are module identifiers
+-- in "foo.bar" format and values are pathnames in architecture-dependent
+-- "foo/bar.so" format. If no modules are found or if package or version
+-- are invalid, an empty table is returned.
+function package_modules(package, version)
+ assert(type(package) == "string")
+ assert(type(version) == "string")
+
+ local result = {}
+ local luas = fs.find(path.lua_dir(package, version))
+ local libs = fs.find(path.lib_dir(package, version))
+ for _, file in ipairs(luas) do
+ local name = path.path_to_module(file)
+ if name then
+ result[name] = file
+ end
+ end
+ for _, file in ipairs(libs) do
+ local name = path.path_to_module(file)
+ if name then
+ result[name] = file
+ end
+ end
+ return result
+end
+
+--- Obtain a list of command-line scripts within an installed package.
+-- @param package string: The package name; for example "luasocket"
+-- @param version string: The exact version number including revision;
+-- for example "2.0.1-1".
+-- @return table: A table of items where keys are command names
+-- as strings and values are pathnames in architecture-dependent
+-- ".../bin/foo" format. If no modules are found or if package or version
+-- are invalid, an empty table is returned.
+function package_commands(package, version)
+ assert(type(package) == "string")
+ assert(type(version) == "string")
+
+ local result = {}
+ local bindir = path.bin_dir(package, version)
+ local bins = fs.find(bindir)
+ for _, file in ipairs(bins) do
+ if file then
+ result[file] = fs.make_path(bindir, file)
+ end
+ end
+ return result
+end
+
+--- Check if a rock contains binary parts or if it is pure Lua.
+-- @param name string: name of an installed rock
+-- @param version string: version of an installed rock
+-- @return boolean: returns true if rock contains platform-specific
+-- binary code, or false if it is a pure-Lua rock.
+function is_binary_rock(name, version)
+ local bin_dir = path.bin_dir(name, version)
+ local lib_dir = path.lib_dir(name, version)
+ if fs.exists(lib_dir) then
+ return true
+ end
+ if fs.exists(bin_dir) then
+ for _, name in pairs(fs.find(bin_dir)) do
+ if fs.is_actual_binary(fs.make_path(bin_dir, name)) then
+ return true
+ end
+ end
+ end
+ return false
+end
+
+function run_hook(rockspec, hook_name)
+ local hooks = rockspec.hooks
+ if not hooks then
+ return true
+ end
+ if not hooks.substituted_variables then
+ util.variable_substitutions(hooks, rockspec.variables)
+ hooks.substituted_variables = true
+ end
+ local hook = hooks[hook_name]
+ if hook then
+ print(hook)
+ if not fs.execute(hook) then
+ return nil, "Failed running "..hook_name.." hook."
+ end
+ end
+ return true
+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 @@
+
+local global_env = _G
+local plain_require = require
+local plain_package_path = package.path
+local plain_package_cpath = package.cpath
+local package, assert, ipairs, pairs, os, print, table, type, next =
+ package, assert, ipairs, pairs, os, print, table, type, next
+
+--- Application interface with LuaRocks.
+-- Load this module to LuaRocks-enable an application:
+-- this overrides the require() function, making it able to
+-- load modules installed as rocks.
+module("luarocks.require")
+
+local path = plain_require("luarocks.path")
+local manif = plain_require("luarocks.manif")
+local deps = plain_require("luarocks.deps")
+local cfg = plain_require("luarocks.cfg")
+
+context = {}
+
+-- Contains a table when rocks trees are loaded,
+-- or 'false' to indicate rocks trees failed to load.
+-- 'nil' indicates rocks trees were not attempted to be loaded yet.
+rocks_trees = nil
+
+local function load_rocks_trees()
+ local any_ok = false
+ local trees = {}
+ for _, tree in pairs(cfg.rocks_trees) do
+ local rocks_dir = tree .. "/rocks/"
+ local manifest, err = manif.load_local_manifest(rocks_dir)
+ if manifest then
+ any_ok = true
+ table.insert(trees, {rocks_dir=rocks_dir, manifest=manifest})
+ end
+ end
+ if not any_ok then
+ rocks_trees = false
+ return false
+ end
+ rocks_trees = trees
+ return true
+end
+
+--- Process the dependencies of a package to determine its dependency
+-- chain for loading modules.
+-- @parse name string: The name of an installed rock.
+-- @parse version string: The version of the rock, in string format
+-- @parse manifest table: The local manifest table where this rock
+-- is installed.
+local function add_context(name, version, manifest)
+ -- assert(type(name) == "string")
+ -- assert(type(version) == "string")
+ -- assert(type(manifest) == "table")
+
+ if context[name] then
+ return
+ end
+ context[name] = version
+
+ local pkgdeps = manifest.dependencies and manifest.dependencies[name][version]
+ if pkgdeps then
+ for _, dep in ipairs(pkgdeps) do
+ local package, constraints = dep.name, dep.constraints
+
+ for _, tree in pairs(rocks_trees) do
+ local entries = tree.manifest.repository[package]
+ if entries then
+ for version, packages in pairs(entries) do
+ if (not constraints) or deps.match_constraints(deps.parse_version(version), constraints) then
+ add_context(package, version, tree.manifest)
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+--- Internal sorting function.
+-- @param a table: A provider table.
+-- @param b table: Another provider table.
+-- @return boolean: True if the version of a is greater than that of b.
+local function sort_versions(a,b)
+ return a.version > b.version
+end
+
+--- Specify a dependency chain for LuaRocks.
+-- In the presence of multiple versions of packages, it is necessary to,
+-- at some point, indicate which dependency chain we're following.
+-- set_context does this by allowing one to pick a package to be the
+-- root of this dependency chain. Once a dependency chain is picked it's
+-- easy to know which modules to load ("I want to use *this* version of
+-- A, which requires *that* version of B, which requires etc etc etc").
+-- @param name string: The package name of an installed rock.
+-- @param version string or nil: Optionally, a version number
+-- When a version is not given, it picks the highest version installed.
+-- @return boolean: true if succeeded, false otherwise.
+function set_context(name, version)
+ --assert(type(name) == "string")
+ --assert(type(version) == "string" or not version)
+
+ if rocks_trees == false or (not rocks_trees and not load_rocks_trees()) then
+ return false
+ end
+
+ local manifest
+ local vtables = {}
+ for _, tree in ipairs(rocks_trees) do
+ if version then
+ local manif_repo = tree.manifest.repository
+ if manif_repo[name] and manif_repo[name][version] then
+ manifest = tree.manifest
+ break
+ end
+ else
+ local versions = manif.get_versions(name, tree.manifest)
+ for _, version in ipairs(versions) do
+ table.insert(vtables, {version = deps.parse_version(version), manifest = tree.manifest})
+ end
+ end
+ end
+ if not version then
+ if not next(vtables) then
+ table.sort(vtables, sort_versions)
+ local highest = vtables[#vtables]
+ version = highest.version.string
+ manifest = highest.manifest
+ end
+ end
+ if not manifest then
+ return false
+ end
+
+ add_context(name, version, manifest)
+ -- TODO: platform independence
+ local lpath, cpath = "", ""
+ for name, version in pairs(context) do
+ lpath = lpath .. path.lua_dir(name, version) .. "/?.lua;"
+ lpath = lpath .. path.lua_dir(name, version) .. "/?/init.lua;"
+ cpath = cpath .. path.lib_dir(name, version) .."/?."..cfg.lib_extension..";"
+ end
+ global_env.package.path = lpath .. plain_package_path
+ global_env.package.cpath = cpath .. plain_package_cpath
+end
+
+--- Call the vanilla require() function using specially constructed
+-- package paths so that it finds exactly the version we want it to find.
+-- @param name string: The rock name.
+-- @param version string: The rock version.
+-- @param module string: The module name, in require() notation.
+-- @return The result returned by require().
+local function plain_require_on(module, name, version, rocks_dir, ...)
+ --assert(type(module) == "string")
+ --assert(type(name) == "string")
+ --assert(type(version) == "string")
+
+ local global_package = global_env.package
+ local save_path = global_package.path
+ local save_cpath = global_package.cpath
+ global_package.path = path.lua_dir(name, version, rocks_dir) .. "/?.lua;"
+ .. path.lua_dir(name, version, rocks_dir) .. "/?/init.lua;" .. save_path
+ global_package.cpath = path.lib_dir(name, version, rocks_dir) .. "/?."..cfg.lib_extension..";" .. save_cpath
+ local result = plain_require(module, ...)
+ global_package.path = save_path
+ global_package.cpath = save_cpath
+ return result
+end
+
+local function pick_module(module, constraints)
+ --assert(type(module) == "string")
+ --assert(not constraints or type(constraints) == "string")
+
+ if not rocks_trees and not load_rocks_trees() then
+ return nil
+ end
+
+ if constraints then
+ if type(constraints) == "string" then
+ constraints = deps.parse_constraints(constraints)
+ else
+ constraints = nil
+ end
+ end
+
+ local providers = {}
+ for _, tree in pairs(rocks_trees) do
+ local entries = tree.manifest.modules[module]
+ if entries then
+ for _, entry in pairs(entries) do
+ local name, version = entry:match("^([^/]*)/(.*)$")
+ if context[name] == version then
+ return name, version, tree
+ end
+ version = deps.parse_version(version)
+ if (not constraints) or deps.match_constraints(version, constraints) then
+ table.insert(providers, {name = name, version = version, repo = tree})
+ end
+ end
+ end
+ end
+
+ if next(providers) then
+ table.sort(providers, sort_versions)
+ local first = providers[1]
+ return first.name, first.version.string, first.repo
+ end
+end
+
+--- Inform which rock LuaRocks would use if require() is called
+-- with the given arguments.
+-- @param module string: The module name, like in plain require().
+-- @param constraints string or nil: An optional comma-separated
+-- list of version constraints.
+-- @return (string, string) or nil: Rock name and version if the
+-- requested module can be supplied by LuaRocks, or nil if it can't.
+function get_rock_from_module(module, constraints)
+ --assert(type(module) == "string")
+ --assert(not constraints or type(constraints) == "string")
+ local name, version = pick_module(module, constraints)
+ return name, version
+end
+
+--- Function that overloads require(), adding LuaRocks support.
+-- This function wraps around Lua's standard require() call,
+-- allowing it to find modules installed by LuaRocks.
+-- A module is searched in installed rocks that match the
+-- current LuaRocks context. If module is not part of the
+-- context, or if a context has not yet been set, the module
+-- in the package with the highest version is used.
+-- If a module is not available in any installed rock, plain
+-- require() is called, using the original package.path and
+-- package.cpath lookup locations.
+-- Additionally, version constraints for the matching rock may
+-- be given as a second parameter. If version constraints could
+-- not be fulfilled, this equates to the rock not being
+-- available: as such, plain require() with the default paths
+-- is called as a fallback.
+-- @param module string: The module name, like in plain require().
+-- @param constraints string or nil: An optional comma-separated
+-- list of version constraints.
+-- @return table: The module table (typically), like in plain
+-- require(). See require()
+-- in the Lua reference manual for details.
+function require(module, ...)
+ --assert(type(module) == "string")
+
+ if package.loaded[module] or rocks_trees == false or (not rocks_trees and not load_rocks_trees()) then
+ return plain_require(module, ...)
+ end
+
+ local name, version, repo = pick_module(module, ...)
+
+ if not name then
+ return plain_require(module, ...)
+ else
+ add_context(name, version, repo.manifest)
+ return plain_require_on(module, name, version, repo.rocks_dir, ...)
+ end
+end
+
+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 @@
+
+--- Module implementing the LuaRocks "search" command.
+-- Queries LuaRocks servers.
+module("luarocks.search", package.seeall)
+
+local fs = require("luarocks.fs")
+local path = require("luarocks.path")
+local manif = require("luarocks.manif")
+local deps = require("luarocks.deps")
+local cfg = require("luarocks.cfg")
+local util = require("luarocks.util")
+
+help_summary = "Query the LuaRocks servers."
+help_arguments = "[--source] [--binary] { [] | --all }"
+help = [[
+--source Return only rockspecs and source rocks,
+ to be used with the "build" command.
+--binary Return only pure Lua and binary rocks (rocks that can be used
+ with the "install" command without requiring a C toolchain).
+--all List all contents of the server that are suitable to
+ this platform, do not filter by name.
+]]
+
+--- Convert the arch field of a query table to table format.
+-- @param query table: A query table.
+local function query_arch_as_table(query)
+ local format = type(query.arch)
+ if format == "table" then
+ return
+ elseif format == "nil" then
+ local accept = {}
+ accept["src"] = true
+ accept["all"] = true
+ accept["rockspec"] = true
+ accept["installed"] = true
+ accept[cfg.arch] = true
+ query.arch = accept
+ elseif format == "string" then
+ local accept = {}
+ for a in string.gmatch(query.arch, "[%w_]+") do
+ accept[a] = true
+ end
+ query.arch = accept
+ end
+end
+
+--- Store a search result (a rock or rockspec) in the results table.
+-- @param results table: The results table, where keys are package names and
+-- versions are tables matching version strings to an array of servers.
+-- @param name string: Package name.
+-- @param version string: Package version.
+-- @param arch string: Architecture of rock ("all", "src" or platform
+-- identifier), "rockspec" or "installed"
+-- @param repo string: Pathname of a local repository of URL of
+-- rocks server.
+local function store_result(results, name, version, arch, repo)
+ assert(type(results) == "table")
+ assert(type(name) == "string")
+ assert(type(version) == "string")
+ assert(type(arch) == "string")
+ assert(type(repo) == "string")
+
+ if not results[name] then results[name] = {} end
+ if not results[name][version] then results[name][version] = {} end
+ table.insert(results[name][version], {
+ arch = arch,
+ repo = repo
+ })
+end
+
+--- Test the name field of a query.
+-- If query has a boolean field exact_name set to false,
+-- then substring match is performed; otherwise, exact string
+-- comparison is done.
+-- @param query table: A query in dependency table format.
+-- @param name string: A package name.
+-- @return boolean: True if names match, false otherwise.
+local function match_name(query, name)
+ assert(type(query) == "table")
+ assert(type(name) == "string")
+ if query.exact_name == false then
+ return name:find(query.name, 0, true) and true or false
+ else
+ return name == query.name
+ end
+end
+
+--- Store a match in a results table if version matches query.
+-- Name, version, arch and repository path are stored in a given
+-- table, optionally checking if version and arch (if given) match
+-- a query.
+-- @param results table: The results table, where keys are package names and
+-- versions are tables matching version strings to an array of servers.
+-- @param repo string: URL or pathname of the repository.
+-- @param name string: The name of the package being tested.
+-- @param version string: The version of the package being tested.
+-- @param arch string: The arch of the package being tested.
+-- @param query table: A table describing the query in dependency
+-- format (for example, {name = "filesystem", exact_name = false,
+-- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec").
+-- If the arch field is omitted, the local architecture (cfg.arch)
+-- is used. The special value "any" is also recognized, returning all
+-- matches regardless of architecture.
+local function store_if_match(results, repo, name, version, arch, query)
+ if match_name(query, name) then
+ if query.arch[arch] or query.arch["any"] then
+ if deps.match_constraints(deps.parse_version(version), query.constraints) then
+ store_result(results, name, version, arch, repo)
+ end
+ end
+ end
+end
+
+--- Perform search on a local repository.
+-- @param repo string: The pathname of the local repository.
+-- @param query table: A table describing the query in dependency
+-- format (for example, {name = "filesystem", exact_name = false,
+-- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec").
+-- If the arch field is omitted, the local architecture (cfg.arch)
+-- is used. The special value "any" is also recognized, returning all
+-- matches regardless of architecture.
+-- @param results table or nil: If given, this table will store the
+-- results; if not given, a new table will be created.
+-- @param table: The results table, where keys are package names and
+-- versions are tables matching version strings to an array of servers.
+-- If a table was given in the "results" parameter, that is the result value.
+function disk_search(repo, query, results)
+ assert(type(repo) == "string")
+ assert(type(query) == "table")
+ assert(type(results) == "table" or not results)
+ if not results then
+ results = {}
+ end
+ query_arch_as_table(query)
+
+ for _, name in pairs(fs.dir(repo)) do
+ local pathname = fs.make_path(repo, name)
+ local rname, rversion, rarch = path.parse_rock_name(name)
+ if not rname then
+ rname, rversion, rarch = path.parse_rockspec_name(name)
+ end
+ if fs.is_dir(pathname) then
+ for _, version in pairs(fs.dir(pathname)) do
+ if version:match("-%d+$") then
+ store_if_match(results, repo, name, version, "installed", query)
+ end
+ end
+ elseif rname then
+ store_if_match(results, repo, rname, rversion, rarch, query)
+ end
+ end
+ return results
+end
+
+--- Perform search on a rocks server.
+-- @param results table: The results table, where keys are package names and
+-- versions are tables matching version strings to an array of servers.
+-- @param repo string: The URL of the rocks server.
+-- @param query table: A table describing the query in dependency
+-- format (for example, {name = "filesystem", exact_name = false,
+-- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec").
+-- If the arch field is omitted, the local architecture (cfg.arch)
+-- is used. The special value "any" is also recognized, returning all
+-- matches regardless of architecture.
+function manifest_search(results, repo, query)
+ assert(type(results) == "table")
+ assert(type(repo) == "string")
+ assert(type(query) == "table")
+
+ query_arch_as_table(query)
+ local manifest, err = manif.load_manifest(repo)
+ if not manifest then
+ print(err)
+ return
+ end
+ for name, versions in pairs(manifest.repository) do
+ for version, items in pairs(versions) do
+ for _, item in ipairs(items) do
+ store_if_match(results, repo, name, version, item.arch, query)
+ end
+ end
+ end
+end
+
+--- Search on all configured rocks servers.
+-- @param query table: A dependency query.
+-- @return table or (nil, string): A table where keys are package names
+-- and values are tables matching version strings to an array of
+-- rocks servers; if no results are found, an empty table is returned.
+-- In case of errors, nil and and error message are returned.
+local function search_repos(query)
+ assert(type(query) == "table")
+
+ local results = {}
+ for _, repo in ipairs(cfg.rocks_servers) do
+ local protocol, pathname = fs.split_url(repo)
+ if protocol == "file" then
+ repo = pathname
+ end
+ manifest_search(results, repo, query)
+ end
+ return results
+end
+
+--- Prepare a query in dependency table format.
+-- @param name string: The query name.
+-- @param version string or nil:
+-- @return table: A query in table format
+function make_query(name, version)
+ assert(type(name) == "string")
+ assert(type(version) == "string" or not version)
+
+ local query = {
+ name = name,
+ constraints = {}
+ }
+ if version then
+ table.insert(query.constraints, { op = "~>", version = deps.parse_version(version)})
+ end
+ return query
+end
+
+--- Get the URL for the latest in a set of versions.
+-- @param name string: The package name to be used in the URL.
+-- @param versions table: An array of version informations, as stored
+-- in search results tables.
+-- @return string or nil: the URL for the latest version if one could
+-- be picked, or nil.
+local function pick_latest_version(name, versions)
+ assert(type(name) == "string")
+ assert(type(versions) == "table")
+
+ local vtables = {}
+ for v, _ in pairs(versions) do
+ table.insert(vtables, deps.parse_version(v))
+ end
+ table.sort(vtables)
+ local version = vtables[#vtables].string
+ local items = versions[version]
+ if items then
+ local pick = 1
+ for i, item in ipairs(items) do
+ if (item.arch == 'src' and items[pick].arch == 'rockspec')
+ or (item.arch ~= 'src' and item.arch ~= 'rockspec') then
+ pick = i
+ end
+ end
+ return path.make_url(items[pick].repo, name, version, items[pick].arch)
+ end
+ return nil
+end
+
+--- Attempt to get a single URL for a given search.
+-- @param query table: A dependency query.
+-- @return string or table or (nil, string): URL for matching rock if
+-- a single one was found, a table of candidates if it could not narrow to
+-- a single result, or nil followed by an error message.
+function find_suitable_rock(query)
+ assert(type(query) == "table")
+
+ local results, err = search_repos(query)
+ if not results then
+ return nil, err
+ end
+ local first = results and next(results)
+ if first and next(results, first) == nil then
+ return pick_latest_version(query.name, results[first])
+ elseif not first then
+ return nil, "No results matching query were found."
+ else
+ return results
+ end
+end
+
+--- Print a list of rocks/rockspecs on standard output.
+-- @param results table: A table where keys are package names and versions
+-- are tables matching version strings to an array of rocks servers.
+-- @param show_repo boolean or nil: Whether to show repository
+-- information or not. Default is true.
+function print_results(results, show_repo)
+ assert(type(results) == "table")
+ assert(type(show_repo) == "boolean" or not show_repo)
+ -- Force display of repo location for the time being
+ show_repo = true -- show_repo == nil and true or show_repo
+
+ for package, versions in util.sortedpairs(results) do
+ print(package)
+ for version, repos in util.sortedpairs(versions, deps.compare_versions) do
+ if show_repo then
+ for _, repo in ipairs(repos) do
+ print(" "..version.." ("..repo.arch..") - "..repo.repo)
+ end
+ else
+ print(" "..version)
+ end
+ end
+ print()
+ end
+end
+
+--- Splits a list of search results into two lists, one for "source" results
+-- to be used with the "build" command, and one for "binary" results to be
+-- used with the "install" command.
+-- @param results table: A search results table.
+-- @return (table, table): Two tables, one for source and one for binary
+-- results.
+local function split_source_and_binary_results(results)
+ local sources, binaries = {}, {}
+ for name, versions in pairs(results) do
+ for version, repos in pairs(versions) do
+ for _, repo in ipairs(repos) do
+ local where = sources
+ if repo.arch == "all" or repo.arch == cfg.arch then
+ where = binaries
+ end
+ store_result(where, name, version, repo.arch, repo.repo)
+ end
+ end
+ end
+ return sources, binaries
+end
+
+--- Given a name and optionally a version, try to find in the rocks
+-- servers a single .src.rock or .rockspec file that satisfies
+-- the request, and run the given function on it; or display to the
+-- user possibilities if it couldn't narrow down a single match.
+-- @param action function: A function that takes a .src.rock or
+-- .rockspec URL as a parameter.
+-- @string name string: A rock name
+-- @string version string or nil: A version number may also be given.
+-- @return The result of the action function, or nil and an error message.
+function act_on_src_or_rockspec(action, name, version)
+ assert(type(action) == "function")
+ assert(type(name) == "string")
+ assert(type(version) == "string" or not version)
+
+ local query = make_query(name, version)
+ query.arch = "src|rockspec"
+ local results, err = find_suitable_rock(query)
+ if type(results) == "string" then
+ return action(results)
+ elseif type(results) == "table" and next(results) then
+ print("Multiple search results were returned.")
+ print()
+ print("Search results:")
+ print("---------------")
+ print_results(results)
+ return nil, "Please narrow your query."
+ else
+ return nil, "Could not find a result named "..name.."."
+ end
+end
+
+--- Driver function for "search" command.
+-- @param name string: A substring of a rock name to search.
+-- @param version string or nil: a version may also be passed.
+-- @return boolean or (nil, string): True if build was successful; nil and an
+-- error message otherwise.
+function run(...)
+ local flags, name, version = util.parse_flags(...)
+
+ if flags["all"] then
+ name, version = "", ""
+ end
+
+ if type(name) ~= "string" and not flags["all"] then
+ return nil, "Enter name and version or use --all; see help."
+ end
+
+ local query = make_query(name, version)
+ query.exact_name = false
+ local results, err = search_repos(query)
+ if not results then
+ return nil, err
+ end
+ print()
+ print("Search results:")
+ print("===============")
+ print()
+ local sources, binaries = split_source_and_binary_results(results)
+ if next(sources) and not flags["binary"] then
+ print("Rockspecs and source rocks:")
+ print("---------------------------")
+ print()
+ print_results(sources, true)
+ end
+ if next(binaries) and not flags["source"] then
+ print("Binary and pure-Lua rocks:")
+ print("--------------------------")
+ print()
+ print_results(binaries, true)
+ end
+ return true
+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 @@
+
+--- Type-checking functions.
+-- Functions and definitions for doing a basic lint check on files
+-- loaded by LuaRocks.
+module("luarocks.type_check", package.seeall)
+
+rockspec_format = "1.0"
+
+rockspec_types = {
+ rockspec_format = "string",
+ MUST_package = "string",
+ MUST_version = "string",
+ description = {
+ summary = "string",
+ detailed = "string",
+ homepage = "string",
+ license = "string",
+ maintainer = "string"
+ },
+ dependencies = {
+ platforms = {},
+ ANY = "string"
+ },
+ supported_platforms = {
+ ANY = "string"
+ },
+ external_dependencies = {
+ platforms = {},
+ ANY = {
+ program = "string",
+ header = "string",
+ library = "string"
+ }
+ },
+ MUST_source = {
+ platforms = {},
+ MUST_url = "string",
+ md5 = "string",
+ file = "string",
+ dir = "string",
+ tag = "string",
+ branch = "string",
+ cvs_tag = "string",
+ cvs_module = "string"
+ },
+ build = {
+ platforms = {},
+ type = "string",
+ install = {
+ lua = {
+ MORE = true
+ },
+ lib = {
+ MORE = true
+ },
+ conf = {
+ MORE = true
+ },
+ bin = {
+ MORE = true
+ }
+ },
+ copy_directories = {
+ ANY = "string"
+ },
+ MORE = true
+ },
+ hooks = {
+ platforms = {},
+ post_install = "string"
+ }
+}
+
+rockspec_types.build.platforms.ANY = rockspec_types.build
+rockspec_types.dependencies.platforms.ANY = rockspec_types.dependencies
+rockspec_types.external_dependencies.platforms.ANY = rockspec_types.external_dependencies
+rockspec_types.MUST_source.platforms.ANY = rockspec_types.MUST_source
+rockspec_types.hooks.platforms.ANY = rockspec_types.hooks
+
+manifest_types = {
+ MUST_repository = {
+ -- packages
+ ANY = {
+ -- versions
+ ANY = {
+ -- items
+ ANY = {
+ MUST_arch = "string",
+ modules = { ANY = "string" },
+ commands = { ANY = "string" },
+ dependencies = { ANY = "string" },
+ -- TODO: to be extended with more metadata.
+ }
+ }
+ }
+ },
+ MUST_modules = {
+ -- modules
+ ANY = {
+ -- providers
+ ANY = "string"
+ }
+ },
+ MUST_commands = {
+ -- modules
+ ANY = {
+ -- commands
+ ANY = "string"
+ }
+ },
+ dependencies = {
+ -- each module
+ ANY = {
+ -- each version
+ ANY = {
+ -- each dependency
+ ANY = {
+ name = "string",
+ constraints = {
+ ANY = {
+ no_upgrade = "boolean",
+ op = "string",
+ version = {
+ string = "string",
+ ANY = 0,
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+local type_check_table
+
+--- Type check an object.
+-- The object is compared against an archetypical value
+-- matching the expected type -- the actual values don't matter,
+-- only their types. Tables are type checked recursively.
+-- @param name any: The object name (for error messages).
+-- @param item any: The object being checked.
+-- @param expected any: The reference object. In case of a table,
+-- its is structured as a type reference table.
+-- @return boolean or (nil, string): true if type checking
+-- succeeded, or nil and an error message if it failed.
+-- @see type_check_table
+local function type_check_item(name, item, expected, context)
+ name = tostring(name)
+
+ local item_type = type(item)
+ local expected_type = type(expected)
+ if expected_type == "number" then
+ if not tonumber(item) then
+ return nil, "Type mismatch on field "..context..name..": expected a number"
+ end
+ elseif expected_type == "table" then
+ if item_type ~= expected_type then
+ return nil, "Type mismatch on field "..context..name..": expected a table"
+ else
+ return type_check_table(item, expected, context..name..".")
+ end
+ elseif item_type ~= expected_type then
+ return nil, "Type mismatch on field "..context..name..": expected a "..expected_type
+ end
+ return true
+end
+
+--- Type check the contents of a table.
+-- The table's contents are compared against a reference table,
+-- which contains the recognized fields, with archetypical values
+-- matching the expected types -- the actual values of items in the
+-- reference table don't matter, only their types (ie, for field x
+-- in tbl that is correctly typed, type(tbl.x) == type(types.x)).
+-- If the reference table contains a field called MORE, then
+-- unknown fields in the checked table are accepted.
+-- If it contains a field called ANY, then its type will be
+-- used to check any unknown fields. If a field is prefixed
+-- with MUST_, it is mandatory; its absence from the table is
+-- a type error.
+-- Tables are type checked recursively.
+-- @param tbl table: The table to be type checked.
+-- @param types table: The reference table, containing
+-- values for recognized fields in the checked table.
+-- @return boolean or (nil, string): true if type checking
+-- succeeded, or nil and an error message if it failed.
+type_check_table = function(tbl, types, context)
+ assert(type(tbl) == "table")
+ assert(type(types) == "table")
+
+ for k, v in pairs(tbl) do
+ local t = types[k] or (type(k) == "string" and types["MUST_"..k]) or types.ANY
+ if t then
+ local ok, err = type_check_item(k, v, t, context)
+ if not ok then return nil, err end
+ elseif types.MORE then
+ -- Accept unknown field
+ else
+ return nil, "Unknown field "..k
+ end
+ end
+ for k, v in pairs(types) do
+ local mandatory_key = k:match("^MUST_(.+)")
+ if mandatory_key then
+ if not tbl[mandatory_key] then
+ return nil, "Mandatory field "..context..mandatory_key.." is missing."
+ end
+ end
+ end
+ return true
+end
+
+--- Type check a rockspec table.
+-- Verify the correctness of elements from a
+-- rockspec table, reporting on unknown fields and type
+-- mismatches.
+-- @return boolean or (nil, string): true if type checking
+-- succeeded, or nil and an error message if it failed.
+function type_check_rockspec(rockspec)
+ assert(type(rockspec) == "table")
+ return type_check_table(rockspec, rockspec_types, "")
+end
+
+--- Type check a manifest table.
+-- Verify the correctness of elements from a
+-- manifest table, reporting on unknown fields and type
+-- mismatches.
+-- @return boolean or (nil, string): true if type checking
+-- succeeded, or nil and an error message if it failed.
+function type_check_manifest(manifest)
+ assert(type(manifest) == "table")
+ return type_check_table(manifest, manifest_types, "")
+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 @@
+
+--- Module implementing the LuaRocks "unpack" command.
+-- Unpack the contents of a rock.
+module("luarocks.unpack", package.seeall)
+
+local fetch = require("luarocks.fetch")
+local fs = require("luarocks.fs")
+local util = require("luarocks.util")
+local build = require("luarocks.build")
+
+help_summary = "Unpack the contents of a rock."
+help_arguments = "{| []}"
+help = [[
+Unpacks the contents of a rock in a newly created directory.
+Argument may be a rock file, or the name of a rock in a rocks server.
+In the latter case, the app version may be given as a second argument.
+]]
+
+--- Load a rockspec file to the given directory, fetches the source
+-- files specified in the rockspec, and unpack them inside the directory.
+-- @param rockspec_file string: The URL for a rockspec file.
+-- @param dir_name string: The directory where to store and unpack files.
+-- @return table or (nil, string): the loaded rockspec table or
+-- nil and an error message.
+local function unpack_rockspec(rockspec_file, dir_name)
+ assert(type(rockspec_file) == "string")
+ assert(type(dir_name) == "string")
+
+ local rockspec = fetch.load_rockspec(rockspec_file)
+ if not rockspec then
+ return nil, "Failed loading rockspec "..rockspec_file
+ end
+ fs.change_dir(dir_name)
+ local ok, sources_dir = fetch.fetch_sources(rockspec, true, ".")
+ if not ok then
+ return nil, sources_dir
+ end
+ fs.change_dir(dir_name)
+ build.apply_patches(rockspec)
+ fs.pop_dir()
+ return rockspec
+end
+
+--- Load a .rock file to the given directory and unpack it inside it.
+-- @param rock_file string: The URL for a .rock file.
+-- @param dir_name string: The directory where to unpack.
+-- @return table or (nil, string): the loaded rockspec table or
+-- nil and an error message.
+local function unpack_rock(rock_file, dir_name, kind)
+ assert(type(rock_file) == "string")
+ assert(type(dir_name) == "string")
+
+ local ok, err = fetch.fetch_and_unpack_rock(rock_file, dir_name)
+ if not ok then
+ return nil, "Failed unzipping rock "..rock_file
+ end
+ fs.change_dir(dir_name)
+ local rockspec_file = dir_name..".rockspec"
+ local rockspec, err = fetch.load_rockspec(rockspec_file)
+ if not rockspec then
+ return nil, "Failed loading rockspec "..rockspec_file..": "..err
+ end
+ if kind == "src" then
+ if rockspec.source.file then
+ local ok, err = fs.unpack_archive(rockspec.source.file)
+ if not ok then
+ return nil, err
+ end
+ fs.change_dir(rockspec.source.dir)
+ build.apply_patches(rockspec)
+ fs.pop_dir()
+ end
+ end
+ return rockspec
+end
+
+--- Create a directory and perform the necessary actions so that
+-- the sources for the rock and its rockspec are unpacked inside it,
+-- laid out properly so that the 'make' command is able to build the module.
+-- @param file string: A rockspec or .rock URL.
+-- @return boolean or (nil, string): true if successful or nil followed
+-- by an error message.
+local function run_unpacker(file)
+ assert(type(file) == "string")
+
+ local base_name = fs.base_name(file)
+ local dir_name, kind, extension = base_name:match("(.*)%.([^.]+)%.(rock)$")
+ if not extension then
+ dir_name, extension = base_name:match("(.*)%.(rockspec)$")
+ kind = "rockspec"
+ end
+ if not extension then
+ return nil, file.." does not seem to be a valid filename."
+ end
+
+ if (fs.exists(dir_name)) then
+ return nil, "Directory "..dir_name.." already exists."
+ end
+ fs.make_dir(dir_name)
+ local rollback = util.schedule_function(fs.delete, fs.absolute_name(dir_name))
+
+ local rockspec, err
+ if extension == "rock" then
+ rockspec, err = unpack_rock(file, dir_name, kind)
+ elseif extension == "rockspec" then
+ rockspec, err = unpack_rockspec(file, dir_name)
+ end
+ if not rockspec then
+ return nil, err
+ end
+ if kind == "src" or kind == "rockspec" then
+ if rockspec.source.dir ~= "." then
+ local ok = fs.copy(rockspec.local_filename, rockspec.source.dir)
+ if not ok then
+ return nil, "Failed copying unpacked rockspec into unpacked source directory."
+ end
+ end
+ print()
+ print("Done. You may now enter directory ")
+ print(fs.make_path(dir_name, rockspec.source.dir))
+ print("and type 'luarocks make' to build.")
+ end
+ util.remove_scheduled_function(rollback)
+ return true
+end
+
+--- Driver function for the "unpack" command.
+-- @param name string: may be a rock filename, for unpacking a
+-- rock file or the name of a rock to be fetched and unpacked.
+-- @param version string or nil: if the name of a package is given, a
+-- version may also be passed.
+-- @return boolean or (nil, string): true if successful or nil followed
+-- by an error message.
+function run(...)
+ local flags, name, version = util.parse_flags(...)
+
+ assert(type(version) == "string" or not version)
+ if type(name) ~= "string" then
+ return nil, "Argument missing, see help."
+ end
+
+ if name:match(".*%.rock") or name:match(".*%.rockspec") then
+ return run_unpacker(name)
+ else
+ local search = require("luarocks.search")
+ return search.act_on_src_or_rockspec(run_unpacker, name, version)
+ end
+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 @@
+
+local global_env = _G
+
+--- Utility functions shared by other modules.
+-- Does not requires modules directly (only as locals
+-- inside specific functions) to avoid interdependencies,
+-- as this is used in the bootstrapping stage of luarocks.cfg.
+module("luarocks.util", package.seeall)
+
+local scheduled_functions = {}
+
+--- Schedule a function to be executed upon program termination.
+-- This is useful for actions such as deleting temporary directories
+-- or failure rollbacks.
+-- @param f function: Function to be executed.
+-- @param ... arguments to be passed to function.
+-- @return table: A token representing the scheduled execution,
+-- which can be used to remove the item later from the list.
+function schedule_function(f, ...)
+ assert(type(f) == "function")
+
+ local item = { fn = f, args = {...} }
+ table.insert(scheduled_functions, item)
+ return item
+end
+
+--- Unschedule a function.
+-- This is useful for cancelling a rollback of a completed operation.
+-- @param table: The token representing the scheduled function that was
+-- returned from the schedule_function call.
+function remove_scheduled_function(item)
+ for k, v in pairs(scheduled_functions) do
+ if v == item then
+ table.remove(scheduled_functions, k)
+ return
+ end
+ end
+end
+
+--- Execute scheduled functions.
+-- Some calls create temporary files and/or directories and register
+-- corresponding cleanup functions. Calling this function will run
+-- these function, erasing temporaries.
+-- Functions are executed in the inverse order they were scheduled.
+function run_scheduled_functions()
+ local fs = require("luarocks.fs")
+ fs.change_dir_to_root()
+ for i = #scheduled_functions, 1, -1 do
+ local item = scheduled_functions[i]
+ item.fn(unpack(item.args))
+ end
+end
+
+--- Extract flags from an arguments list.
+-- Given string arguments, extract flag arguments into a flags set.
+-- For example, given "foo", "--tux=beep", "--bla", "bar", "--baz",
+-- it would return the following:
+-- {["bla"] = true, ["tux"] = "beep", ["baz"] = true}, "foo", "bar".
+function parse_flags(...)
+ local args = {...}
+ local flags = {}
+ for i = #args, 1, -1 do
+ local flag = args[i]:match("^%-%-(.*)")
+ if flag then
+ local var,val = flag:match("([a-z_%-]*)=(.*)")
+ if val then
+ flags[var] = val
+ else
+ flags[flag] = true
+ end
+ table.remove(args, i)
+ end
+ end
+ return flags, unpack(args)
+end
+
+--- Merges contents of src on top of dst's contents.
+-- @param dst Destination table, which will receive src's contents.
+-- @param src Table which provides new contents to dst.
+-- @see platform_overrides
+function deep_merge(dst, src)
+ for k, v in pairs(src) do
+ if type(v) == "table" then
+ if not dst[k] then
+ dst[k] = {}
+ end
+ deep_merge(dst[k], v)
+ else
+ dst[k] = v
+ end
+ end
+end
+
+--- Perform platform-specific overrides on a table.
+-- Overrides values of table with the contents of the appropriate
+-- subset of its "platforms" field. The "platforms" field should
+-- be a table containing subtables keyed with strings representing
+-- platform names. Names that match the contents of the global
+-- cfg.platforms setting are used. For example, if
+-- cfg.platforms= {"foo"}, then the fields of
+-- tbl.platforms.foo will overwrite those of tbl with the same
+-- names. For table values, the operation is performed recursively
+-- (tbl.platforms.foo.x.y.z overrides tbl.x.y.z; other contents of
+-- tbl.x are preserved).
+-- @param tbl table or nil: Table which may contain a "platforms" field;
+-- if it doesn't (or if nil is passed), this function does nothing.
+function platform_overrides(tbl)
+ assert(type(tbl) == "table" or not tbl)
+
+ local cfg = require("luarocks.cfg")
+
+ if not tbl then return end
+
+ if tbl.platforms then
+ for _, platform in ipairs(cfg.platforms) do
+ local platform_tbl = tbl.platforms[platform]
+ if platform_tbl then
+ deep_merge(tbl, platform_tbl)
+ end
+ end
+ end
+ tbl.platforms = nil
+end
+
+--- Perform make-style variable substitutions on string values of a table.
+-- For every string value tbl.x which contains a substring of the format
+-- "$(XYZ)" will have this substring replaced by vars["XYZ"], if that field
+-- exists in vars. Only string values are processed; this function
+-- does not scan subtables recursively.
+-- @param tbl table: Table to have its string values modified.
+-- @param vars table: Table containing string-string key-value pairs
+-- representing variables to replace in the strings values of tbl.
+function variable_substitutions(tbl, vars)
+ assert(type(tbl) == "table")
+ assert(type(vars) == "table")
+
+ local updated = {}
+ for k, v in pairs(tbl) do
+ if type(v) == "string" then
+ updated[k] = v:gsub("%$%((%a[%a%d_]+)%)", vars)
+ end
+ end
+ for k, v in pairs(updated) do
+ tbl[k] = v
+ end
+end
+
+--- Return an array of keys of a table.
+-- @param tbl table: The input table.
+-- @return table: The array of keys.
+function keys(tbl)
+ local ks = {}
+ for k,_ in pairs(tbl) do
+ table.insert(ks, k)
+ end
+ return ks
+end
+
+-- The iterator function used internally by util.sortedpairs.
+-- @param tbl table: The table to be iterated.
+-- @param sort_function function or nil: An optional comparison function
+-- to be used by table.sort when sorting keys.
+-- @see sortedpairs
+local function sortedpairs_iterator(tbl, sort_function)
+ local ks = keys(tbl)
+ table.sort(ks, sort_function)
+ for _, k in ipairs(ks) do
+ coroutine.yield(k, tbl[k])
+ end
+end
+
+--- A table iterator generator that returns elements sorted by key,
+-- to be used in "for" loops.
+-- @param tbl table: The table to be iterated.
+-- @param sort_function function or nil: An optional comparison function
+-- to be used by table.sort when sorting keys.
+-- @return function: the iterator function.
+function sortedpairs(tbl, sort_function)
+ return coroutine.wrap(function() sortedpairs_iterator(tbl, sort_function) end)
+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 @@
+#!/bin/bash
+
+if [ -e ./run_tests.sh ]
+then
+ cd ../src
+elif [ -d src ]
+then
+ cd src
+elif ! [ -d luarocks ]
+then
+ echo "Go to the src directory and run this."
+ exit 1
+fi
+
+if [ ! -d ../rocks ]
+then
+ echo "Downloading entire rocks repository for tests"
+ cd ..
+ wget -r -nH -np -R"index.*" http://luarocks.luaforge.net/rocks/
+ cd src
+fi
+
+rocks=(
+ `ls ../rocks/*.rockspec | grep -v luacom`
+ `ls ../rocks/*.src.rock | grep -v luacom`
+)
+
+bin/luarocks-admin make-manifest ../rocks || exit 1
+
+[ "$1" ] && rocks=("$1")
+
+TRY() {
+ "$@" || {
+ echo "Failed running: $@"
+ exit 1
+ }
+}
+
+list_search() {
+ bin/luarocks list $name | grep $version
+}
+
+for rock in "${rocks[@]}"
+do
+ base=`basename $rock`
+ baserockspec=`basename $rock .rockspec`
+ basesrcrock=`basename $rock .src.rock`
+ if [ "$base" != "$baserockspec" ]
+ then
+ base=$baserockspec
+ name=`echo $base | sed 's/\(.*\)-[^-]*-[^-]*$/\1/'`
+ version=`echo $base | sed 's/.*-\([^-]*-[^-]*\)$/\1/'`
+ TRY bin/luarocks pack $rock
+ TRY bin/luarocks build $base.src.rock
+ TRY rm $base.src.rock
+ else
+ base=$basesrcrock
+ name=`echo $base | sed 's/\(.*\)-[^-]*-[^-]*$/\1/'`
+ version=`echo $base | sed 's/.*-\([^-]*-[^-]*\)$/\1/'`
+ TRY bin/luarocks build $rock
+ fi
+ TRY bin/luarocks pack $name $version
+ TRY bin/luarocks install $base.*.rock
+ TRY rm $base.*.rock
+ TRY list_search $name $version
+ bin/luarocks remove $name $version
+ # TODO: differentiate between error and dependency block.
+done
+
+if bin/luarocks install nonexistant | grep "No results"
+then echo "OK, got expected error."
+else exit 1
+fi
+
+TRY ../test/test_deps.lua
+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 @@
+#!/usr/bin/env lua
+
+deps = require "luarocks.deps"
+
+print(deps.show_dep(deps.parse_dep("lfs 2.1.9pre5"), true))
+print(deps.show_dep(deps.parse_dep("cgilua cvs-2"), true))
+print(deps.show_dep(deps.parse_dep("foobar 0.0.1beta"), true))
+print(deps.show_dep(deps.parse_dep("foobar 0.0.1a"), true))
+
+print(deps.show_dep(deps.parse_dep("foobar 1"), true))
+print(deps.show_dep(deps.parse_dep("foobar 2.0"), true))
+print(deps.show_dep(deps.parse_dep("foobar 3.5a4"), true))
+print(deps.show_dep(deps.parse_dep("foobar 1.1pre2"), true))
+print(deps.show_dep(deps.parse_dep("foobar 2.0-beta3"), true))
+print(deps.show_dep(deps.parse_dep("foobar 5.3"), true))
+print(deps.show_dep(deps.parse_dep("foobar 3.5rc2"), true))
+print(deps.show_dep(deps.parse_dep("foobar 4.19p"), true))
+
+print()
+comparisons = {
+-- first second eq le
+ {"Vista", "XP", false, true},
+ {"XP", "3.1", false, true},
+ {"1.0", "1.0", true, false},
+ {"2.2.10", "2.2-10", false, false},
+ {"2.2", "2.2-10", true, false},
+ {"1.0beta1", "1.0rc3", false, true},
+ {"2.0beta3", "2.0", false, true},
+ {"2.0beta", "2.0beta2", false, true},
+ {"2.0beta4", "2.0beta3", false, false},
+ {"2.1alpha1", "2.0beta1", false, false},
+ {"1.5p3", "1.5.1", false, true},
+ {"1.1.3", "1.1.3a", false, true},
+ {"1.5a100", "1.5b1", false, true},
+ {"2.0alpha100", "2.0beta1", false, true},
+ {"2.0.0beta3", "2.0beta2", false, false},
+ {"2.0-1", "2.0-2", false, true},
+ {"2.0-2", "2.0-1", false, false},
+ --[[
+ -- Corner cases I don't wish to handle by now.
+ {"2.0.0beta2", "2.0beta2", true, true},
+ {"2.0.0beta2", "2.0beta3", false, true},
+ ]]
+}
+
+local v1, v2
+
+err = false
+
+function result(test, expected)
+ if test == expected then
+ print(test, "OK")
+ else
+ print(test, "ERROR", deps.show_version(v1, true), deps.show_version(v2, true))
+ err = true
+ end
+end
+
+for _, c in ipairs(comparisons) do
+ v1, v2 = deps.parse_version(c[1]), deps.parse_version(c[2])
+ print(c[1].." == "..c[2].." ?")
+ result(v1 == v2, c[3])
+ print(c[1].." < "..c[2].." ?")
+ result(v1 < v2, c[4])
+end
+
+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 @@
+#!/usr/bin/env lua
+
+local luarocks = require("luarocks.require")
+
+luarocks.set_context("cgilua", "cvs-2")
+
+print(package.path)
+
+print(package.cpath)
+
+local socket = require("socket")
+if not socket then os.exit(1) end
+print(socket, socket._VERSION)
+
+local socket2 = require("socket")
+if not socket2 then os.exit(1) end
+print(socket2, socket2._VERSION)
+
+local mime = require("mime")
+if not mime then os.exit(1) end
+print(mime, mime._VERSION)
+
+local socket = require("lfs")
+if not lfs then os.exit(1) end
+print(lfs, lfs._VERSION)
--
cgit v1.2.3-55-g6feb