This is an important refactor of the way documentation generation works

* Installs luarocks WITH it's executable (easy to install other rocks if necessary)
* Use Lua supplied with luarocks
* Create Utils/luadocumentor.bat, which works with RELATIVE PATH ! -> Everybody can generate the doc
* Updated launch files accordingly
This commit is contained in:
Grey-Echo 2017-04-05 01:26:39 +02:00
parent 513a103947
commit 3b69cf992e
313 changed files with 16547 additions and 36807 deletions

View File

@ -3,7 +3,6 @@
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.ui.externaltools.launchGroup"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/lua/5.1/bin/luadocumentor.bat}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;Moose\Core\*.lua&quot; &#13;&#10;&quot;Moose\Wrapper\*.lua&quot; &#13;&#10;&quot;Moose\Actions\*.lua&quot; &#13;&#10;&quot;Moose\Functional\*.lua&quot; &#13;&#10;&quot;Moose\Tasking\*.lua&quot; &#13;&#10;&quot;Moose\Utilities\*.lua&quot; &#13;&#10;&quot;Moose\AI\*.lua&quot; &#13;&#10;--dir &quot;${workspace_loc:/Moose_Framework/docs/Documentation}&#13;&#10;--style ${workspace_loc:/Moose_Framework/docs/Stylesheet/stylesheet.css}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Framework/Moose Development}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/luadocumentor.bat}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Framework/Utils}"/>
</launchConfiguration>

View File

@ -3,7 +3,7 @@
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.ui.externaltools.launchGroup"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/lua/5.1/bin/lua.exe}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/luarocks/lua5.1.exe}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;Moose_Create.lua&quot; &#13;&#10;&quot;D&quot;&#13;&#10;&quot;${current_date}&quot; &#13;&#10;&quot;${workspace_loc:/Moose_Framework//Moose Development/Moose}&quot; &#13;&#10;&quot;${workspace_loc:/Moose_Framework/Moose Mission Setup}&quot;"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Framework/Moose Mission Setup}"/>
</launchConfiguration>

View File

@ -3,7 +3,7 @@
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.ui.externaltools.launchGroup"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/lua/5.1/bin/lua.exe}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/luarocks/lua5.1.exe}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;Moose_Create.lua&quot; &#13;&#10;&quot;S&quot;&#13;&#10;&quot;${current_date}&quot; &#13;&#10;&quot;${workspace_loc:/Moose_Framework//Moose Development/Moose}&quot; &#13;&#10;&quot;${workspace_loc:/Moose_Framework/Moose Mission Setup}&quot;"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Framework/Moose Mission Setup}"/>
</launchConfiguration>

View File

@ -4,6 +4,5 @@
<listEntry value="org.eclipse.ui.externaltools.launchGroup"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Moose Mission Setup/Moose Mission Update/Moose_Update_Missions.bat}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;${folder_prompt}&quot;"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Framework/Moose Mission Setup/Moose Mission Update}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${folder_prompt}"/>
</launchConfiguration>

View File

@ -1,6 +1,6 @@
--- **Core** - The RADIO class is responsible for **transmitting radio communications**.
--
-- --- bitmap
-- ![Banner Image](..\Presentations\RADIO\Dia1.JPG)
--
-- ===
--
@ -27,19 +27,20 @@
--
-- ===
--
-- ### Authors: Hugues "Grey_Echo" Bousquet
-- ### Author: Hugues "Grey_Echo" Bousquet
--
-- @module Radio
--- # 1) RADIO class, extends @{Base#BASE}
--
-- ## 1.1) RADIO usage
--
-- There are 3 steps to a successful radio transmission.
--
-- * First, you need to **"add" a @{#RADIO} object** to your @{Positionable#POSITIONABLE}. This is done using the @{Positionable#POSITIONABLE.GetRadio}() function,
-- * First, you need to **"add a @{#RADIO} object** to your @{Positionable#POSITIONABLE}. This is done using the @{Positionable#POSITIONABLE.GetRadio}() function,
-- * Then, you will **set the relevant parameters** to the transmission (see below),
-- * When done, you can actually **broadcast the transmission** (i.e. play the sound) with the @{Positionable#POSITIONABLE.Broadcast}() function.
-- * When done, you can actually **broadcast the transmission** (i.e. play the sound) with the @{RADIO.Broadcast}() function.
--
-- Methods to set relevant parameters for both a @{Unit#UNIT} or a @{Group#GROUP} or any other @{Positionable#POSITIONABLE}
--
@ -53,7 +54,7 @@
-- * @{#RADIO.SetSubtitle}() : Set both the subtitle and its duration,
-- * @{#RADIO.NewUnitTransmission}() : Shortcut to set all the relevant parameters in one method call
--
-- Additional Methods to set relevant parameters if the transmiter is any other @{Wrapper.Positionable#POSITIONABLE}
-- Additional Methods to set relevant parameters if the transmiter is any other @{Positionable#POSITIONABLE}
--
-- * @{#RADIO.SetPower}() : Sets the power of the antenna in Watts
-- * @{#RADIO.NewGenericTransmission}() : Shortcut to set all the relevant parameters in one method call
@ -68,7 +69,7 @@
-- * Note that if the transmission has a subtitle, it will be readable, regardless of the quality of the transmission.
--
-- @type RADIO
-- @field Wrapper.Positionable#POSITIONABLE Positionable The transmiter
-- @field Positionable#POSITIONABLE Positionable The transmiter
-- @field #string FileName Name of the sound file
-- @field #number Frequency Frequency of the transmission in Hz
-- @field #number Modulation Modulation of the transmission (either radio.modulation.AM or radio.modulation.FM)

View File

@ -66,13 +66,6 @@
-- @module Zone
--- The ZONE_BASE class
-- @type ZONE_BASE
-- @field #string ZoneName Name of the zone.
-- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability.
-- @extends Core.Base#BASE
--- # 1) ZONE_BASE class, extends @{Base#BASE}
--
-- This class is an abstract BASE class for derived classes, and is not meant to be instantiated.
@ -107,7 +100,11 @@
-- * @{#ZONE_BASE.FlareZone}(): Flares the zone boundaries in a color.
--
-- ===
-- @field #ZONE_BASE ZONE_BASE
--
-- @type ZONE_BASE
-- @field #string ZoneName Name of the zone.
-- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability.
-- @extends Core.Base#BASE
ZONE_BASE = {
ClassName = "ZONE_BASE",
ZoneName = "",
@ -306,12 +303,6 @@ function ZONE_BASE:GetZoneMaybe()
end
--- The ZONE_RADIUS class, defined by a zone name, a location and a radius.
-- @type ZONE_RADIUS
-- @field Dcs.DCSTypes#Vec2 Vec2 The current location of the zone.
-- @field Dcs.DCSTypes#Distance Radius The radius of the zone.
-- @extends Core.Zone#ZONE_BASE
--- # 2) @{Zone#ZONE_RADIUS} class, extends @{Zone#ZONE_BASE}
--
-- The ZONE_RADIUS class defined by a zone name, a location and a radius.
@ -342,8 +333,11 @@ end
--
-- ===
--
-- @field #ZONE_RADIUS ZONE_RADIUS
--
-- The ZONE_RADIUS class, defined by a zone name, a location and a radius.
-- @type ZONE_RADIUS
-- @field Dcs.DCSTypes#Vec2 Vec2 The current location of the zone.
-- @field Dcs.DCSTypes#Distance Radius The radius of the zone.
-- @extends Core.Zone#ZONE_BASE
ZONE_RADIUS = {
ClassName="ZONE_RADIUS",
}
@ -379,7 +373,6 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound )
local Angle
local RadialBase = math.pi*2
--
for Angle = 0, 360, (360 / Points ) do
local Radial = Angle * RadialBase / 360
Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius()

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,3 +0,0 @@
@echo off
"C:\Program Files\lua\5.1\bin\lua.exe" -e "package.path=\"C:\\Users\\svenv\\AppData\\Roaming/luarocks/share/lua/5.1/?.lua;C:\\Users\\svenv\\AppData\\Roaming/luarocks/share/lua/5.1/?/init.lua;c:\\program files\\lua\\5.1\\/share/lua/5.1/?.lua;c:\\program files\\lua\\5.1\\/share/lua/5.1/?/init.lua;C:\\Program Files (x86)\\LuaRocks\\lua\\?.lua;\"..package.path; package.cpath=\"C:\\Users\\svenv\\AppData\\Roaming/luarocks/lib/lua/5.1/?.dll;c:\\program files\\lua\\5.1\\/lib/lua/5.1/?.dll;\"..package.cpath" -e "local k,l,_=pcall(require,\"luarocks.loader\") _=k and l.add_context(\"luadocumentor\",\"0.1.5-1\")" "c:\program files\lua\5.1\\lib\luarocks\rocks\luadocumentor\0.1.5-1\bin\luadocumentor" %*
exit /b %ERRORLEVEL%

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,163 +0,0 @@
.\" $Id: lua.man,v 1.11 2006/01/06 16:03:34 lhf Exp $
.TH LUA 1 "$Date: 2006/01/06 16:03:34 $"
.SH NAME
lua \- Lua interpreter
.SH SYNOPSIS
.B lua
[
.I options
]
[
.I script
[
.I args
]
]
.SH DESCRIPTION
.B lua
is the stand-alone Lua interpreter.
It loads and executes Lua programs,
either in textual source form or
in precompiled binary form.
(Precompiled binaries are output by
.BR luac ,
the Lua compiler.)
.B lua
can be used as a batch interpreter and also interactively.
.LP
The given
.I options
(see below)
are executed and then
the Lua program in file
.I script
is loaded and executed.
The given
.I args
are available to
.I script
as strings in a global table named
.BR arg .
If these arguments contain spaces or other characters special to the shell,
then they should be quoted
(but note that the quotes will be removed by the shell).
The arguments in
.B arg
start at 0,
which contains the string
.RI ' script '.
The index of the last argument is stored in
.BR arg.n .
The arguments given in the command line before
.IR script ,
including the name of the interpreter,
are available in negative indices in
.BR arg .
.LP
At the very start,
before even handling the command line,
.B lua
executes the contents of the environment variable
.BR LUA_INIT ,
if it is defined.
If the value of
.B LUA_INIT
is of the form
.RI '@ filename ',
then
.I filename
is executed.
Otherwise, the string is assumed to be a Lua statement and is executed.
.LP
Options start with
.B '\-'
and are described below.
You can use
.B "'\--'"
to signal the end of options.
.LP
If no arguments are given,
then
.B "\-v \-i"
is assumed when the standard input is a terminal;
otherwise,
.B "\-"
is assumed.
.LP
In interactive mode,
.B lua
prompts the user,
reads lines from the standard input,
and executes them as they are read.
If a line does not contain a complete statement,
then a secondary prompt is displayed and
lines are read until a complete statement is formed or
a syntax error is found.
So, one way to interrupt the reading of an incomplete statement is
to force a syntax error:
adding a
.B ';'
in the middle of a statement is a sure way of forcing a syntax error
(except inside multiline strings and comments; these must be closed explicitly).
If a line starts with
.BR '=' ,
then
.B lua
displays the values of all the expressions in the remainder of the
line. The expressions must be separated by commas.
The primary prompt is the value of the global variable
.BR _PROMPT ,
if this value is a string;
otherwise, the default prompt is used.
Similarly, the secondary prompt is the value of the global variable
.BR _PROMPT2 .
So,
to change the prompts,
set the corresponding variable to a string of your choice.
You can do that after calling the interpreter
or on the command line
(but in this case you have to be careful with quotes
if the prompt string contains a space; otherwise you may confuse the shell.)
The default prompts are "> " and ">> ".
.SH OPTIONS
.TP
.B \-
load and execute the standard input as a file,
that is,
not interactively,
even when the standard input is a terminal.
.TP
.BI \-e " stat"
execute statement
.IR stat .
You need to quote
.I stat
if it contains spaces, quotes,
or other characters special to the shell.
.TP
.B \-i
enter interactive mode after
.I script
is executed.
.TP
.BI \-l " name"
call
.BI require(' name ')
before executing
.IR script .
Typically used to load libraries.
.TP
.B \-v
show version information.
.SH "SEE ALSO"
.BR luac (1)
.br
http://www.lua.org/
.SH DIAGNOSTICS
Error messages should be self explanatory.
.SH AUTHORS
R. Ierusalimschy,
L. H. de Figueiredo,
and
W. Celes
.\" EOF

View File

@ -1,136 +0,0 @@
.\" $Id: luac.man,v 1.28 2006/01/06 16:03:34 lhf Exp $
.TH LUAC 1 "$Date: 2006/01/06 16:03:34 $"
.SH NAME
luac \- Lua compiler
.SH SYNOPSIS
.B luac
[
.I options
] [
.I filenames
]
.SH DESCRIPTION
.B luac
is the Lua compiler.
It translates programs written in the Lua programming language
into binary files that can be later loaded and executed.
.LP
The main advantages of precompiling chunks are:
faster loading,
protecting source code from accidental user changes,
and
off-line syntax checking.
.LP
Pre-compiling does not imply faster execution
because in Lua chunks are always compiled into bytecodes before being executed.
.B luac
simply allows those bytecodes to be saved in a file for later execution.
.LP
Pre-compiled chunks are not necessarily smaller than the corresponding source.
The main goal in pre-compiling is faster loading.
.LP
The binary files created by
.B luac
are portable only among architectures with the same word size and byte order.
.LP
.B luac
produces a single output file containing the bytecodes
for all source files given.
By default,
the output file is named
.BR luac.out ,
but you can change this with the
.B \-o
option.
.LP
In the command line,
you can mix
text files containing Lua source and
binary files containing precompiled chunks.
This is useful to combine several precompiled chunks,
even from different (but compatible) platforms,
into a single precompiled chunk.
.LP
You can use
.B "'\-'"
to indicate the standard input as a source file
and
.B "'\--'"
to signal the end of options
(that is,
all remaining arguments will be treated as files even if they start with
.BR "'\-'" ).
.LP
The internal format of the binary files produced by
.B luac
is likely to change when a new version of Lua is released.
So,
save the source files of all Lua programs that you precompile.
.LP
.SH OPTIONS
Options must be separate.
.TP
.B \-l
produce a listing of the compiled bytecode for Lua's virtual machine.
Listing bytecodes is useful to learn about Lua's virtual machine.
If no files are given, then
.B luac
loads
.B luac.out
and lists its contents.
.TP
.BI \-o " file"
output to
.IR file ,
instead of the default
.BR luac.out .
(You can use
.B "'\-'"
for standard output,
but not on platforms that open standard output in text mode.)
The output file may be a source file because
all files are loaded before the output file is written.
Be careful not to overwrite precious files.
.TP
.B \-p
load files but do not generate any output file.
Used mainly for syntax checking and for testing precompiled chunks:
corrupted files will probably generate errors when loaded.
Lua always performs a thorough integrity test on precompiled chunks.
Bytecode that passes this test is completely safe,
in the sense that it will not break the interpreter.
However,
there is no guarantee that such code does anything sensible.
(None can be given, because the halting problem is unsolvable.)
If no files are given, then
.B luac
loads
.B luac.out
and tests its contents.
No messages are displayed if the file passes the integrity test.
.TP
.B \-s
strip debug information before writing the output file.
This saves some space in very large chunks,
but if errors occur when running a stripped chunk,
then the error messages may not contain the full information they usually do.
For instance,
line numbers and names of local variables are lost.
.TP
.B \-v
show version information.
.SH FILES
.TP 15
.B luac.out
default output file
.SH "SEE ALSO"
.BR lua (1)
.br
http://www.lua.org/
.SH DIAGNOSTICS
Error messages should be self explanatory.
.SH AUTHORS
L. H. de Figueiredo,
R. Ierusalimschy and
W. Celes
.\" EOF

3
Utils/luadocumentor.bat Normal file
View File

@ -0,0 +1,3 @@
@echo off
"./luarocks/lua5.1" -e "package.path=\"./luarocks/systree/share/lua/5.1/?.lua;./luarocks/systree/share/lua/5.1/?/init.lua;./luarocks/systree/share/lua/5.1/?.lua;./luarocks/systree/share/lua/5.1/?/init.lua;./luarocks/lua/?.lua;\"..package.path; package.cpath=\"./luarocks/lib/lua/5.1/?.dll;./luarocks/systree/lib/lua/5.1/?.dll;\"..package.cpath" -e "local k,l,_=pcall(require,\"luarocks.loader\") _=k and l.add_context(\"luadocumentor\",\"0.1.5-1\")" "./luarocks/systree/lib/luarocks/rocks/luadocumentor/0.1.5-1/bin/luadocumentor" -f doc -d "../docs/Documentation" -s "../docs/Stylesheet/stylesheet.css" "../Moose Development/Moose" %*
exit /b %ERRORLEVEL%

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<noInheritable></noInheritable>
<assemblyIdentity type="win32" name="Microsoft.VC80.CRT" version="8.0.50727.762" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
<file name="msvcr80.dll" hash="10f4cb2831f1e9288a73387a8734a8b604e5beaa" hashalg="SHA1"><asmv2:hash xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"><dsig:Transforms><dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity"></dsig:Transform></dsig:Transforms><dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></dsig:DigestMethod><dsig:DigestValue>n9On8FItNsK/DmT8UQxu6jYDtWQ=</dsig:DigestValue></asmv2:hash></file>
<file name="msvcp80.dll" hash="b2082dfd3009365c5b287448dcb3b4e2158a6d26" hashalg="SHA1"><asmv2:hash xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"><dsig:Transforms><dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity"></dsig:Transform></dsig:Transforms><dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></dsig:DigestMethod><dsig:DigestValue>0KJ/VTwP4OUHx98HlIW2AdW1kuY=</dsig:DigestValue></asmv2:hash></file>
<file name="msvcm80.dll" hash="542490d0fcf8615c46d0ca487033ccaeb3941f0b" hashalg="SHA1"><asmv2:hash xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"><dsig:Transforms><dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity"></dsig:Transform></dsig:Transforms><dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></dsig:DigestMethod><dsig:DigestValue>YJuB+9Os2oxW4mY+2oC/r8lICZE=</dsig:DigestValue></asmv2:hash></file>
</assembly>

BIN
Utils/luarocks/bin2c5.1.exe Normal file

Binary file not shown.

View File

@ -0,0 +1,14 @@
rocks_trees = {
home..[[/luarocks]],
{ name = [[user]],
root = home..[[/luarocks]],
},
{ name = [[system]],
root = [[C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks\systree]],
},
}
variables = {
MSVCRT = 'MSVCR80',
LUALIB = 'lua5.1.lib'
}
verbose = false -- set to 'true' to enable verbose output

View File

@ -1,5 +1,5 @@
/*
** $Id: lua.h,v 1.218.1.7 2012/01/13 20:36:20 roberto Exp $
** $Id: lua.h,v 1.218.1.5 2008/08/06 13:30:12 roberto Exp $
** Lua - An Extensible Extension Language
** Lua.org, PUC-Rio, Brazil (http://www.lua.org)
** See Copyright Notice at the end of this file
@ -17,9 +17,9 @@
#define LUA_VERSION "Lua 5.1"
#define LUA_RELEASE "Lua 5.1.5"
#define LUA_RELEASE "Lua 5.1.4"
#define LUA_VERSION_NUM 501
#define LUA_COPYRIGHT "Copyright (C) 1994-2012 Lua.org, PUC-Rio"
#define LUA_COPYRIGHT "Copyright (C) 1994-2008 Lua.org, PUC-Rio"
#define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo & W. Celes"
@ -362,7 +362,7 @@ struct lua_Debug {
/******************************************************************************
* Copyright (C) 1994-2012 Lua.org, PUC-Rio. All rights reserved.
* Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the

View File

@ -91,7 +91,7 @@
".\\?.lua;" LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" \
LUA_CDIR"?.lua;" LUA_CDIR"?\\init.lua"
#define LUA_CPATH_DEFAULT \
".\\?.dll;" LUA_CDIR"?.dll;" LUA_CDIR"loadall.dll"
".\\?.dll;" ".\\?51.dll;" LUA_CDIR"?.dll;" LUA_CDIR"?51.dll;" LUA_CDIR"clibs\\?.dll;" LUA_CDIR"clibs\\?51.dll;" LUA_CDIR"loadall.dll;" LUA_CDIR"clibs\\loadall.dll"
#else
#define LUA_ROOT "/usr/local/"
@ -101,7 +101,7 @@
"./?.lua;" LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" \
LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua"
#define LUA_CPATH_DEFAULT \
"./?.so;" LUA_CDIR"?.so;" LUA_CDIR"loadall.so"
"./?.so;" "./lib?51.so;" LUA_CDIR"?.so;" LUA_CDIR"lib?51.so;" LUA_CDIR"loadall.so"
#endif

BIN
Utils/luarocks/lua.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,122 @@
--- Module implementing the luarocks-admin "add" command.
-- Adds a rock or rockspec to a rocks server.
local add = {}
package.loaded["luarocks.add"] = add
local cfg = require("luarocks.cfg")
local util = require("luarocks.util")
local dir = require("luarocks.dir")
local manif = require("luarocks.manif")
local index = require("luarocks.index")
local fs = require("luarocks.fs")
local cache = require("luarocks.cache")
util.add_run_function(add)
add.help_summary = "Add a rock or rockspec to a rocks server."
add.help_arguments = "[--server=<server>] [--no-refresh] {<rockspec>|<rock>...}"
add.help = [[
Arguments are local files, which may be rockspecs or rocks.
The flag --server indicates which server to use.
If not given, the default server set in the upload_server variable
from the configuration file is used instead.
The flag --no-refresh indicates the local cache should not be refreshed
prior to generation of the updated manifest.
]]
local function add_files_to_server(refresh, rockfiles, server, upload_server)
assert(type(refresh) == "boolean" or not refresh)
assert(type(rockfiles) == "table")
assert(type(server) == "string")
assert(type(upload_server) == "table" or not upload_server)
local download_url, login_url = cache.get_server_urls(server, upload_server)
local at = fs.current_dir()
local refresh_fn = refresh and cache.refresh_local_cache or cache.split_server_url
local local_cache, protocol, server_path, user, password = refresh_fn(server, download_url, cfg.upload_user, cfg.upload_password)
if not local_cache then
return nil, protocol
end
if protocol == "file" then
return nil, "Server "..server.." is not recognized, check your configuration."
end
if not login_url then
login_url = protocol.."://"..server_path
end
local ok, err = fs.change_dir(at)
if not ok then return nil, err end
local files = {}
for _, rockfile in ipairs(rockfiles) do
if fs.exists(rockfile) then
util.printout("Copying file "..rockfile.." to "..local_cache.."...")
local absolute = fs.absolute_name(rockfile)
fs.copy(absolute, local_cache, cfg.perm_read)
table.insert(files, dir.base_name(absolute))
else
util.printerr("File "..rockfile.." not found")
end
end
if #files == 0 then
return nil, "No files found"
end
local ok, err = fs.change_dir(local_cache)
if not ok then return nil, err end
util.printout("Updating manifest...")
manif.make_manifest(local_cache, "one", true)
manif.zip_manifests()
util.printout("Updating index.html...")
index.make_index(local_cache)
local login_info = ""
if user then login_info = " -u "..user end
if password then login_info = login_info..":"..password end
if not login_url:match("/$") then
login_url = login_url .. "/"
end
table.insert(files, "index.html")
table.insert(files, "manifest")
for ver in util.lua_versions() do
table.insert(files, "manifest-"..ver)
table.insert(files, "manifest-"..ver..".zip")
end
-- TODO abstract away explicit 'curl' call
local cmd
if protocol == "rsync" then
local srv, path = server_path:match("([^/]+)(/.+)")
cmd = cfg.variables.RSYNC.." "..cfg.variables.RSYNCFLAGS.." -e ssh "..local_cache.."/ "..user.."@"..srv..":"..path.."/"
elseif upload_server and upload_server.sftp then
local part1, part2 = upload_server.sftp:match("^([^/]*)/(.*)$")
cmd = cfg.variables.SCP.." "..table.concat(files, " ").." "..user.."@"..part1..":/"..part2
else
cmd = cfg.variables.CURL.." "..login_info.." -T '{"..table.concat(files, ",").."}' "..login_url
end
util.printout(cmd)
fs.execute(cmd)
return true
end
function add.command(flags, ...)
local files = {...}
if #files < 1 then
return nil, "Argument missing. "..util.see_help("add", "luarocks-admin")
end
local server, server_table = cache.get_upload_server(flags["server"])
if not server then return nil, server_table end
return add_files_to_server(not flags["no-refresh"], files, server, server_table)
end
return add

View File

@ -0,0 +1,92 @@
--- Module implementing the luarocks-admin "remove" command.
-- Removes a rock or rockspec from a rocks server.
local admin_remove = {}
package.loaded["luarocks.admin_remove"] = admin_remove
local cfg = require("luarocks.cfg")
local util = require("luarocks.util")
local dir = require("luarocks.dir")
local manif = require("luarocks.manif")
local index = require("luarocks.index")
local fs = require("luarocks.fs")
local cache = require("luarocks.cache")
util.add_run_function(admin_remove)
admin_remove.help_summary = "Remove a rock or rockspec from a rocks server."
admin_remove.help_arguments = "[--server=<server>] [--no-refresh] {<rockspec>|<rock>...}"
admin_remove.help = [[
Arguments are local files, which may be rockspecs or rocks.
The flag --server indicates which server to use.
If not given, the default server set in the upload_server variable
from the configuration file is used instead.
The flag --no-refresh indicates the local cache should not be refreshed
prior to generation of the updated manifest.
]]
local function remove_files_from_server(refresh, rockfiles, server, upload_server)
assert(type(refresh) == "boolean" or not refresh)
assert(type(rockfiles) == "table")
assert(type(server) == "string")
assert(type(upload_server) == "table" or not upload_server)
local download_url, login_url = cache.get_server_urls(server, upload_server)
local at = fs.current_dir()
local refresh_fn = refresh and cache.refresh_local_cache or cache.split_server_url
local local_cache, protocol, server_path, user, password = refresh_fn(server, download_url, cfg.upload_user, cfg.upload_password)
if not local_cache then
return nil, protocol
end
if protocol ~= "rsync" then
return nil, "This command requires 'rsync', check your configuration."
end
local ok, err = fs.change_dir(at)
if not ok then return nil, err end
local nr_files = 0
for _, rockfile in ipairs(rockfiles) do
local basename = dir.base_name(rockfile)
local file = dir.path(local_cache, basename)
util.printout("Removing file "..file.."...")
fs.delete(file)
if not fs.exists(file) then
nr_files = nr_files + 1
else
util.printerr("Failed removing "..file)
end
end
if nr_files == 0 then
return nil, "No files removed."
end
local ok, err = fs.change_dir(local_cache)
if not ok then return nil, err end
util.printout("Updating manifest...")
manif.make_manifest(local_cache, "one", true)
util.printout("Updating index.html...")
index.make_index(local_cache)
local srv, path = server_path:match("([^/]+)(/.+)")
local cmd = cfg.variables.RSYNC.." "..cfg.variables.RSYNCFLAGS.." --delete -e ssh "..local_cache.."/ "..user.."@"..srv..":"..path.."/"
util.printout(cmd)
fs.execute(cmd)
return true
end
function admin_remove.command(flags, ...)
local files = {...}
if #files < 1 then
return nil, "Argument missing. "..util.see_help("remove", "luarocks-admin")
end
local server, server_table = cache.get_upload_server(flags["server"])
if not server then return nil, server_table end
return remove_files_from_server(not flags["no-refresh"], files, server, server_table)
end
return admin_remove

View File

@ -0,0 +1,415 @@
--- Module implementing the LuaRocks "build" command.
-- Builds a rock, compiling its C parts if any.
local build = {}
package.loaded["luarocks.build"] = build
local pack = require("luarocks.pack")
local path = require("luarocks.path")
local util = require("luarocks.util")
local repos = require("luarocks.repos")
local fetch = require("luarocks.fetch")
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local deps = require("luarocks.deps")
local manif = require("luarocks.manif")
local remove = require("luarocks.remove")
local cfg = require("luarocks.cfg")
util.add_run_function(build)
build.help_summary = "Build/compile a rock."
build.help_arguments = "[--pack-binary-rock] [--keep] {<rockspec>|<rock>|<name> [<version>]}"
build.help = [[
Build and install 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.
--pack-binary-rock Do not install rock. Instead, produce a .rock file
with the contents of compilation in the current
directory.
--keep Do not remove previously installed versions of the
rock after building a new one. This behavior can
be made permanent by setting keep_other_versions=true
in the configuration file.
--branch=<name> Override the `source.branch` field in the loaded
rockspec. Allows to specify a different branch to
fetch. Particularly for SCM rocks.
--only-deps Installs only the dependencies of the rock.
]]..util.deps_mode_help()
--- 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, if is_module_path is set, 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.
-- @param is_module_path boolean: True if string keys in files should be
-- interpreted as dotted module paths.
-- @param perms string: Permissions of the newly created files installed.
-- Directories are always created with the default permissions.
-- @return boolean or (nil, string): True if succeeded or
-- nil and an error message.
local function install_files(files, location, is_module_path, perms)
assert(type(files) == "table" or not files)
assert(type(location) == "string")
if files then
for k, file in pairs(files) do
local dest = location
local filename = dir.base_name(file)
if type(k) == "string" then
local modname = k
if is_module_path then
dest = dir.path(location, path.module_to_path(modname))
local ok, err = fs.make_dir(dest)
if not ok then return nil, err end
if filename:match("%.lua$") then
local basename = modname:match("([^.]+)$")
filename = basename..".lua"
end
else
dest = dir.path(location, dir.dir_name(modname))
local ok, err = fs.make_dir(dest)
if not ok then return nil, err end
filename = dir.base_name(modname)
end
else
local ok, err = fs.make_dir(dest)
if not ok then return nil, err end
end
local ok = fs.copy(dir.path(file), dir.path(dest, filename), perms)
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(dir.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 build.apply_patches(rockspec)
assert(type(rockspec) == "table")
local build_spec = rockspec.build
if build_spec.extra_files then
extract_from_rockspec(build_spec.extra_files)
end
if build_spec.patches then
extract_from_rockspec(build_spec.patches)
for patch, patchdata in util.sortedpairs(build_spec.patches) do
util.printout("Applying patch "..patch.."...")
local ok, err = fs.apply_patch(tostring(patch), patchdata)
if not ok then
return nil, "Failed applying patch "..patch
end
end
end
return true
end
local function install_default_docs(name, version)
local patterns = { "readme", "license", "copying", ".*%.md" }
local dest = dir.path(path.install_dir(name, version), "doc")
local has_dir = false
for file in fs.dir() do
for _, pattern in ipairs(patterns) do
if file:lower():match("^"..pattern) then
if not has_dir then
fs.make_dir(dest)
has_dir = true
end
fs.copy(file, dest, cfg.perm_read)
break
end
end
end
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.
-- @param minimal_mode boolean: true if there's no need to fetch,
-- unpack or change dir (this is used by "luarocks make"). Implies
-- need_to_fetch = false.
-- @param deps_mode string: Dependency mode: "one" for the current default tree,
-- "all" for all trees, "order" for all trees with priority >= the current default,
-- "none" for no trees.
-- @param build_only_deps boolean: true to build the listed dependencies only.
-- @return (string, string) or (nil, string, [string]): Name and version of
-- installed rock if succeeded or nil and an error message followed by an error code.
function build.build_rockspec(rockspec_file, need_to_fetch, minimal_mode, deps_mode, build_only_deps)
assert(type(rockspec_file) == "string")
assert(type(need_to_fetch) == "boolean")
local rockspec, err, errcode = fetch.load_rockspec(rockspec_file)
if err then
return nil, err, errcode
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
if not build_only_deps then
ok, err, errcode = deps.check_external_deps(rockspec, "build")
if err then
return nil, err, errcode
end
end
if deps_mode == "none" then
util.printerr("Warning: skipping dependency checks.")
else
local ok, err, errcode = deps.fulfill_dependencies(rockspec, deps_mode)
if err then
return nil, err, errcode
end
end
local name, version = rockspec.name, rockspec.version
if build_only_deps then
util.printout("Stopping after installing dependencies for " ..name.." "..version)
util.printout()
return name, version
end
if repos.is_installed(name, version) then
repos.delete_version(name, version, deps_mode)
end
if not minimal_mode then
local source_dir
if need_to_fetch then
ok, source_dir, errcode = fetch.fetch_sources(rockspec, true)
if not ok then
return nil, source_dir, errcode
end
local ok, err = fs.change_dir(source_dir)
if not ok then return nil, err end
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 = { name = path.lua_dir(name, version), is_module_path = true, perms = cfg.perm_read },
lib = { name = path.lib_dir(name, version), is_module_path = true, perms = cfg.perm_exec },
conf = { name = path.conf_dir(name, version), is_module_path = false, perms = cfg.perm_read },
bin = { name = path.bin_dir(name, version), is_module_path = false, perms = cfg.perm_exec },
}
for _, d in pairs(dirs) do
local ok, err = fs.make_dir(d.name)
if not ok then return nil, err end
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_spec = rockspec.build
if not minimal_mode then
ok, err = build.apply_patches(rockspec)
if err then
return nil, err
end
end
if build_spec.type ~= "none" then
-- Temporary compatibility
if build_spec.type == "module" then
util.printout("Do not use 'module' as a build type. Use 'builtin' instead.")
build_spec.type = "builtin"
end
if cfg.accepted_build_types and util.array_contains(cfg.accepted_build_types, build_spec.type) then
return nil, "This rockspec uses the '"..build_spec.type.."' build type, which is blocked by the 'accepted_build_types' setting in your LuaRocks configuration."
end
local build_type
ok, build_type = pcall(require, "luarocks.build." .. build_spec.type)
if not ok or not type(build_type) == "table" then
return nil, "Failed initializing build back-end for build type '"..build_spec.type.."': "..build_type
end
ok, err = build_type.run(rockspec)
if not ok then
return nil, "Build error: " .. err
end
end
if build_spec.install then
for id, install_dir in pairs(dirs) do
ok, err = install_files(build_spec.install[id], install_dir.name, install_dir.is_module_path, install_dir.perms)
if not ok then
return nil, err
end
end
end
local copy_directories = build_spec.copy_directories
local copying_default = false
if not copy_directories then
copy_directories = {"doc"}
copying_default = true
end
local any_docs = false
for _, copy_dir in pairs(copy_directories) do
if fs.is_dir(copy_dir) then
local dest = dir.path(path.install_dir(name, version), copy_dir)
fs.make_dir(dest)
fs.copy_contents(copy_dir, dest)
any_docs = true
else
if not copying_default then
return nil, "Directory '"..copy_dir.."' not found"
end
end
end
if not any_docs then
install_default_docs(name, version)
end
for _, d in pairs(dirs) do
fs.remove_dir_if_empty(d.name)
end
fs.pop_dir()
fs.copy(rockspec.local_filename, path.rockspec_file(name, version), cfg.perm_read)
if need_to_fetch then
fs.pop_dir()
end
ok, err = manif.make_rock_manifest(name, version)
if err then return nil, err end
ok, err = repos.deploy_files(name, version, repos.should_wrap_bin_scripts(rockspec), deps_mode)
if err then return nil, err end
util.remove_scheduled_function(rollback)
rollback = util.schedule_function(function()
repos.delete_version(name, version, deps_mode)
end)
ok, err = repos.run_hook(rockspec, "post_install")
if err then return nil, err end
util.announce_install(rockspec)
util.remove_scheduled_function(rollback)
return name, version
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.
-- @param deps_mode: string: Which trees to check dependencies for:
-- "one" for the current default tree, "all" for all trees,
-- "order" for all trees with priority >= the current default, "none" for no trees.
-- @param build_only_deps boolean: true to build the listed dependencies only.
-- @return boolean or (nil, string, [string]): True if build was successful,
-- or false and an error message and an optional error code.
function build.build_rock(rock_file, need_to_fetch, deps_mode, build_only_deps)
assert(type(rock_file) == "string")
assert(type(need_to_fetch) == "boolean")
local ok, err, errcode
local unpack_dir
unpack_dir, err, errcode = fetch.fetch_and_unpack_rock(rock_file)
if not unpack_dir then
return nil, err, errcode
end
local rockspec_file = path.rockspec_name_from_rock(rock_file)
ok, err = fs.change_dir(unpack_dir)
if not ok then return nil, err end
ok, err, errcode = build.build_rockspec(rockspec_file, need_to_fetch, false, deps_mode, build_only_deps)
fs.pop_dir()
return ok, err, errcode
end
local function do_build(name, version, deps_mode, build_only_deps)
if name:match("%.rockspec$") then
return build.build_rockspec(name, true, false, deps_mode, build_only_deps)
elseif name:match("%.src%.rock$") then
return build.build_rock(name, false, deps_mode, build_only_deps)
elseif name:match("%.all%.rock$") then
local install = require("luarocks.install")
local install_fun = build_only_deps and install.install_binary_rock_deps or install.install_binary_rock
return install_fun(name, deps_mode)
elseif name:match("%.rock$") then
return build.build_rock(name, true, deps_mode, build_only_deps)
elseif not name:match(dir.separator) then
local search = require("luarocks.search")
return search.act_on_src_or_rockspec(do_build, name:lower(), version, nil, deps_mode, build_only_deps)
end
return nil, "Don't know what to do with "..name
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, exitcode): True if build was successful; nil and an
-- error message otherwise. exitcode is optionally returned.
function build.command(flags, name, version)
if type(name) ~= "string" then
return nil, "Argument missing. "..util.see_help("build")
end
assert(type(version) == "string" or not version)
if flags["pack-binary-rock"] then
return pack.pack_binary_rock(name, version, do_build, name, version, deps.get_deps_mode(flags))
else
local ok, err = fs.check_command_permissions(flags)
if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end
ok, err = do_build(name, version, deps.get_deps_mode(flags), flags["only-deps"])
if not ok then return nil, err end
name, version = ok, err
if (not flags["only-deps"]) and (not flags["keep"]) and not cfg.keep_other_versions then
local ok, err = remove.remove_other_versions(name, version, flags["force"], flags["force-fast"])
if not ok then util.printerr(err) end
end
manif.check_dependencies(nil, deps.get_deps_mode(flags))
return name, version
end
end
return build

View File

@ -0,0 +1,78 @@
--- Module handling the LuaRocks local cache.
-- Adds a rock or rockspec to a rocks server.
local cache = {}
package.loaded["luarocks.cache"] = cache
local fs = require("luarocks.fs")
local cfg = require("luarocks.cfg")
local dir = require("luarocks.dir")
local util = require("luarocks.util")
function cache.get_upload_server(server)
if not server then server = cfg.upload_server end
if not server then
return nil, "No server specified and no default configured with upload_server."
end
return server, cfg.upload_servers and cfg.upload_servers[server]
end
function cache.get_server_urls(server, upload_server)
local download_url = server
local login_url = nil
if upload_server then
if upload_server.rsync then download_url = "rsync://"..upload_server.rsync
elseif upload_server.http then download_url = "http://"..upload_server.http
elseif upload_server.ftp then download_url = "ftp://"..upload_server.ftp
end
if upload_server.ftp then login_url = "ftp://"..upload_server.ftp
elseif upload_server.sftp then login_url = "sftp://"..upload_server.sftp
end
end
return download_url, login_url
end
function cache.split_server_url(server, url, user, password)
local protocol, server_path = dir.split_url(url)
if server_path:match("@") then
local credentials
credentials, server_path = server_path:match("([^@]*)@(.*)")
if credentials:match(":") then
user, password = credentials:match("([^:]*):(.*)")
else
user = credentials
end
end
local local_cache = cfg.local_cache .. "/" .. server
return local_cache, protocol, server_path, user, password
end
function cache.refresh_local_cache(server, url, user, password)
local local_cache, protocol, server_path, user, password = cache.split_server_url(server, url, user, password)
local ok, err = fs.make_dir(local_cache)
if not ok then
return nil, "Failed creating local cache dir: "..err
end
fs.change_dir(local_cache)
if not ok then return nil, err end
util.printout("Refreshing cache "..local_cache.."...")
-- TODO abstract away explicit 'wget' call
local ok = false
if protocol == "rsync" then
local srv, path = server_path:match("([^/]+)(/.+)")
ok = fs.execute(cfg.variables.RSYNC.." "..cfg.variables.RSYNCFLAGS.." -e ssh "..user.."@"..srv..":"..path.."/ "..local_cache.."/")
else
local login_info = ""
if user then login_info = " --user="..user end
if password then login_info = login_info .. " --password="..password end
ok = fs.execute(cfg.variables.WGET.." --no-cache -q -m -np -nd "..protocol.."://"..server_path..login_info)
end
if not ok then
return nil, "Failed downloading cache."
end
return local_cache, protocol, server_path, user, password
end
return cache

View File

@ -0,0 +1,760 @@
--- Configuration for LuaRocks.
-- Tries to load the user's configuration file and
-- defines defaults for unset values. See the
-- <a href="http://luarocks.org/en/Config_file_format">config
-- file format documentation</a> for details.
--
-- End-users shouldn't edit this file. They can override any defaults
-- set in this file using their system-wide or user-specific configuration
-- files. Run `luarocks` with no arguments to see the locations of
-- these files in your platform.
local rawset, next, table, pairs, require, io, os, setmetatable, pcall, ipairs, package, tonumber, type, assert, _VERSION =
rawset, next, table, pairs, require, io, os, setmetatable, pcall, ipairs, package, tonumber, type, assert, _VERSION
--module("luarocks.cfg")
local cfg = {}
package.loaded["luarocks.cfg"] = cfg
local util = require("luarocks.util")
cfg.lua_version = _VERSION:match(" (5%.[123])$") or "5.1"
local version_suffix = cfg.lua_version:gsub("%.", "_")
-- Load site-local global configurations
local ok, site_config = pcall(require, "luarocks.site_config_"..version_suffix)
if not ok then
ok, site_config = pcall(require, "luarocks.site_config")
end
if not ok then
io.stderr:write("Site-local luarocks/site_config.lua file not found. Incomplete installation?\n")
site_config = {}
end
cfg.program_version = "2.4.2"
cfg.program_series = "2.4"
cfg.major_version = (cfg.program_version:match("([^.]%.[^.])")) or cfg.program_series
cfg.variables = {}
cfg.rocks_trees = {}
cfg.platforms = {}
local persist = require("luarocks.persist")
cfg.errorcodes = setmetatable({
OK = 0,
UNSPECIFIED = 1,
PERMISSIONDENIED = 2,
CONFIGFILE = 3,
CRASH = 99
},{
__index = function(t, key)
local val = rawget(t, key)
if not val then
error("'"..tostring(key).."' is not a valid errorcode", 2)
end
return val
end
})
local popen_ok, popen_result = pcall(io.popen, "")
if popen_ok then
if popen_result then
popen_result:close()
end
else
io.stderr:write("Your version of Lua does not support io.popen,\n")
io.stderr:write("which is required by LuaRocks. Please check your Lua installation.\n")
os.exit(cfg.errorcodes.UNSPECIFIED)
end
-- System detection:
-- A proper installation of LuaRocks will hardcode the system
-- and proc values with site_config.LUAROCKS_UNAME_S and site_config.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).
local system = site_config.LUAROCKS_UNAME_S or io.popen("uname -s"):read("*l")
local proc = site_config.LUAROCKS_UNAME_M or io.popen("uname -m"):read("*l")
if proc:match("i[%d]86") then
cfg.target_cpu = "x86"
elseif proc:match("amd64") or proc:match("x86_64") then
cfg.target_cpu = "x86_64"
elseif proc:match("Power Macintosh") then
cfg.target_cpu = "powerpc"
else
cfg.target_cpu = proc
end
if system == "FreeBSD" then
cfg.platforms.unix = true
cfg.platforms.freebsd = true
cfg.platforms.bsd = true
elseif system == "OpenBSD" then
cfg.platforms.unix = true
cfg.platforms.openbsd = true
cfg.platforms.bsd = true
elseif system == "NetBSD" then
cfg.platforms.unix = true
cfg.platforms.netbsd = true
cfg.platforms.bsd = true
elseif system == "Darwin" then
cfg.platforms.unix = true
cfg.platforms.macosx = true
cfg.platforms.bsd = true
elseif system == "Linux" then
cfg.platforms.unix = true
cfg.platforms.linux = true
elseif system == "SunOS" then
cfg.platforms.unix = true
cfg.platforms.solaris = true
elseif system and system:match("^CYGWIN") then
cfg.platforms.unix = true
cfg.platforms.cygwin = true
elseif system and system:match("^MSYS") then
cfg.platforms.unix = true
cfg.platforms.msys = true
cfg.platforms.cygwin = true
elseif system and system:match("^Windows") then
cfg.platforms.windows = true
cfg.platforms.win32 = true
elseif system and system:match("^MINGW") then
cfg.platforms.windows = true
cfg.platforms.mingw32 = true
cfg.platforms.win32 = true
elseif system == "Haiku" then
cfg.platforms.unix = true
cfg.platforms.haiku = true
else
cfg.platforms.unix = true
-- Fall back to Unix in unknown systems.
end
-- Set order for platform overrides.
-- More general platform identifiers should be listed first,
-- more specific ones later.
local platform_order = {
-- Unixes
"unix",
"bsd",
"solaris",
"netbsd",
"openbsd",
"freebsd",
"linux",
"macosx",
"cygwin",
"msys",
"haiku",
-- Windows
"win32",
"mingw32",
"windows",
}
-- Path configuration:
local sys_config_file, home_config_file
local sys_config_file_default, home_config_file_default
local sys_config_dir, home_config_dir
local sys_config_ok, home_config_ok = false, false
local extra_luarocks_module_dir
sys_config_dir = site_config.LUAROCKS_SYSCONFDIR or site_config.LUAROCKS_PREFIX
if cfg.platforms.windows then
cfg.home = os.getenv("APPDATA") or "c:"
sys_config_dir = sys_config_dir or "c:/luarocks"
home_config_dir = cfg.home.."/luarocks"
cfg.home_tree = cfg.home.."/luarocks/"
else
cfg.home = os.getenv("HOME") or ""
sys_config_dir = sys_config_dir or "/etc/luarocks"
home_config_dir = cfg.home.."/.luarocks"
cfg.home_tree = (os.getenv("USER") ~= "root") and cfg.home.."/.luarocks/"
end
-- Create global environment for the config files;
local env_for_config_file = function()
local e
e = {
home = cfg.home,
lua_version = cfg.lua_version,
platforms = util.make_shallow_copy(cfg.platforms),
processor = cfg.target_cpu, -- remains for compat reasons
target_cpu = cfg.target_cpu, -- replaces `processor`
os_getenv = os.getenv,
dump_env = function()
-- debug function, calling it from a config file will show all
-- available globals to that config file
print(util.show_table(e, "global environment"))
end,
}
return e
end
-- Merge values from config files read into the `cfg` table
local merge_overrides = function(overrides)
-- remove some stuff we do not want to integrate
overrides.os_getenv = nil
overrides.dump_env = nil
-- remove tables to be copied verbatim instead of deeply merged
if overrides.rocks_trees then cfg.rocks_trees = nil end
if overrides.rocks_servers then cfg.rocks_servers = nil end
-- perform actual merge
util.deep_merge(cfg, overrides)
end
-- load config file from a list until first succesful one. Info is
-- added to `cfg` module table, returns filepath of succesfully loaded
-- file or nil if it failed
local load_config_file = function(list)
for _, filepath in ipairs(list) do
local result, err, errcode = persist.load_into_table(filepath, env_for_config_file())
if (not result) and errcode ~= "open" then
-- errcode is either "load" or "run"; bad config file, so error out
io.stderr:write(err.."\n")
os.exit(cfg.errorcodes.CONFIGFILE)
end
if result then
-- succes in loading and running, merge contents and exit
merge_overrides(result)
return filepath
end
end
return nil -- nothing was loaded
end
-- Load system configuration file
do
sys_config_file_default = sys_config_dir.."/config-"..cfg.lua_version..".lua"
sys_config_file = load_config_file({
site_config.LUAROCKS_SYSCONFIG or sys_config_file_default,
sys_config_dir.."/config.lua",
})
sys_config_ok = (sys_config_file ~= nil)
end
-- Load user configuration file (if allowed)
if not site_config.LUAROCKS_FORCE_CONFIG then
home_config_file_default = home_config_dir.."/config-"..cfg.lua_version..".lua"
local config_env_var = "LUAROCKS_CONFIG_" .. version_suffix
local config_env_value = os.getenv(config_env_var)
if not config_env_value then
config_env_var = "LUAROCKS_CONFIG"
config_env_value = os.getenv(config_env_var)
end
-- first try environment provided file, so we can explicitly warn when it is missing
if config_env_value then
local list = { config_env_value }
home_config_file = load_config_file(list)
home_config_ok = (home_config_file ~= nil)
if not home_config_ok then
io.stderr:write("Warning: could not load configuration file `"..config_env_value.."` given in environment variable "..config_env_var.."\n")
end
end
-- try the alternative defaults if there was no environment specified file or it didn't work
if not home_config_ok then
local list = {
home_config_file_default,
home_config_dir.."/config.lua",
}
home_config_file = load_config_file(list)
home_config_ok = (home_config_file ~= nil)
end
end
if not next(cfg.rocks_trees) then
if cfg.home_tree then
table.insert(cfg.rocks_trees, { name = "user", root = cfg.home_tree } )
end
if site_config.LUAROCKS_ROCKS_TREE then
table.insert(cfg.rocks_trees, { name = "system", root = site_config.LUAROCKS_ROCKS_TREE } )
end
end
-- update platforms list; keyed -> array
do
-- if explicitly given by user,
if cfg.platforms[1] then
local is_windows = cfg.platforms.windows
-- Clear auto-detected values
for k, _ in pairs(cfg.platforms) do
if type(k) == "string" then
cfg.platforms[k] = nil
end
end
-- and set the ones given by the user.
for _, plat in ipairs(cfg.platforms) do
cfg.platforms[plat] = true
end
-- If no major platform family was set by the user,
if not (cfg.platforms.unix or cfg.platforms.windows) then
-- set some fallback defaults in case the user provides an incomplete configuration.
-- LuaRocks expects a set of defaults to be available.
-- This is used for setting defaults here only; the platform overrides
-- will use only the user's list.
if is_windows then
cfg.platforms.windows = true
table.insert(cfg.platforms, "windows")
else
cfg.platforms.unix = true
table.insert(cfg.platforms, "unix")
end
end
else
-- Sort detected platform defaults
local order = {}
for i, v in ipairs(platform_order) do
order[v] = i
end
local entries = {}
for k, v in pairs(cfg.platforms) do
if type(k) == "string" and v == true then
table.insert(entries, k)
end
end
table.sort(entries, function(a, b) return order[a] < order[b] end)
util.deep_merge(cfg.platforms, entries)
end
end
-- Configure defaults:
local defaults = {
local_by_default = false,
accept_unknown_fields = false,
fs_use_modules = true,
hooks_enabled = true,
deps_mode = "one",
check_certificates = false,
perm_read = "0644",
perm_exec = "0755",
lua_modules_path = "/share/lua/"..cfg.lua_version,
lib_modules_path = "/lib/lua/"..cfg.lua_version,
rocks_subdir = site_config.LUAROCKS_ROCKS_SUBDIR or "/lib/luarocks/rocks",
arch = "unknown",
lib_extension = "unknown",
obj_extension = "unknown",
link_lua_explicitly = false,
rocks_servers = {
{
"https://luarocks.org",
"https://raw.githubusercontent.com/rocks-moonscript-org/moonrocks-mirror/master/",
"http://luafr.org/moonrocks/",
"http://luarocks.logiceditor.com/rocks",
}
},
disabled_servers = {},
upload = {
server = "https://luarocks.org",
tool_version = "1.0.0",
api_version = "1",
},
lua_extension = "lua",
lua_interpreter = site_config.LUA_INTERPRETER or "lua",
downloader = site_config.LUAROCKS_DOWNLOADER or "wget",
md5checker = site_config.LUAROCKS_MD5CHECKER or "md5sum",
connection_timeout = 30, -- 0 = no timeout
variables = {
MAKE = "make",
CC = "cc",
LD = "ld",
CVS = "cvs",
GIT = "git",
SSCM = "sscm",
SVN = "svn",
HG = "hg",
RSYNC = "rsync",
WGET = "wget",
SCP = "scp",
CURL = "curl",
PWD = "pwd",
MKDIR = "mkdir",
RMDIR = "rmdir",
CP = "cp",
LS = "ls",
RM = "rm",
FIND = "find",
TEST = "test",
CHMOD = "chmod",
MKTEMP = "mktemp",
ZIP = "zip",
UNZIP = "unzip -n",
GUNZIP = "gunzip",
BUNZIP2 = "bunzip2",
TAR = "tar",
MD5SUM = "md5sum",
OPENSSL = "openssl",
MD5 = "md5",
STAT = "stat",
TOUCH = "touch",
CMAKE = "cmake",
SEVENZ = "7z",
RSYNCFLAGS = "--exclude=.git -Oavz",
STATFLAG = "-c '%a'",
CURLNOCERTFLAG = "",
WGETNOCERTFLAG = "",
},
external_deps_subdirs = site_config.LUAROCKS_EXTERNAL_DEPS_SUBDIRS or {
bin = "bin",
lib = "lib",
include = "include"
},
runtime_external_deps_subdirs = site_config.LUAROCKS_RUNTIME_EXTERNAL_DEPS_SUBDIRS or {
bin = "bin",
lib = "lib",
include = "include"
},
rocks_provided = {}
}
if cfg.platforms.windows then
local full_prefix = (site_config.LUAROCKS_PREFIX or (os.getenv("PROGRAMFILES")..[[\LuaRocks]]))
extra_luarocks_module_dir = full_prefix.."/lua/?.lua"
home_config_file = home_config_file and home_config_file:gsub("\\","/")
defaults.fs_use_modules = false
defaults.arch = "win32-"..cfg.target_cpu
defaults.lib_extension = "dll"
defaults.external_lib_extension = "dll"
defaults.obj_extension = "obj"
defaults.external_deps_dirs = { "c:/external/" }
defaults.variables.LUA_BINDIR = site_config.LUA_BINDIR and site_config.LUA_BINDIR:gsub("\\", "/") or "c:/lua"..cfg.lua_version.."/bin"
defaults.variables.LUA_INCDIR = site_config.LUA_INCDIR and site_config.LUA_INCDIR:gsub("\\", "/") or "c:/lua"..cfg.lua_version.."/include"
defaults.variables.LUA_LIBDIR = site_config.LUA_LIBDIR and site_config.LUA_LIBDIR:gsub("\\", "/") or "c:/lua"..cfg.lua_version.."/lib"
defaults.makefile = "Makefile.win"
defaults.variables.MAKE = "nmake"
defaults.variables.CC = "cl"
defaults.variables.RC = "rc"
defaults.variables.WRAPPER = full_prefix.."\\rclauncher.c"
defaults.variables.LD = "link"
defaults.variables.MT = "mt"
defaults.variables.LUALIB = "lua"..cfg.lua_version..".lib"
defaults.variables.CFLAGS = "/nologo /MD /O2"
defaults.variables.LIBFLAG = "/nologo /dll"
local bins = { "SEVENZ", "CP", "FIND", "LS", "MD5SUM",
"MKDIR", "MV", "PWD", "RMDIR", "TEST", "UNAME", "WGET" }
for _, var in ipairs(bins) do
if defaults.variables[var] then
defaults.variables[var] = full_prefix.."\\tools\\"..defaults.variables[var]
end
end
defaults.external_deps_patterns = {
bin = { "?.exe", "?.bat" },
lib = { "?.lib", "?.dll", "lib?.dll" },
include = { "?.h" }
}
defaults.runtime_external_deps_patterns = {
bin = { "?.exe", "?.bat" },
lib = { "?.dll", "lib?.dll" },
include = { "?.h" }
}
defaults.export_path = "SET PATH=%s"
defaults.export_path_separator = ";"
defaults.export_lua_path = "SET LUA_PATH=%s"
defaults.export_lua_cpath = "SET LUA_CPATH=%s"
defaults.wrapper_suffix = ".bat"
local localappdata = os.getenv("LOCALAPPDATA")
if not localappdata then
-- for Windows versions below Vista
localappdata = os.getenv("USERPROFILE").."/Local Settings/Application Data"
end
defaults.local_cache = localappdata.."/LuaRocks/Cache"
defaults.web_browser = "start"
end
if cfg.platforms.mingw32 then
defaults.obj_extension = "o"
defaults.cmake_generator = "MinGW Makefiles"
defaults.variables.MAKE = "mingw32-make"
defaults.variables.CC = "mingw32-gcc"
defaults.variables.RC = "windres"
defaults.variables.LD = "mingw32-gcc"
defaults.variables.CFLAGS = "-O2"
defaults.variables.LIBFLAG = "-shared"
defaults.makefile = "Makefile"
defaults.external_deps_patterns = {
bin = { "?.exe", "?.bat" },
-- mingw lookup list from http://stackoverflow.com/a/15853231/1793220
-- ...should we keep ?.lib at the end? It's not in the above list.
lib = { "lib?.dll.a", "?.dll.a", "lib?.a", "cyg?.dll", "lib?.dll", "?.dll", "?.lib" },
include = { "?.h" }
}
defaults.runtime_external_deps_patterns = {
bin = { "?.exe", "?.bat" },
lib = { "cyg?.dll", "?.dll", "lib?.dll" },
include = { "?.h" }
}
end
if cfg.platforms.unix then
defaults.lib_extension = "so"
defaults.external_lib_extension = "so"
defaults.obj_extension = "o"
defaults.external_deps_dirs = { "/usr/local", "/usr" }
defaults.variables.LUA_BINDIR = site_config.LUA_BINDIR or "/usr/local/bin"
defaults.variables.LUA_INCDIR = site_config.LUA_INCDIR or "/usr/local/include"
defaults.variables.LUA_LIBDIR = site_config.LUA_LIBDIR or "/usr/local/lib"
defaults.variables.CFLAGS = "-O2"
defaults.cmake_generator = "Unix Makefiles"
defaults.variables.CC = "gcc"
defaults.variables.LD = "gcc"
defaults.gcc_rpath = true
defaults.variables.LIBFLAG = "-shared"
defaults.external_deps_patterns = {
bin = { "?" },
lib = { "lib?.a", "lib?.so", "lib?.so.*" },
include = { "?.h" }
}
defaults.runtime_external_deps_patterns = {
bin = { "?" },
lib = { "lib?.so", "lib?.so.*" },
include = { "?.h" }
}
defaults.export_path = "export PATH='%s'"
defaults.export_path_separator = ":"
defaults.export_lua_path = "export LUA_PATH='%s'"
defaults.export_lua_cpath = "export LUA_CPATH='%s'"
defaults.wrapper_suffix = ""
defaults.local_cache = cfg.home.."/.cache/luarocks"
if not defaults.variables.CFLAGS:match("-fPIC") then
defaults.variables.CFLAGS = defaults.variables.CFLAGS.." -fPIC"
end
defaults.web_browser = "xdg-open"
end
if cfg.platforms.cygwin then
defaults.lib_extension = "so" -- can be overridden in the config file for mingw builds
defaults.arch = "cygwin-"..cfg.target_cpu
defaults.cmake_generator = "Unix Makefiles"
defaults.variables.CC = "echo -llua | xargs gcc"
defaults.variables.LD = "echo -llua | xargs gcc"
defaults.variables.LIBFLAG = "-shared"
defaults.link_lua_explicitly = true
end
if cfg.platforms.msys then
-- msys is basically cygwin made out of mingw, meaning the subsytem is unixish
-- enough, yet we can freely mix with native win32
defaults.external_deps_patterns = {
bin = { "?.exe", "?.bat", "?" },
lib = { "lib?.so", "lib?.so.*", "lib?.dll.a", "?.dll.a",
"lib?.a", "lib?.dll", "?.dll", "?.lib" },
include = { "?.h" }
}
defaults.runtime_external_deps_patterns = {
bin = { "?.exe", "?.bat" },
lib = { "lib?.so", "?.dll", "lib?.dll" },
include = { "?.h" }
}
end
if cfg.platforms.bsd then
defaults.variables.MAKE = "gmake"
defaults.variables.STATFLAG = "-f '%OLp'"
end
if cfg.platforms.macosx then
defaults.variables.MAKE = "make"
defaults.external_lib_extension = "dylib"
defaults.arch = "macosx-"..cfg.target_cpu
defaults.variables.LIBFLAG = "-bundle -undefined dynamic_lookup -all_load"
defaults.variables.STAT = "/usr/bin/stat"
defaults.variables.STATFLAG = "-f '%A'"
local version = io.popen("sw_vers -productVersion"):read("*l")
version = tonumber(version and version:match("^[^.]+%.([^.]+)")) or 3
if version >= 10 then
version = 8
elseif version >= 5 then
version = 5
else
defaults.gcc_rpath = false
end
defaults.variables.CC = "env MACOSX_DEPLOYMENT_TARGET=10."..version.." gcc"
defaults.variables.LD = "env MACOSX_DEPLOYMENT_TARGET=10."..version.." gcc"
defaults.web_browser = "open"
end
if cfg.platforms.linux then
defaults.arch = "linux-"..cfg.target_cpu
end
if cfg.platforms.freebsd then
defaults.arch = "freebsd-"..cfg.target_cpu
defaults.gcc_rpath = false
defaults.variables.CC = "cc"
defaults.variables.LD = "cc"
end
if cfg.platforms.openbsd then
defaults.arch = "openbsd-"..cfg.target_cpu
end
if cfg.platforms.netbsd then
defaults.arch = "netbsd-"..cfg.target_cpu
end
if cfg.platforms.solaris then
defaults.arch = "solaris-"..cfg.target_cpu
--defaults.platforms = {"unix", "solaris"}
defaults.variables.MAKE = "gmake"
end
-- Expose some more values detected by LuaRocks for use by rockspec authors.
defaults.variables.LIB_EXTENSION = defaults.lib_extension
defaults.variables.OBJ_EXTENSION = defaults.obj_extension
defaults.variables.LUAROCKS_PREFIX = site_config.LUAROCKS_PREFIX
defaults.variables.LUA = site_config.LUA_DIR_SET and (defaults.variables.LUA_BINDIR.."/"..defaults.lua_interpreter) or defaults.lua_interpreter
-- Add built-in modules to rocks_provided
defaults.rocks_provided["lua"] = cfg.lua_version.."-1"
if bit32 then -- Lua 5.2+
defaults.rocks_provided["bit32"] = cfg.lua_version.."-1"
end
if utf8 then -- Lua 5.3+
defaults.rocks_provided["utf8"] = cfg.lua_version.."-1"
end
if package.loaded.jit then
-- LuaJIT
local lj_version = package.loaded.jit.version:match("LuaJIT (.*)"):gsub("%-","")
--defaults.rocks_provided["luajit"] = lj_version.."-1"
defaults.rocks_provided["luabitop"] = lj_version.."-1"
end
-- Use defaults:
-- Populate some arrays with values from their 'defaults' counterparts
-- if they were not already set by user.
for _, entry in ipairs({"variables", "rocks_provided"}) do
if not cfg[entry] then
cfg[entry] = {}
end
for k,v in pairs(defaults[entry]) do
if not cfg[entry][k] then
cfg[entry][k] = v
end
end
end
-- For values not set in the config file, use values from the 'defaults' table.
local cfg_mt = {
__index = function(t, k)
local default = defaults[k]
if default then
rawset(t, k, default)
end
return default
end
}
setmetatable(cfg, cfg_mt)
if not cfg.check_certificates then
cfg.variables.CURLNOCERTFLAG = "-k"
cfg.variables.WGETNOCERTFLAG = "--no-check-certificate"
end
function cfg.make_paths_from_tree(tree)
local lua_path, lib_path, bin_path
if type(tree) == "string" then
lua_path = tree..cfg.lua_modules_path
lib_path = tree..cfg.lib_modules_path
bin_path = tree.."/bin"
else
lua_path = tree.lua_dir or tree.root..cfg.lua_modules_path
lib_path = tree.lib_dir or tree.root..cfg.lib_modules_path
bin_path = tree.bin_dir or tree.root.."/bin"
end
return lua_path, lib_path, bin_path
end
function cfg.package_paths(current)
local new_path, new_cpath, new_bin = {}, {}, {}
local function add_tree_to_paths(tree)
local lua_path, lib_path, bin_path = cfg.make_paths_from_tree(tree)
table.insert(new_path, lua_path.."/?.lua")
table.insert(new_path, lua_path.."/?/init.lua")
table.insert(new_cpath, lib_path.."/?."..cfg.lib_extension)
table.insert(new_bin, bin_path)
end
if current then
add_tree_to_paths(current)
end
for _,tree in ipairs(cfg.rocks_trees) do
add_tree_to_paths(tree)
end
if extra_luarocks_module_dir then
table.insert(new_path, extra_luarocks_module_dir)
end
return table.concat(new_path, ";"), table.concat(new_cpath, ";"), table.concat(new_bin, cfg.export_path_separator)
end
function cfg.init_package_paths()
local lr_path, lr_cpath, lr_bin = cfg.package_paths()
package.path = util.remove_path_dupes(package.path .. ";" .. lr_path, ";")
package.cpath = util.remove_path_dupes(package.cpath .. ";" .. lr_cpath, ";")
end
function cfg.which_config()
local ret = {
system = {
file = sys_config_file or sys_config_file_default,
ok = sys_config_ok,
},
user = {
file = home_config_file or home_config_file_default,
ok = home_config_ok,
}
}
ret.nearest = (ret.user.ok and ret.user.file) or ret.system.file
return ret
end
cfg.user_agent = "LuaRocks/"..cfg.program_version.." "..cfg.arch
cfg.http_proxy = os.getenv("http_proxy")
cfg.https_proxy = os.getenv("https_proxy")
cfg.no_proxy = os.getenv("no_proxy")
--- Check if platform was detected
-- @param query string: The platform name to check.
-- @return boolean: true if LuaRocks is currently running on queried platform.
function cfg.is_platform(query)
assert(type(query) == "string")
for _, platform in ipairs(cfg.platforms) do
if platform == query then
return true
end
end
end
return cfg

View File

@ -0,0 +1,199 @@
--- Functions for command-line scripts.
local command_line = {}
local unpack = unpack or table.unpack
local util = require("luarocks.util")
local cfg = require("luarocks.cfg")
local path = require("luarocks.path")
local dir = require("luarocks.dir")
local deps = require("luarocks.deps")
local fs = require("luarocks.fs")
local program = util.this_program("luarocks")
local function error_handler(err)
return debug.traceback("LuaRocks "..cfg.program_version..
" bug (please report at https://github.com/keplerproject/luarocks/issues).\n"..err, 2)
end
--- Display an error message and exit.
-- @param message string: The error message.
-- @param exitcode number: the exitcode to use
local function die(message, exitcode)
assert(type(message) == "string")
util.printerr("\nError: "..message)
local ok, err = xpcall(util.run_scheduled_functions, error_handler)
if not ok then
util.printerr("\nError: "..err)
exitcode = cfg.errorcodes.CRASH
end
os.exit(exitcode or cfg.errorcodes.UNSPECIFIED)
end
local function replace_tree(flags, tree)
tree = dir.normalize(tree)
flags["tree"] = tree
path.use_tree(tree)
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 command_line.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.ERROR then
die(flags.ERROR.." See --help.")
end
if flags["from"] then flags["server"] = flags["from"] end
if flags["only-from"] then flags["only-server"] = flags["only-from"] end
if flags["only-sources-from"] then flags["only-sources"] = flags["only-sources-from"] end
if flags["to"] then flags["tree"] = flags["to"] end
if flags["nodeps"] then
flags["deps-mode"] = "none"
end
cfg.flags = flags
local command
if flags["verbose"] then -- setting it in the config file will kick-in earlier in the process
cfg.verbose = true
fs.verbose()
end
if flags["timeout"] then -- setting it in the config file will kick-in earlier in the process
local timeout = tonumber(flags["timeout"])
if timeout then
cfg.connection_timeout = timeout
else
die "Argument error: --timeout expects a numeric argument."
end
end
if flags["version"] then
util.printout(program.." "..cfg.program_version)
util.printout(program_description)
util.printout()
os.exit(cfg.errorcodes.OK)
elseif flags["help"] or #nonflags == 0 then
command = "help"
else
command = table.remove(nonflags, 1)
end
command = command:gsub("-", "_")
if cfg.local_by_default then
flags["local"] = true
end
if flags["deps-mode"] and not deps.check_deps_mode_flag(flags["deps-mode"]) then
die("Invalid entry for --deps-mode.")
end
if flags["branch"] then
cfg.branch = flags["branch"]
end
if flags["tree"] then
local named = false
for _, tree in ipairs(cfg.rocks_trees) do
if type(tree) == "table" and flags["tree"] == tree.name then
if not tree.root then
die("Configuration error: tree '"..tree.name.."' has no 'root' field.")
end
replace_tree(flags, tree.root)
named = true
break
end
end
if not named then
local root_dir = fs.absolute_name(flags["tree"])
replace_tree(flags, root_dir)
end
elseif flags["local"] then
if not cfg.home_tree then
die("The --local flag is meant for operating in a user's home directory.\n"..
"You are running as a superuser, which is intended for system-wide operation.\n"..
"To force using the superuser's home, use --tree explicitly.")
end
replace_tree(flags, cfg.home_tree)
else
local trees = cfg.rocks_trees
path.use_tree(trees[#trees])
end
if type(cfg.root_dir) == "string" then
cfg.root_dir = cfg.root_dir:gsub("/+$", "")
else
cfg.root_dir.root = cfg.root_dir.root:gsub("/+$", "")
end
cfg.rocks_dir = cfg.rocks_dir:gsub("/+$", "")
cfg.deploy_bin_dir = cfg.deploy_bin_dir:gsub("/+$", "")
cfg.deploy_lua_dir = cfg.deploy_lua_dir:gsub("/+$", "")
cfg.deploy_lib_dir = cfg.deploy_lib_dir:gsub("/+$", "")
cfg.variables.ROCKS_TREE = cfg.rocks_dir
cfg.variables.SCRIPTS_DIR = cfg.deploy_bin_dir
if flags["server"] then
local protocol, path = dir.split_url(flags["server"])
table.insert(cfg.rocks_servers, 1, protocol.."://"..path)
end
if flags["only-server"] then
cfg.rocks_servers = { flags["only-server"] }
end
if flags["only-sources"] then
cfg.only_sources_from = flags["only-sources"]
end
if command ~= "help" then
for k, v in pairs(cmdline_vars) do
cfg.variables[k] = v
end
end
if not fs.current_dir() or fs.current_dir() == "" then
die("Current directory does not exist. Please run LuaRocks from an existing directory.")
end
if commands[command] then
local cmd = require(commands[command])
local call_ok, ok, err, exitcode = xpcall(function() return cmd.command(flags, unpack(nonflags)) end, error_handler)
if not call_ok then
die(ok, cfg.errorcodes.CRASH)
elseif not ok then
die(err, exitcode)
end
else
die("Unknown command: "..command)
end
util.run_scheduled_functions()
end
return command_line

View File

@ -0,0 +1,72 @@
--- Module implementing the LuaRocks "config" command.
-- Queries information about the LuaRocks configuration.
local config_cmd = {}
local cfg = require("luarocks.cfg")
local util = require("luarocks.util")
local dir = require("luarocks.dir")
util.add_run_function(config_cmd)
config_cmd.help_summary = "Query information about the LuaRocks configuration."
config_cmd.help_arguments = "<flag>"
config_cmd.help = [[
--lua-incdir Path to Lua header files.
--lua-libdir Path to Lua library files.
--lua-ver Lua version (in major.minor format). e.g. 5.1
--system-config Location of the system config file.
--user-config Location of the user config file.
--rock-trees Rocks trees in use. First the user tree, then the system tree.
]]
local function config_file(conf)
print(dir.normalize(conf.file))
if conf.ok then
return true
else
return nil, "file not found"
end
end
--- Driver function for "config" command.
-- @return boolean: True if succeeded, nil on errors.
function config_cmd.command(flags)
if flags["lua-incdir"] then
print(cfg.variables.LUA_INCDIR)
return true
end
if flags["lua-libdir"] then
print(cfg.variables.LUA_LIBDIR)
return true
end
if flags["lua-ver"] then
print(cfg.lua_version)
return true
end
local conf = cfg.which_config()
if flags["system-config"] then
return config_file(conf.system)
end
if flags["user-config"] then
return config_file(conf.user)
end
if flags["rock-trees"] then
for _, tree in ipairs(cfg.rocks_trees) do
if type(tree) == "string" then
util.printout(dir.normalize(tree))
else
local name = tree.name and "\t"..tree.name or ""
util.printout(dir.normalize(tree.root)..name)
end
end
return true
end
return nil, "Please provide a flag for querying configuration values. "..util.see_help("config")
end
return config_cmd

View File

@ -0,0 +1,752 @@
--- 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.
local deps = {}
package.loaded["luarocks.deps"] = deps
local cfg = require("luarocks.cfg")
local manif_core = require("luarocks.manif_core")
local path = require("luarocks.path")
local dir = require("luarocks.dir")
local util = require("luarocks.util")
local operators = {
["=="] = "==",
["~="] = "~=",
[">"] = ">",
["<"] = "<",
[">="] = ">=",
["<="] = "<=",
["~>"] = "~>",
-- plus some convenience translations
[""] = "==",
["="] = "==",
["!="] = "~="
}
local deltas = {
scm = 1100,
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 deps.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
util.printerr("Warning: version number '"..vstring.."' could not be parsed.")
version[i] = 0
break
end
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 deps.compare_versions(a, b)
return deps.parse_version(a) > deps.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,]*(.*)")
local _op = operators[op]
version = deps.parse_version(version)
if not _op then
return nil, "Encountered bad constraint operator: '"..tostring(op).."' in '"..input.."'"
end
if not version then
return nil, "Could not parse version from constraint: '"..input.."'"
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 deps.parse_constraints(input)
assert(type(input) == "string")
local constraints, constraint, oinput = {}, nil, input
while #input > 0 do
constraint, input = parse_constraint(input)
if constraint then
table.insert(constraints, constraint)
else
return nil, "Failed to parse constraint '"..tostring(oinput).."' with error: ".. input
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 deps.parse_dep(dep)
assert(type(dep) == "string")
local name, rest = dep:match("^%s*([a-zA-Z0-9][a-zA-Z0-9%.%-%_]*)%s*(.*)")
if not name then return nil, "failed to extract dependency name from '"..tostring(dep).."'" end
local constraints, err = deps.parse_constraints(rest)
if not constraints then return nil, err 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 deps.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 deps.show_dep(dep, internal)
assert(type(dep) == "table")
assert(type(internal) == "boolean" or not internal)
if #dep.constraints > 0 then
local pretty = {}
for _, c in ipairs(dep.constraints) do
table.insert(pretty, c.op .. " " .. deps.show_version(c.version, internal))
end
return dep.name.." "..table.concat(pretty, ", ")
else
return dep.name
end
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 = deps.parse_version(version) end
if type(requested) ~= "table" then requested = deps.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 deps.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
if type(constr.version) == "string" then
constr.version = deps.parse_version(constr.version)
end
local constr_version, constr_op = constr.version, constr.op
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 string or nil: latest installed version of the rock matching the dependency
-- or nil if it could not be matched.
local function match_dep(dep, blacklist, deps_mode)
assert(type(dep) == "table")
local versions
if cfg.rocks_provided[dep.name] then
-- provided rocks have higher priority than manifest's rocks
versions = { cfg.rocks_provided[dep.name] }
else
versions = manif_core.get_versions(dep.name, deps_mode)
end
local latest_version
for _, vstring in ipairs(versions) do
if not blacklist or not blacklist[vstring] then
local version = deps.parse_version(vstring)
if deps.match_constraints(version, dep.constraints) then
if not latest_version or version > latest_version then
latest_version = version
end
end
end
end
return latest_version and latest_version.string
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, table: A table where keys are dependencies parsed
-- in table format and values are tables containing fields 'name' and
-- version' representing matches; a table of missing dependencies
-- parsed as tables; and a table of "no-upgrade" missing dependencies
-- (to be used in plugin modules so that a plugin does not force upgrade of
-- its parent application).
function deps.match_deps(rockspec, blacklist, deps_mode)
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, deps_mode)
if found then
if not cfg.rocks_provided[dep.name] then
matched[dep] = {name = dep.name, version = 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
local function rock_status(name, deps_mode)
local search = require("luarocks.search")
local installed = match_dep(search.make_query(name), nil, deps_mode)
local installation_type = cfg.rocks_provided[name] and "provided by VM" or "installed"
return installed and installed.." "..installation_type or "not installed"
end
--- Check depenendencies of a package and report any missing ones.
-- @param name string: package name.
-- @param version string: package version.
-- @param dependencies table: array of dependencies.
-- @param deps_mode string: Which trees to check dependencies for:
-- "one" for the current default tree, "all" for all trees,
-- "order" for all trees with priority >= the current default, "none" for no trees.
function deps.report_missing_dependencies(name, version, dependencies, deps_mode)
local first_missing_dep = true
for _, dep in ipairs(dependencies) do
if not match_dep(dep, nil, deps_mode) then
if first_missing_dep then
util.printout(("Missing dependencies for %s %s:"):format(name, version))
first_missing_dep = false
end
util.printout((" %s (%s)"):format(deps.show_dep(dep), rock_status(dep.name, deps_mode)))
end
end
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, [string]): True if no errors occurred, or
-- nil and an error message if any test failed, followed by an optional
-- error code.
function deps.fulfill_dependencies(rockspec, deps_mode)
local search = require("luarocks.search")
local install = require("luarocks.install")
if rockspec.supported_platforms then
if not deps.platforms_set then
deps.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 deps.platforms_set[plat] then
return nil, "This rockspec for "..rockspec.package.." does not support "..plat.." platforms."
end
else
if deps.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
deps.report_missing_dependencies(rockspec.name, rockspec.version, rockspec.dependencies, deps_mode)
local first_missing_dep = true
for _, dep in ipairs(rockspec.dependencies) do
if not match_dep(dep, nil, deps_mode) then
if first_missing_dep then
util.printout()
first_missing_dep = false
end
util.printout(("%s %s depends on %s (%s)"):format(
rockspec.name, rockspec.version, deps.show_dep(dep), rock_status(dep.name, deps_mode)))
if dep.constraints[1] and dep.constraints[1].no_upgrade then
util.printerr("This version of "..rockspec.name.." is designed for use with")
util.printerr(deps.show_dep(dep)..", but is configured to avoid upgrading it")
util.printerr("automatically. Please upgrade "..dep.name.." with")
util.printerr(" luarocks install "..dep.name)
util.printerr("or choose an older version of "..rockspec.name.." with")
util.printerr(" luarocks search "..rockspec.name)
return nil, "Failed matching dependencies"
end
local url, search_err = search.find_suitable_rock(dep)
if not url then
return nil, "Could not satisfy dependency "..deps.show_dep(dep)..": "..search_err
end
util.printout("Installing "..url)
local ok, install_err, errcode = install.command({deps_mode = deps_mode}, url)
if not ok then
return nil, "Failed installing dependency: "..url.." - "..install_err, errcode
end
end
end
return true
end
--- If filename matches a pattern, return the capture.
-- For example, given "libfoo.so" and "lib?.so" is a pattern,
-- returns "foo" (which can then be used to build names
-- based on other patterns.
-- @param file string: a filename
-- @param pattern string: a pattern, where ? is to be matched by the filename.
-- @return string The pattern, if found, or nil.
local function deconstruct_pattern(file, pattern)
local depattern = "^"..(pattern:gsub("%.", "%%."):gsub("%*", ".*"):gsub("?", "(.*)")).."$"
return (file:match(depattern))
end
--- Construct all possible patterns for a name and add to the files array.
-- Run through the patterns array replacing all occurrences of "?"
-- with the given file name and store them in the files array.
-- @param file string A raw name (e.g. "foo")
-- @param array of string An array of patterns with "?" as the wildcard
-- (e.g. {"?.so", "lib?.so"})
-- @param files The array of constructed names
local function add_all_patterns(file, patterns, files)
for _, pattern in ipairs(patterns) do
table.insert(files, (pattern:gsub("?", file)))
end
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: <key>_DIR, <key>_BINDIR,
-- <key>_INCDIR and <key>_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 deps.check_external_deps(rockspec, mode)
assert(type(rockspec) == "table")
local fs = require("luarocks.fs")
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
if rockspec.external_dependencies then
for name, ext_files in util.sortedpairs(rockspec.external_dependencies) do
local ok = true
local failed_files = {program = {}, header = {}, library = {}}
local failed_dirname
local failed_testfile
for _, extdir in ipairs(cfg.external_deps_dirs) do
ok = true
local prefix = vars[name.."_DIR"]
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 not prefix then
prefix = extdir
end
if type(prefix) == "table" then
if prefix.bin then
dirs.BINDIR.subdir = prefix.bin
end
if prefix.include then
if dirs.INCDIR then
dirs.INCDIR.subdir = prefix.include
end
end
if prefix.lib then
dirs.LIBDIR.subdir = prefix.lib
end
prefix = prefix.prefix
end
for dirname, dirdata in util.sortedpairs(dirs) do
local paths
local path_var_value = vars[name.."_"..dirname]
if path_var_value then
paths = { path_var_value }
elseif type(dirdata.subdir) == "table" then
paths = {}
for i,v in ipairs(dirdata.subdir) do
paths[i] = dir.path(prefix, v)
end
else
paths = { dir.path(prefix, dirdata.subdir) }
end
dirdata.dir = paths[1]
local file = ext_files[dirdata.testfile]
if file then
local files = {}
if not file:match("%.") then
add_all_patterns(file, dirdata.pattern, files)
else
for _, pattern in ipairs(dirdata.pattern) do
local matched = deconstruct_pattern(file, pattern)
if matched then
add_all_patterns(matched, dirdata.pattern, files)
end
end
table.insert(files, file)
end
local found = false
for _, f in ipairs(files) do
-- small convenience hack
if f:match("%.so$") or f:match("%.dylib$") or f:match("%.dll$") then
f = f:gsub("%.[^.]+$", "."..cfg.external_lib_extension)
end
local pattern
if f:match("%*") then
pattern = f:gsub("%.", "%%."):gsub("%*", ".*")
f = "matching "..f
end
for _, d in ipairs(paths) do
if pattern then
for entry in fs.dir(d) do
if entry:match(pattern) then
found = true
break
end
end
else
found = fs.is_file(dir.path(d, f))
end
if found then
dirdata.dir = d
break
else
table.insert(failed_files[dirdata.testfile], f.." in "..d)
end
end
if found then
break
end
end
if not found then
ok = false
failed_dirname = dirname
failed_testfile = dirdata.testfile
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
local lines = {"Could not find "..failed_testfile.." file for "..name}
local failed_paths = {}
for _, failed_file in ipairs(failed_files[failed_testfile]) do
if not failed_paths[failed_file] then
failed_paths[failed_file] = true
table.insert(lines, " No file "..failed_file)
end
end
table.insert(lines, "You may have to install "..name.." in your system and/or pass "..name.."_DIR or "..name.."_"..failed_dirname.." to the luarocks command.")
table.insert(lines, "Example: luarocks install "..rockspec.name.." "..name.."_DIR=/usr/local")
return nil, table.concat(lines, "\n"), "dependency"
end
end
end
return true
end
--- Recursively add satisfied dependencies of a package to a table,
-- to build a transitive closure of all dependent packages.
-- Additionally ensures that `dependencies` table of the manifest is up-to-date.
-- @param results table: The results table being built, maps package names to versions.
-- @param manifest table: The manifest table containing dependencies.
-- @param name string: Package name.
-- @param version string: Package version.
function deps.scan_deps(results, manifest, name, version, deps_mode)
assert(type(results) == "table")
assert(type(manifest) == "table")
assert(type(name) == "string")
assert(type(version) == "string")
local fetch = require("luarocks.fetch")
if results[name] then
return
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), false)
if not rockspec then
util.printerr("Couldn't load rockspec for "..name.." "..version..": "..err)
return
end
dependencies_name[version] = rockspec.dependencies
else
rockspec = { dependencies = deplist }
end
local matched = deps.match_deps(rockspec, nil, deps_mode)
results[name] = version
for _, match in pairs(matched) do
deps.scan_deps(results, manifest, match.name, match.version, deps_mode)
end
end
local valid_deps_modes = {
one = true,
order = true,
all = true,
none = true,
}
function deps.check_deps_mode_flag(flag)
return valid_deps_modes[flag]
end
function deps.get_deps_mode(flags)
if flags["deps-mode"] then
return flags["deps-mode"]
else
return cfg.deps_mode
end
end
function deps.deps_mode_to_flag(deps_mode)
return "--deps-mode="..deps_mode
end
return deps

View File

@ -0,0 +1,74 @@
--- Generic utilities for handling pathnames.
local dir = {}
package.loaded["luarocks.dir"] = dir
dir.separator = "/"
--- Strip the path off a path+filename.
-- @param pathname string: A path+name, such as "/a/b/c"
-- or "\a\b\c".
-- @return string: The filename without its path, such as "c".
function dir.base_name(pathname)
assert(type(pathname) == "string")
local base = pathname:gsub("[/\\]*$", ""):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.dir_name(pathname)
assert(type(pathname) == "string")
return (pathname:gsub("/*$", ""):match("(.*)[/]+[^/]*")) or ""
end
--- Describe a path in a cross-platform way.
-- Use this function to avoid platform-specific directory
-- separators in other modules. Removes trailing slashes from
-- each component given, to avoid repeated separators.
-- Separators inside strings are kept, to handle URLs containing
-- protocols.
-- @param ... strings representing directories
-- @return string: a string with a platform-specific representation
-- of the path.
function dir.path(...)
local t = {...}
while t[1] == "" do
table.remove(t, 1)
end
return (table.concat(t, "/"):gsub("([^:])/+", "%1/"):gsub("^/+", "/"):gsub("/*$", ""))
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 pathname without the protocol.
function dir.split_url(url)
assert(type(url) == "string")
local protocol, pathname = url:match("^([^:]*)://(.*)")
if not protocol then
protocol = "file"
pathname = url
end
return protocol, pathname
end
--- Normalize a url or local path.
-- URLs should be in the "protocol://path" format. System independent
-- forward slashes are used, removing trailing and double slashes
-- @param url string: an URL or a local pathname.
-- @return string: Normalized result.
function dir.normalize(name)
local protocol, pathname = dir.split_url(name)
pathname = pathname:gsub("\\", "/"):gsub("(.)/*$", "%1"):gsub("//", "/")
if protocol ~= "file" then pathname = protocol .."://"..pathname end
return pathname
end
return dir

View File

@ -0,0 +1,157 @@
--- Module implementing the LuaRocks "doc" command.
-- Shows documentation for an installed rock.
local doc = {}
package.loaded["luarocks.doc"] = doc
local util = require("luarocks.util")
local search = require("luarocks.search")
local path = require("luarocks.path")
local dir = require("luarocks.dir")
local fetch = require("luarocks.fetch")
local fs = require("luarocks.fs")
local download = require("luarocks.download")
util.add_run_function(doc)
doc.help_summary = "Show documentation for an installed rock."
doc.help = [[
<argument> is an existing package name.
Without any flags, tries to load the documentation
using a series of heuristics.
With these flags, return only the desired information:
--home Open the home page of project.
--list List documentation files only.
For more information about a rock, see the 'show' command.
]]
local function show_homepage(homepage, name, version)
if not homepage then
return nil, "No 'homepage' field in rockspec for "..name.." "..version
end
util.printout("Opening "..homepage.." ...")
fs.browser(homepage)
return true
end
local function try_to_open_homepage(name, version)
local temp_dir, err = fs.make_temp_dir("doc-"..name.."-"..(version or ""))
if not temp_dir then
return nil, "Failed creating temporary directory: "..err
end
util.schedule_function(fs.delete, temp_dir)
local ok, err = fs.change_dir(temp_dir)
if not ok then return nil, err end
local filename, err = download.download("rockspec", name, version)
if not filename then return nil, err end
local rockspec, err = fetch.load_local_rockspec(filename)
if not rockspec then return nil, err end
fs.pop_dir()
local descript = rockspec.description or {}
if not descript.homepage then return nil, "No homepage defined for "..name end
return show_homepage(descript.homepage, name, version)
end
--- Driver function for "doc" command.
-- @param name or nil: an existing package name.
-- @param version string or nil: a version may also be passed.
-- @return boolean: True if succeeded, nil on errors.
function doc.command(flags, name, version)
if not name then
return nil, "Argument missing. "..util.see_help("doc")
end
name = name:lower()
local iname, iversion, repo = search.pick_installed_rock(name, version, flags["tree"])
if not iname then
util.printout(name..(version and " "..version or "").." is not installed. Looking for it in the rocks servers...")
return try_to_open_homepage(name, version)
end
name, version = iname, iversion
local rockspec, err = fetch.load_local_rockspec(path.rockspec_file(name, version, repo))
if not rockspec then return nil,err end
local descript = rockspec.description or {}
if flags["home"] then
return show_homepage(descript.homepage, name, version)
end
local directory = path.install_dir(name,version,repo)
local docdir
local directories = { "doc", "docs" }
for _, d in ipairs(directories) do
local dirname = dir.path(directory, d)
if fs.is_dir(dirname) then
docdir = dirname
break
end
end
if not docdir then
if descript.homepage and not flags["list"] then
util.printout("Local documentation directory not found -- opening "..descript.homepage.." ...")
fs.browser(descript.homepage)
return true
end
return nil, "Documentation directory not found for "..name.." "..version
end
docdir = dir.normalize(docdir):gsub("/+", "/")
local files = fs.find(docdir)
local htmlpatt = "%.html?$"
local extensions = { htmlpatt, "%.md$", "%.txt$", "%.textile$", "" }
local basenames = { "index", "readme", "manual" }
local porcelain = flags["porcelain"]
if #files > 0 then
util.title("Documentation files for "..name.." "..version, porcelain)
if porcelain then
for _, file in ipairs(files) do
util.printout(docdir.."/"..file)
end
else
util.printout(docdir.."/")
for _, file in ipairs(files) do
util.printout("\t"..file)
end
end
end
if flags["list"] then
return true
end
for _, extension in ipairs(extensions) do
for _, basename in ipairs(basenames) do
local filename = basename..extension
local found
for _, file in ipairs(files) do
if file:lower():match(filename) and ((not found) or #file < #found) then
found = file
end
end
if found then
local pathname = dir.path(docdir, found)
util.printout()
util.printout("Opening "..pathname.." ...")
util.printout()
local ok = fs.browser(pathname)
if not ok and not pathname:match(htmlpatt) then
local fd = io.open(pathname, "r")
util.printout(fd:read("*a"))
fd:close()
end
return true
end
end
end
return true
end
return doc

View File

@ -0,0 +1,109 @@
--- Module implementing the luarocks "download" command.
-- Download a rock from the repository.
local download = {}
package.loaded["luarocks.download"] = download
local util = require("luarocks.util")
local path = require("luarocks.path")
local fetch = require("luarocks.fetch")
local search = require("luarocks.search")
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local cfg = require("luarocks.cfg")
util.add_run_function(download)
download.help_summary = "Download a specific rock file from a rocks server."
download.help_arguments = "[--all] [--arch=<arch> | --source | --rockspec] [<name> [<version>]]"
download.help = [[
--all Download all files if there are multiple matches.
--source Download .src.rock if available.
--rockspec Download .rockspec if available.
--arch=<arch> Download rock for a specific architecture.
]]
local function get_file(filename)
local protocol, pathname = dir.split_url(filename)
if protocol == "file" then
local ok, err = fs.copy(pathname, fs.current_dir(), cfg.perm_read)
if ok then
return pathname
else
return nil, err
end
else
return fetch.fetch_url(filename)
end
end
function download.download(arch, name, version, all)
local query = search.make_query(name, version)
if arch then query.arch = arch end
local search_err
if all then
if name == "" then query.exact_name = false end
local results = search.search_repos(query)
local has_result = false
local all_ok = true
local any_err = ""
for name, result in pairs(results) do
for version, items in pairs(result) do
for _, item in ipairs(items) do
-- Ignore provided rocks.
if item.arch ~= "installed" then
has_result = true
local filename = path.make_url(item.repo, name, version, item.arch)
local ok, err = get_file(filename)
if not ok then
all_ok = false
any_err = any_err .. "\n" .. err
end
end
end
end
end
if has_result then
return all_ok, any_err
end
else
local url
url, search_err = search.find_suitable_rock(query)
if url then
return get_file(url)
end
end
return nil, "Could not find a result named "..name..(version and " "..version or "")..
(search_err and ": "..search_err or ".")
end
--- Driver function for the "download" command.
-- @param name string: a rock name.
-- @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 download.command(flags, name, version)
assert(type(version) == "string" or not version)
if type(name) ~= "string" and not flags["all"] then
return nil, "Argument missing. "..util.see_help("download")
end
if not name then name, version = "", "" end
local arch
if flags["source"] then
arch = "src"
elseif flags["rockspec"] then
arch = "rockspec"
elseif flags["arch"] then
arch = flags["arch"]
end
local dl, err = download.download(arch, name:lower(), version, flags["all"])
return dl and true, err
end
return download

View File

@ -0,0 +1,394 @@
--- Functions related to fetching and loading local and remote files.
local fetch = {}
package.loaded["luarocks.fetch"] = fetch
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
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")
local cfg = require("luarocks.cfg")
function fetch.is_basic_protocol(protocol, remote)
return protocol == "http" or protocol == "https" or protocol == "ftp" or (not remote and protocol == "file")
end
--- 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, [string]): the absolute local pathname for the
-- fetched file, or nil and a message in case of errors, followed by
-- an optional error code.
function fetch.fetch_url(url, filename, cache)
assert(type(url) == "string")
assert(type(filename) == "string" or not filename)
local protocol, pathname = dir.split_url(url)
if protocol == "file" then
return fs.absolute_name(pathname)
elseif fetch.is_basic_protocol(protocol, true) then
local ok, name = fs.download(url, filename, cache)
if not ok then
return nil, "Failed downloading "..url..(filename and " - "..filename or ""), "network"
end
return name
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, [string]): absolute local pathname of
-- the fetched file and temporary directory name; or nil and an error message
-- followed by an optional error code
function fetch.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 dir.base_name(url)
local protocol, pathname = dir.split_url(url)
if protocol == "file" then
if fs.exists(pathname) then
return pathname, dir.dir_name(fs.absolute_name(pathname))
else
return nil, "File not found: "..pathname
end
else
local temp_dir, err = fs.make_temp_dir(tmpname)
if not temp_dir then
return nil, "Failed creating temporary directory "..tmpname..": "..err
end
util.schedule_function(fs.delete, temp_dir)
local ok, err = fs.change_dir(temp_dir)
if not ok then return nil, err end
local file, err, errcode = fetch.fetch_url(url, filename)
fs.pop_dir()
if not file then
return nil, "Error fetching file: "..err, errcode
end
return file, temp_dir
end
end
-- Determine base directory of a fetched URL by extracting its
-- archive and looking for a directory in the root.
-- @param file string: absolute local pathname of the fetched file
-- @param temp_dir string: temporary directory in which URL was fetched.
-- @param src_url string: URL to use when inferring base directory.
-- @param src_dir string or nil: expected base directory (inferred
-- from src_url if not given).
-- @return (string, string) or (string, nil) or (nil, string):
-- The inferred base directory and the one actually found (which may
-- be nil if not found), or nil followed by an error message.
-- The inferred dir is returned first to avoid confusion with errors,
-- because it is never nil.
function fetch.find_base_dir(file, temp_dir, src_url, src_dir)
local ok, err = fs.change_dir(temp_dir)
if not ok then return nil, err end
fs.unpack_archive(file)
local inferred_dir = src_dir or fetch.url_to_base_dir(src_url)
local found_dir = nil
if fs.exists(inferred_dir) then
found_dir = inferred_dir
else
util.printerr("Directory "..inferred_dir.." not found")
local files = fs.list_dir()
if files then
table.sort(files)
for i,filename in ipairs(files) do
if fs.is_dir(filename) then
util.printerr("Found "..filename)
found_dir = filename
break
end
end
end
end
fs.pop_dir()
return inferred_dir, found_dir
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, [string]): the directory containing the contents
-- of the unpacked rock.
function fetch.fetch_and_unpack_rock(rock_file, dest)
assert(type(rock_file) == "string")
assert(type(dest) == "string" or not dest)
local name = dir.base_name(rock_file):match("(.*)%.[^.]*%.rock")
local rock_file, err, errcode = fetch.fetch_url_at_temp_dir(rock_file,"luarocks-rock-"..name)
if not rock_file then
return nil, "Could not fetch rock file: " .. err, errcode
end
rock_file = fs.absolute_name(rock_file)
local unpack_dir
if dest then
unpack_dir = dest
local ok, err = fs.make_dir(unpack_dir)
if not ok then
return nil, "Failed unpacking rock file: " .. err
end
else
unpack_dir = fs.make_temp_dir(name)
end
if not dest then
util.schedule_function(fs.delete, unpack_dir)
end
local ok, err = fs.change_dir(unpack_dir)
if not ok then return nil, err end
ok = fs.unzip(rock_file)
if not ok then
return nil, "Failed unpacking rock file: " .. rock_file
end
fs.pop_dir()
return unpack_dir
end
function fetch.url_to_base_dir(url)
-- for extensions like foo.tar.gz, "gz" is stripped first
local known_exts = {}
for _, ext in ipairs{"zip", "git", "tgz", "tar", "gz", "bz2"} do
known_exts[ext] = ""
end
local base = dir.base_name(url)
return (base:gsub("%.([^.]*)$", known_exts):gsub("%.tar", ""))
end
--- Back-end function that actually loads the local rockspec.
-- Performs some validation and postprocessing of the rockspec contents.
-- @param filename string: The local filename of the rockspec file.
-- @param quick boolean: if true, skips some steps when loading
-- rockspec.
-- @return table or (nil, string): A table representing the rockspec
-- or nil followed by an error message.
function fetch.load_local_rockspec(filename, quick)
assert(type(filename) == "string")
filename = fs.absolute_name(filename)
local rockspec, err = persist.load_into_table(filename)
if not rockspec then
return nil, "Could not load rockspec file "..filename.." ("..err..")"
end
if cfg.branch and (type(rockspec.source) == "table") then
rockspec.source.branch = cfg.branch
end
local globals = err
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
if not quick then
local ok, err = type_check.type_check_rockspec(rockspec, globals)
if not ok then
return nil, filename..": "..err
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 = dir.base_name(filename)
if basename == "rockspec" then
rockspec.name = rockspec.package:lower()
else
rockspec.name = basename:match("(.*)-[^-]*-[0-9]*")
if not rockspec.name then
return nil, "Expected filename in format 'name-version-revision.rockspec'."
end
end
local protocol, pathname = dir.split_url(rockspec.source.url)
if fetch.is_basic_protocol(protocol) then
rockspec.source.file = rockspec.source.file or dir.base_name(rockspec.source.url)
end
rockspec.source.protocol, rockspec.source.pathname = protocol, pathname
-- Temporary compatibility
if rockspec.source.cvs_module then rockspec.source.module = rockspec.source.cvs_module end
if rockspec.source.cvs_tag then rockspec.source.tag = rockspec.source.cvs_tag end
local name_version = rockspec.package:lower() .. "-" .. rockspec.version
if basename ~= "rockspec" and 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 = fetch.url_to_base_dir(filebase)
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, err = deps.parse_dep(rockspec.dependencies[i])
if not parsed then
return nil, "Parse error processing dependency '"..rockspec.dependencies[i].."': "..tostring(err)
end
rockspec.dependencies[i] = parsed
end
else
rockspec.dependencies = {}
end
if not quick then
path.configure_paths(rockspec)
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.
-- @param location string or nil: Where to download. If not given,
-- a temporary dir is created.
-- @return table or (nil, string, [string]): A table representing the rockspec
-- or nil followed by an error message and optional error code.
function fetch.load_rockspec(filename, location)
assert(type(filename) == "string")
local name
local basename = dir.base_name(filename)
if basename == "rockspec" then
name = "rockspec"
else
name = basename:match("(.*)%.rockspec")
if not name then
return nil, "Filename '"..filename.."' does not look like a rockspec."
end
end
local err, errcode
if location then
local ok, err = fs.change_dir(location)
if not ok then return nil, err end
filename, err = fetch.fetch_url(filename)
fs.pop_dir()
else
filename, err, errcode = fetch.fetch_url_at_temp_dir(filename,"luarocks-rockspec-"..name)
end
if not filename then
return nil, err, errcode
end
return fetch.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;
-- if not given, will extract to a temporary directory.
-- @return (string, string) or (nil, string, [string]): The absolute pathname of
-- the fetched source tarball and the temporary directory created to
-- store it; or nil and an error message and optional error code.
function fetch.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, store_dir
local ok, err, errcode
if dest_dir then
ok, err = fs.change_dir(dest_dir)
if not ok then return nil, err, "dest_dir" end
source_file, err, errcode = fetch.fetch_url(url, filename)
fs.pop_dir()
store_dir = dest_dir
else
source_file, store_dir, errcode = fetch.fetch_url_at_temp_dir(url, "luarocks-source-"..name, filename)
end
if not source_file then
return nil, err or store_dir, errcode
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.", "md5"
end
end
if extract then
local ok, err = fs.change_dir(store_dir)
if not ok then return nil, err end
ok, err = fs.unpack_archive(rockspec.source.file)
if not ok then return nil, err end
if not fs.exists(rockspec.source.dir) then
return nil, "Directory "..rockspec.source.dir.." not found inside archive "..rockspec.source.file, "source.dir", source_file, store_dir
end
fs.pop_dir()
end
return source_file, store_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.
-- if not given, will extract to a temporary 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.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 ok, proto
if fetch.is_basic_protocol(protocol) then
proto = fetch
else
ok, proto = pcall(require, "luarocks.fetch."..protocol:gsub("[+-]", "_"))
if not ok then
return nil, "Unknown protocol "..protocol
end
end
if cfg.only_sources_from
and rockspec.source.pathname
and #rockspec.source.pathname > 0 then
if #cfg.only_sources_from == 0 then
return nil, "Can't download "..rockspec.source.url.." -- download from remote servers disabled"
elseif rockspec.source.pathname:find(cfg.only_sources_from, 1, true) ~= 1 then
return nil, "Can't download "..rockspec.source.url.." -- only downloading from "..cfg.only_sources_from
end
end
return proto.get_sources(rockspec, extract, dest_dir)
end
return fetch

View File

@ -0,0 +1,55 @@
--- Fetch back-end for retrieving sources from CVS.
local cvs = {}
local unpack = unpack or table.unpack
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
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 cvs.get_sources(rockspec, extract, dest_dir)
assert(type(rockspec) == "table")
assert(type(dest_dir) == "string" or not dest_dir)
local cvs_cmd = rockspec.variables.CVS
local ok, err_msg = fs.is_tool_available(cvs_cmd, "CVS")
if not ok then
return nil, err_msg
end
local name_version = rockspec.name .. "-" .. rockspec.version
local module = rockspec.source.module or dir.base_name(rockspec.source.url)
local command = {cvs_cmd, "-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 store_dir
if not dest_dir then
store_dir = fs.make_temp_dir(name_version)
if not store_dir then
return nil, "Failed creating temporary directory."
end
util.schedule_function(fs.delete, store_dir)
else
store_dir = dest_dir
end
local ok, err = fs.change_dir(store_dir)
if not ok then return nil, err end
if not fs.execute(unpack(command)) then
return nil, "Failed fetching files from CVS."
end
fs.pop_dir()
return module, store_dir
end
return cvs

View File

@ -0,0 +1,92 @@
--- Fetch back-end for retrieving sources from GIT.
local git = {}
local unpack = unpack or table.unpack
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local util = require("luarocks.util")
--- Git >= 1.7.10 can clone a branch **or tag**, < 1.7.10 by branch only. We
-- need to know this in order to build the appropriate command; if we can't
-- clone by tag then we'll have to issue a subsequent command to check out the
-- given tag.
-- @return boolean: Whether Git can clone by tag.
local function git_can_clone_by_tag(git_cmd)
local version_string = io.popen(fs.Q(git_cmd)..' --version'):read()
local major, minor, tiny = version_string:match('(%d-)%.(%d+)%.?(%d*)')
major, minor, tiny = tonumber(major), tonumber(minor), tonumber(tiny) or 0
local value = major > 1 or (major == 1 and (minor > 7 or (minor == 7 and tiny >= 10)))
git_can_clone_by_tag = function() return value end
return value
end
--- 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 git.get_sources(rockspec, extract, dest_dir, depth)
assert(type(rockspec) == "table")
assert(type(dest_dir) == "string" or not dest_dir)
local git_cmd = rockspec.variables.GIT
local name_version = rockspec.name .. "-" .. rockspec.version
local module = dir.base_name(rockspec.source.url)
-- Strip off .git from base name if present
module = module:gsub("%.git$", "")
local ok, err_msg = fs.is_tool_available(git_cmd, "Git")
if not ok then
return nil, err_msg
end
local store_dir
if not dest_dir then
store_dir = fs.make_temp_dir(name_version)
if not store_dir then
return nil, "Failed creating temporary directory."
end
util.schedule_function(fs.delete, store_dir)
else
store_dir = dest_dir
end
store_dir = fs.absolute_name(store_dir)
local ok, err = fs.change_dir(store_dir)
if not ok then return nil, err end
local command = {fs.Q(git_cmd), "clone", depth or "--depth=1", rockspec.source.url, module}
local tag_or_branch = rockspec.source.tag or rockspec.source.branch
-- If the tag or branch is explicitly set to "master" in the rockspec, then
-- we can avoid passing it to Git since it's the default.
if tag_or_branch == "master" then tag_or_branch = nil end
if tag_or_branch then
if git_can_clone_by_tag(git_cmd) then
-- The argument to `--branch` can actually be a branch or a tag as of
-- Git 1.7.10.
table.insert(command, 3, "--branch=" .. tag_or_branch)
end
end
if not fs.execute(unpack(command)) then
return nil, "Failed cloning git repository."
end
ok, err = fs.change_dir(module)
if not ok then return nil, err end
if tag_or_branch and not git_can_clone_by_tag() then
local checkout_command = {fs.Q(git_cmd), "checkout", tag_or_branch}
if not fs.execute(unpack(checkout_command)) then
return nil, 'Failed to check out the "' .. tag_or_branch ..'" tag or branch.'
end
end
fs.delete(dir.path(store_dir, module, ".git"))
fs.delete(dir.path(store_dir, module, ".gitignore"))
fs.pop_dir()
fs.pop_dir()
return module, store_dir
end
return git

View File

@ -0,0 +1,19 @@
--- Fetch back-end for retrieving sources from local Git repositories.
local git_file = {}
local git = require("luarocks.fetch.git")
--- Fetch sources for building a rock from a local Git repository.
-- @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 git_file.get_sources(rockspec, extract, dest_dir)
rockspec.source.url = rockspec.source.url:gsub("^git.file://", "")
return git.get_sources(rockspec, extract, dest_dir)
end
return git_file

View File

@ -0,0 +1,26 @@
--- Fetch back-end for retrieving sources from Git repositories
-- that use http:// transport. For example, for fetching a repository
-- that requires the following command line:
-- `git clone http://example.com/foo.git`
-- you can use this in the rockspec:
-- source = { url = "git+http://example.com/foo.git" }
-- Prefer using the normal git:// fetch mode as it is more widely
-- available in older versions of LuaRocks.
local git_http = {}
local git = require("luarocks.fetch.git")
--- Fetch sources for building a rock from a local Git repository.
-- @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 git_http.get_sources(rockspec, extract, dest_dir)
rockspec.source.url = rockspec.source.url:gsub("^git.", "")
return git.get_sources(rockspec, extract, dest_dir, "--")
end
return git_http

View File

@ -0,0 +1,7 @@
--- Fetch back-end for retrieving sources from Git repositories
-- that use https:// transport. For example, for fetching a repository
-- that requires the following command line:
-- `git clone https://example.com/foo.git`
-- you can use this in the rockspec:
-- source = { url = "git+https://example.com/foo.git" }
return require "luarocks.fetch.git_http"

View File

@ -0,0 +1,32 @@
--- Fetch back-end for retrieving sources from Git repositories
-- that use ssh:// transport. For example, for fetching a repository
-- that requires the following command line:
-- `git clone ssh://git@example.com/path/foo.git
-- you can use this in the rockspec:
-- source = { url = "git+ssh://git@example.com/path/foo.git" }
-- It also handles scp-style ssh urls: git@example.com:path/foo.git,
-- but you have to prepend the "git+ssh://" and why not use the "newer"
-- style anyway?
local git_ssh = {}
local git = require("luarocks.fetch.git")
--- Fetch sources for building a rock from a local Git repository.
-- @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 git_ssh.get_sources(rockspec, extract, dest_dir)
rockspec.source.url = rockspec.source.url:gsub("^git.", "")
-- Handle old-style scp-like git ssh urls
if rockspec.source.url:match("^ssh://[^/]+:[^%d]") then
rockspec.source.url = rockspec.source.url:gsub("^ssh://", "")
end
return git.get_sources(rockspec, extract, dest_dir, "--")
end
return git_ssh

View File

@ -0,0 +1,65 @@
--- Fetch back-end for retrieving sources from HG.
local hg = {}
local unpack = unpack or table.unpack
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local util = require("luarocks.util")
--- Download sources for building a rock, using hg.
-- @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 hg.get_sources(rockspec, extract, dest_dir)
assert(type(rockspec) == "table")
assert(type(dest_dir) == "string" or not dest_dir)
local hg_cmd = rockspec.variables.HG
local ok, err_msg = fs.is_tool_available(hg_cmd, "Mercurial")
if not ok then
return nil, err_msg
end
local name_version = rockspec.name .. "-" .. rockspec.version
-- Strip off special hg:// protocol type
local url = rockspec.source.url:gsub("^hg://", "")
local module = dir.base_name(url)
local command = {hg_cmd, "clone", url, module}
local tag_or_branch = rockspec.source.tag or rockspec.source.branch
if tag_or_branch then
command = {hg_cmd, "clone", "--rev", tag_or_branch, url, module}
end
local store_dir
if not dest_dir then
store_dir = fs.make_temp_dir(name_version)
if not store_dir then
return nil, "Failed creating temporary directory."
end
util.schedule_function(fs.delete, store_dir)
else
store_dir = dest_dir
end
local ok, err = fs.change_dir(store_dir)
if not ok then return nil, err end
if not fs.execute(unpack(command)) then
return nil, "Failed cloning hg repository."
end
ok, err = fs.change_dir(module)
if not ok then return nil, err end
fs.delete(dir.path(store_dir, module, ".hg"))
fs.delete(dir.path(store_dir, module, ".hgignore"))
fs.pop_dir()
fs.pop_dir()
return module, store_dir
end
return hg

View File

@ -0,0 +1,24 @@
--- Fetch back-end for retrieving sources from hg repositories
-- that use http:// transport. For example, for fetching a repository
-- that requires the following command line:
-- `hg clone http://example.com/foo`
-- you can use this in the rockspec:
-- source = { url = "hg+http://example.com/foo" }
local hg_http = {}
local hg = require("luarocks.fetch.hg")
--- Download sources for building a rock, using hg over http.
-- @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 hg_http.get_sources(rockspec, extract, dest_dir)
rockspec.source.url = rockspec.source.url:gsub("^hg.", "")
return hg.get_sources(rockspec, extract, dest_dir)
end
return hg_http

View File

@ -0,0 +1,8 @@
--- Fetch back-end for retrieving sources from hg repositories
-- that use https:// transport. For example, for fetching a repository
-- that requires the following command line:
-- `hg clone https://example.com/foo`
-- you can use this in the rockspec:
-- source = { url = "hg+https://example.com/foo" }
return require "luarocks.fetch.hg_http"

View File

@ -0,0 +1,8 @@
--- Fetch back-end for retrieving sources from hg repositories
-- that use ssh:// transport. For example, for fetching a repository
-- that requires the following command line:
-- `hg clone ssh://example.com/foo`
-- you can use this in the rockspec:
-- source = { url = "hg+ssh://example.com/foo" }
return require "luarocks.fetch.hg_http"

View File

@ -0,0 +1,44 @@
--- Fetch back-end for retrieving sources from Surround SCM Server
local sscm = {}
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
--- 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 sscm.get_sources(rockspec, extract, dest_dir)
assert(type(rockspec) == "table")
assert(type(dest_dir) == "string" or not dest_dir)
local sscm_cmd = rockspec.variables.SSCM
local module = rockspec.source.module or dir.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_cmd..[[ 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_cmd, "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
return sscm

View File

@ -0,0 +1,64 @@
--- Fetch back-end for retrieving sources from Subversion.
local svn = {}
local unpack = unpack or table.unpack
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local util = require("luarocks.util")
--- Download sources for building a rock, using Subversion.
-- @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 svn.get_sources(rockspec, extract, dest_dir)
assert(type(rockspec) == "table")
assert(type(dest_dir) == "string" or not dest_dir)
local svn_cmd = rockspec.variables.SVN
local ok, err_msg = fs.is_tool_available(svn_cmd, "--version", "Subversion")
if not ok then
return nil, err_msg
end
local name_version = rockspec.name .. "-" .. rockspec.version
local module = rockspec.source.module or dir.base_name(rockspec.source.url)
local url = rockspec.source.url:gsub("^svn://", "")
local command = {svn_cmd, "checkout", url, module}
if rockspec.source.tag then
table.insert(command, 5, "-r")
table.insert(command, 6, rockspec.source.tag)
end
local store_dir
if not dest_dir then
store_dir = fs.make_temp_dir(name_version)
if not store_dir then
return nil, "Failed creating temporary directory."
end
util.schedule_function(fs.delete, store_dir)
else
store_dir = dest_dir
end
local ok, err = fs.change_dir(store_dir)
if not ok then return nil, err end
if not fs.execute(unpack(command)) then
return nil, "Failed fetching files from Subversion."
end
ok, err = fs.change_dir(module)
if not ok then return nil, err end
for _, d in ipairs(fs.find(".")) do
if dir.base_name(d) == ".svn" then
fs.delete(dir.path(store_dir, module, d))
end
end
fs.pop_dir()
fs.pop_dir()
return module, store_dir
end
return svn

View File

@ -0,0 +1,76 @@
--- 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.
local pairs = pairs
local fs = {}
package.loaded["luarocks.fs"] = fs
local cfg = require("luarocks.cfg")
local pack = table.pack or function(...) return { n = select("#", ...), ... } end
local unpack = table.unpack or unpack
local old_popen, old_exec
fs.verbose = function() -- patch io.popen and os.execute to display commands in verbose mode
if old_popen or old_exec then return end
old_popen = io.popen
io.popen = function(one, two)
if two == nil then
print("\nio.popen: ", one)
else
print("\nio.popen: ", one, "Mode:", two)
end
return old_popen(one, two)
end
old_exec = os.execute
os.execute = function(cmd)
-- redact api keys if present
print("\nos.execute: ", (cmd:gsub("(/api/[^/]+/)([^/]+)/", function(cap, key) return cap.."<redacted>/" end)) )
local code = pack(old_exec(cmd))
print("Results: "..tostring(code.n))
for i = 1,code.n do
print(" "..tostring(i).." ("..type(code[i]).."): "..tostring(code[i]))
end
return unpack(code, 1, code.n)
end
end
if cfg.verbose then fs.verbose() end
local function load_fns(fs_table)
for name, fn in pairs(fs_table) do
if not fs[name] then
fs[name] = fn
end
end
end
-- Load platform-specific functions
local loaded_platform = nil
for _, platform in ipairs(cfg.platforms) do
local ok, fs_plat = pcall(require, "luarocks.fs."..platform)
if ok and fs_plat then
loaded_platform = platform
load_fns(fs_plat)
break
end
end
-- Load platform-independent pure-Lua functionality
local fs_lua = require("luarocks.fs.lua")
load_fns(fs_lua)
-- Load platform-specific fallbacks for missing Lua modules
local ok, fs_plat_tools = pcall(require, "luarocks.fs."..loaded_platform..".tools")
if ok and fs_plat_tools then
load_fns(fs_plat_tools)
load_fns(require("luarocks.fs.tools"))
end
return fs

View File

@ -0,0 +1,873 @@
--- Native Lua implementation of filesystem and platform abstractions,
-- using LuaFileSystem, LZLib, MD5 and LuaCurl.
-- module("luarocks.fs.lua")
local fs_lua = {}
local fs = require("luarocks.fs")
local cfg = require("luarocks.cfg")
local dir = require("luarocks.dir")
local util = require("luarocks.util")
local path = require("luarocks.path")
local socket_ok, zip_ok, unzip_ok, lfs_ok, md5_ok, posix_ok, _
local http, ftp, lrzip, luazip, lfs, md5, posix
if cfg.fs_use_modules then
socket_ok, http = pcall(require, "socket.http")
_, ftp = pcall(require, "socket.ftp")
zip_ok, lrzip = pcall(require, "luarocks.tools.zip")
unzip_ok, luazip = pcall(require, "zip"); _G.zip = nil
lfs_ok, lfs = pcall(require, "lfs")
md5_ok, md5 = pcall(require, "md5")
posix_ok, posix = pcall(require, "posix")
end
local patch = require("luarocks.tools.patch")
local dir_stack = {}
local dir_separator = "/"
--- Test is file/dir is writable.
-- Warning: testing if a file/dir is writable does not guarantee
-- that it will remain writable and therefore it is no replacement
-- for checking the result of subsequent operations.
-- @param file string: filename to test
-- @return boolean: true if file exists, false otherwise.
function fs_lua.is_writable(file)
assert(file)
file = dir.normalize(file)
local result
if fs.is_dir(file) then
local file2 = dir.path(file, '.tmpluarockstestwritable')
local fh = io.open(file2, 'wb')
result = fh ~= nil
if fh then fh:close() end
os.remove(file2)
else
local fh = io.open(file, 'r+b')
result = fh ~= nil
if fh then fh:close() end
end
return result
end
local function quote_args(command, ...)
local out = { command }
for _, arg in ipairs({...}) do
assert(type(arg) == "string")
out[#out+1] = fs.Q(arg)
end
return table.concat(out, " ")
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 fs_lua.execute(command, ...)
assert(type(command) == "string")
return fs.execute_string(quote_args(command, ...))
end
--- Run the given command, quoting its arguments, silencing its output.
-- The command is executed in the current directory in the dir stack.
-- Silencing is omitted if 'verbose' mode is enabled.
-- @param command string: The command to be executed. No quoting/escaping
-- is applied.
-- @param ... Strings containing additional arguments, which will be quoted.
-- @return boolean: true if command succeeds (status code 0), false
-- otherwise.
function fs_lua.execute_quiet(command, ...)
assert(type(command) == "string")
if cfg.verbose then -- omit silencing output
return fs.execute_string(quote_args(command, ...))
else
return fs.execute_string(fs.quiet(quote_args(command, ...)))
end
end
--- Checks if the given tool is available.
-- The tool is executed using a flag, usually just to ask its version.
-- @param tool_cmd string: The command to be used to check the tool's presence (e.g. hg in case of Mercurial)
-- @param tool_name string: The actual name of the tool (e.g. Mercurial)
-- @param arg string: The flag to pass to the tool. '--version' by default.
function fs_lua.is_tool_available(tool_cmd, tool_name, arg)
assert(type(tool_cmd) == "string")
assert(type(tool_name) == "string")
arg = arg or "--version"
assert(type(arg) == "string")
if not fs.execute_quiet(fs.Q(tool_cmd), arg) then
local msg = "'%s' program not found. Make sure %s is installed and is available in your PATH " ..
"(or you may want to edit the 'variables.%s' value in file '%s')"
return nil, msg:format(tool_cmd, tool_name, tool_name:upper(), cfg.which_config().nearest)
else
return true
end
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 + msg if not
-- or if it could not perform the check for any reason.
function fs_lua.check_md5(file, md5sum)
file = dir.normalize(file)
local computed, msg = fs.get_md5(file)
if not computed then
return false, msg
end
if computed:match("^"..md5sum) then
return true
else
return false, "Mismatch MD5 hash for file "..file
end
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 fs_lua.list_dir(at)
local result = {}
for file in fs.dir(at) do
result[#result+1] = file
end
return result
end
--- Iterate over the contents of a directory.
-- @param at string or nil: directory to list (will be the current
-- directory if none is given).
-- @return function: an iterator function suitable for use with
-- the for statement.
function fs_lua.dir(at)
if not at then
at = fs.current_dir()
end
at = dir.normalize(at)
if not fs.is_dir(at) then
return function() end
end
return coroutine.wrap(function() fs.dir_iterator(at) end)
end
---------------------------------------------------------------------
-- LuaFileSystem functions
---------------------------------------------------------------------
if lfs_ok then
--- 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 fs_lua.execute_string(cmd)
local code = os.execute(cmd)
return (code == 0 or code == true)
end
--- Obtain current directory.
-- Uses the module's internal dir stack.
-- @return string: the absolute pathname of the current directory.
function fs_lua.current_dir()
return lfs.currentdir()
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 d string: The directory to switch to.
function fs_lua.change_dir(d)
table.insert(dir_stack, lfs.currentdir())
d = dir.normalize(d)
return lfs.chdir(d)
end
--- Change directory to root.
-- Allows leaving a directory (e.g. for deleting it) in
-- a crossplatform way.
function fs_lua.change_dir_to_root()
local current = lfs.currentdir()
if not current or current == "" then
return false
end
table.insert(dir_stack, current)
lfs.chdir("/") -- works on Windows too
return true
end
--- Change working directory to the previous in the dir stack.
-- @return true if a pop ocurred, false if the stack was empty.
function fs_lua.pop_dir()
local d = table.remove(dir_stack)
if d then
lfs.chdir(d)
return true
else
return false
end
end
--- Create a directory if it does not already exist.
-- If any of the higher levels in the path name do not exist
-- too, they are created as well.
-- @param directory string: pathname of directory to create.
-- @return boolean or (boolean, string): true on success or (false, error message) on failure.
function fs_lua.make_dir(directory)
assert(type(directory) == "string")
directory = dir.normalize(directory)
local path = nil
if directory:sub(2, 2) == ":" then
path = directory:sub(1, 2)
directory = directory:sub(4)
else
if directory:match("^/") then
path = ""
end
end
for d in directory:gmatch("([^"..dir.separator.."]+)"..dir.separator.."*") do
path = path and path .. dir.separator .. d or d
local mode = lfs.attributes(path, "mode")
if not mode then
local ok, err = lfs.mkdir(path)
if not ok then
return false, err
end
ok, err = fs.chmod(path, cfg.perm_exec)
if not ok then
return false, err
end
elseif mode ~= "directory" then
return false, path.." is not a directory"
end
end
return true
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 d string: pathname of directory to remove.
function fs_lua.remove_dir_if_empty(d)
assert(d)
d = dir.normalize(d)
lfs.rmdir(d)
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 d string: pathname of directory to remove.
function fs_lua.remove_dir_tree_if_empty(d)
assert(d)
d = dir.normalize(d)
for i=1,10 do
lfs.rmdir(d)
d = dir.dir_name(d)
end
end
--- Copy a file.
-- @param src string: Pathname of source
-- @param dest string: Pathname of destination
-- @param perms string or nil: Permissions for destination file,
-- or nil to use the source filename permissions
-- @return boolean or (boolean, string): true on success, false on failure,
-- plus an error message.
function fs_lua.copy(src, dest, perms)
assert(src and dest)
src = dir.normalize(src)
dest = dir.normalize(dest)
local destmode = lfs.attributes(dest, "mode")
if destmode == "directory" then
dest = dir.path(dest, dir.base_name(src))
end
if not perms then perms = fs.get_permissions(src) end
local src_h, err = io.open(src, "rb")
if not src_h then return nil, err end
local dest_h, err = io.open(dest, "w+b")
if not dest_h then src_h:close() return nil, err end
while true do
local block = src_h:read(8192)
if not block then break end
dest_h:write(block)
end
src_h:close()
dest_h:close()
fs.chmod(dest, perms)
return true
end
--- Implementation function for recursive copy of directory contents.
-- Assumes paths are normalized.
-- @param src string: Pathname of source
-- @param dest string: Pathname of destination
-- @param perms string or nil: Optional permissions.
-- If not given, permissions of the source are copied over to the destination.
-- @return boolean or (boolean, string): true on success, false on failure
local function recursive_copy(src, dest, perms)
local srcmode = lfs.attributes(src, "mode")
if srcmode == "file" then
local ok = fs.copy(src, dest, perms)
if not ok then return false end
elseif srcmode == "directory" then
local subdir = dir.path(dest, dir.base_name(src))
local ok, err = fs.make_dir(subdir)
if not ok then return nil, err end
for file in lfs.dir(src) do
if file ~= "." and file ~= ".." then
local ok = recursive_copy(dir.path(src, file), subdir, perms)
if not ok then return false end
end
end
end
return true
end
--- Recursively copy the contents of a directory.
-- @param src string: Pathname of source
-- @param dest string: Pathname of destination
-- @param perms string or nil: Optional permissions.
-- @return boolean or (boolean, string): true on success, false on failure,
-- plus an error message.
function fs_lua.copy_contents(src, dest, perms)
assert(src and dest)
src = dir.normalize(src)
dest = dir.normalize(dest)
assert(lfs.attributes(src, "mode") == "directory")
for file in lfs.dir(src) do
if file ~= "." and file ~= ".." then
local ok = recursive_copy(dir.path(src, file), dest, perms)
if not ok then
return false, "Failed copying "..src.." to "..dest
end
end
end
return true
end
--- Implementation function for recursive removal of directories.
-- Assumes paths are normalized.
-- @param name string: Pathname of file
-- @return boolean or (boolean, string): true on success,
-- or nil and an error message on failure.
local function recursive_delete(name)
local ok = os.remove(name)
if ok then return true end
local pok, ok, err = pcall(function()
for file in lfs.dir(name) do
if file ~= "." and file ~= ".." then
local ok, err = recursive_delete(dir.path(name, file))
if not ok then return nil, err end
end
end
local ok, err = lfs.rmdir(name)
return ok, (not ok) and err
end)
if pok then
return ok, err
else
return pok, ok
end
end
--- Delete a file or a directory and all its contents.
-- @param name string: Pathname of source
-- @return nil
function fs_lua.delete(name)
name = dir.normalize(name)
recursive_delete(name)
end
--- Internal implementation function for fs.dir.
-- Yields a filename on each iteration.
-- @param at string: directory to list
-- @return nil
function fs_lua.dir_iterator(at)
for file in lfs.dir(at) do
if file ~= "." and file ~= ".." then
coroutine.yield(file)
end
end
end
--- Implementation function for recursive find.
-- Assumes paths are normalized.
-- @param cwd string: Current working directory in recursion.
-- @param prefix string: Auxiliary prefix string to form pathname.
-- @param result table: Array of strings where results are collected.
local function recursive_find(cwd, prefix, result)
for file in lfs.dir(cwd) do
if file ~= "." and file ~= ".." then
local item = prefix .. file
table.insert(result, item)
local pathname = dir.path(cwd, file)
if lfs.attributes(pathname, "mode") == "directory" then
recursive_find(pathname, item..dir_separator, result)
end
end
end
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 fs_lua.find(at)
assert(type(at) == "string" or not at)
if not at then
at = fs.current_dir()
end
at = dir.normalize(at)
if not fs.is_dir(at) then
return {}
end
local result = {}
recursive_find(at, "", result)
return result
end
--- Test for existance of a file.
-- @param file string: filename to test
-- @return boolean: true if file exists, false otherwise.
function fs_lua.exists(file)
assert(file)
file = dir.normalize(file)
return type(lfs.attributes(file)) == "table"
end
--- Test is pathname is a directory.
-- @param file string: pathname to test
-- @return boolean: true if it is a directory, false otherwise.
function fs_lua.is_dir(file)
assert(file)
file = dir.normalize(file)
return lfs.attributes(file, "mode") == "directory"
end
--- Test is pathname is a regular file.
-- @param file string: pathname to test
-- @return boolean: true if it is a file, false otherwise.
function fs_lua.is_file(file)
assert(file)
file = dir.normalize(file)
return lfs.attributes(file, "mode") == "file"
end
function fs_lua.set_time(file, time)
file = dir.normalize(file)
return lfs.touch(file, time)
end
end
---------------------------------------------------------------------
-- LuaZip functions
---------------------------------------------------------------------
if zip_ok then
function fs_lua.zip(zipfile, ...)
return lrzip.zip(zipfile, ...)
end
end
if unzip_ok then
--- 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 fs_lua.unzip(zipfile)
local zipfile, err = luazip.open(zipfile)
if not zipfile then return nil, err end
local files = zipfile:files()
local file = files()
repeat
if file.filename:sub(#file.filename) == "/" then
local ok, err = fs.make_dir(dir.path(fs.current_dir(), file.filename))
if not ok then return nil, err end
else
local base = dir.dir_name(file.filename)
if base ~= "" then
base = dir.path(fs.current_dir(), base)
if not fs.is_dir(base) then
local ok, err = fs.make_dir(base)
if not ok then return nil, err end
end
end
local rf, err = zipfile:open(file.filename)
if not rf then zipfile:close(); return nil, err end
local contents = rf:read("*a")
rf:close()
local wf, err = io.open(dir.path(fs.current_dir(), file.filename), "wb")
if not wf then zipfile:close(); return nil, err end
wf:write(contents)
wf:close()
end
file = files()
until not file
zipfile:close()
return true
end
end
---------------------------------------------------------------------
-- LuaSocket functions
---------------------------------------------------------------------
if socket_ok then
local ltn12 = require("ltn12")
local luasec_ok, https = pcall(require, "ssl.https")
local redirect_protocols = {
http = http,
https = luasec_ok and https,
}
local function request(url, method, http, loop_control)
local result = {}
local proxy = cfg.http_proxy
if type(proxy) ~= "string" then proxy = nil end
-- LuaSocket's http.request crashes when given URLs missing the scheme part.
if proxy and not proxy:find("://") then
proxy = "http://" .. proxy
end
if cfg.show_downloads then
io.write(method.." "..url.." ...\n")
end
local dots = 0
if cfg.connection_timeout and cfg.connection_timeout > 0 then
http.TIMEOUT = cfg.connection_timeout
end
local res, status, headers, err = http.request {
url = url,
proxy = proxy,
method = method,
redirect = false,
sink = ltn12.sink.table(result),
step = cfg.show_downloads and function(...)
io.write(".")
io.flush()
dots = dots + 1
if dots == 70 then
io.write("\n")
dots = 0
end
return ltn12.pump.step(...)
end,
headers = {
["user-agent"] = cfg.user_agent.." via LuaSocket"
},
}
if cfg.show_downloads then
io.write("\n")
end
if not res then
return nil, status
elseif status == 301 or status == 302 then
local location = headers.location
if location then
local protocol, rest = dir.split_url(location)
if redirect_protocols[protocol] then
if not loop_control then
loop_control = {}
elseif loop_control[location] then
return nil, "Redirection loop -- broken URL?"
end
loop_control[url] = true
return request(location, method, redirect_protocols[protocol], loop_control)
else
return nil, "URL redirected to unsupported protocol - install luasec to get HTTPS support.", "https"
end
end
return nil, err
elseif status ~= 200 then
return nil, err
else
return result, status, headers, err
end
end
local function http_request(url, http, cached)
if cached then
local tsfd = io.open(cached..".timestamp", "r")
if tsfd then
local timestamp = tsfd:read("*a")
tsfd:close()
local result, status, headers, err = request(url, "HEAD", http)
if status == 200 and headers["last-modified"] == timestamp then
return true
end
if not result then
return nil, status, headers
end
end
end
local result, status, headers, err = request(url, "GET", http)
if result then
if cached and headers["last-modified"] then
local tsfd = io.open(cached..".timestamp", "w")
if tsfd then
tsfd:write(headers["last-modified"])
tsfd:close()
end
end
return table.concat(result)
else
return nil, status, headers
end
end
local downloader_warning = false
--- 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, string): true and the filename on success,
-- false and the error message on failure.
function fs_lua.download(url, filename, cache)
assert(type(url) == "string")
assert(type(filename) == "string" or not filename)
filename = fs.absolute_name(filename or dir.base_name(url))
-- delegate to the configured downloader so we don't have to deal with whitelists
if cfg.no_proxy then
return fs.use_downloader(url, filename, cache)
end
local content, err, https_err
if util.starts_with(url, "http:") then
content, err, https_err = http_request(url, http, cache and filename)
elseif util.starts_with(url, "ftp:") then
content, err = ftp.get(url)
elseif util.starts_with(url, "https:") then
-- skip LuaSec when proxy is enabled since it is not supported
if luasec_ok and not cfg.https_proxy then
content, err = http_request(url, https, cache and filename)
else
https_err = true
end
else
err = "Unsupported protocol"
end
if https_err then
if not downloader_warning then
util.printerr("Warning: falling back to "..cfg.downloader.." - install luasec to get native HTTPS support")
downloader_warning = true
end
return fs.use_downloader(url, filename, cache)
end
if cache and content == true then
return true, filename
end
if not content then
return false, tostring(err)
end
local file = io.open(filename, "wb")
if not file then return false end
file:write(content)
file:close()
return true, filename
end
else --...if socket_ok == false then
function fs_lua.download(url, filename, cache)
return fs.use_downloader(url, filename, cache)
end
end
---------------------------------------------------------------------
-- MD5 functions
---------------------------------------------------------------------
if md5_ok then
-- Support the interface of lmd5 by lhf in addition to md5 by Roberto
-- and the keplerproject.
if not md5.sumhexa and md5.digest then
md5.sumhexa = function(msg)
return md5.digest(msg)
end
end
--- Get the MD5 checksum for a file.
-- @param file string: The file to be computed.
-- @return string: The MD5 checksum or nil + error
function fs_lua.get_md5(file)
file = fs.absolute_name(file)
local file_handler = io.open(file, "rb")
if not file_handler then return nil, "Failed to open file for reading: "..file end
local computed = md5.sumhexa(file_handler:read("*a"))
file_handler:close()
if computed then return computed end
return nil, "Failed to compute MD5 hash for file "..file
end
end
---------------------------------------------------------------------
-- POSIX functions
---------------------------------------------------------------------
if posix_ok then
local octal_to_rwx = {
["0"] = "---",
["1"] = "--x",
["2"] = "-w-",
["3"] = "-wx",
["4"] = "r--",
["5"] = "r-x",
["6"] = "rw-",
["7"] = "rwx",
}
function fs_lua.chmod(file, mode)
-- LuaPosix (as of 5.1.15) does not support octal notation...
if mode:sub(1,1) == "0" then
local new_mode = {}
for c in mode:sub(-3):gmatch(".") do
table.insert(new_mode, octal_to_rwx[c])
end
mode = table.concat(new_mode)
end
local err = posix.chmod(file, mode)
return err == 0
end
function fs_lua.get_permissions(file)
return posix.stat(file, "mode")
end
--- Create a temporary directory.
-- @param name string: name pattern to use for avoiding conflicts
-- when creating temporary directory.
-- @return string or (nil, string): name of temporary directory or (nil, error message) on failure.
function fs_lua.make_temp_dir(name)
assert(type(name) == "string")
name = dir.normalize(name)
return posix.mkdtemp((os.getenv("TMPDIR") or "/tmp") .. "/luarocks_" .. name:gsub(dir.separator, "_") .. "-XXXXXX")
end
end
---------------------------------------------------------------------
-- Other functions
---------------------------------------------------------------------
--- Apply a patch.
-- @param patchname string: The filename of the patch.
-- @param patchdata string or nil: The actual patch as a string.
function fs_lua.apply_patch(patchname, patchdata)
local p, all_ok = patch.read_patch(patchname, patchdata)
if not all_ok then
return nil, "Failed reading patch "..patchname
end
if p then
return patch.apply_patch(p, 1)
end
end
--- Move a file.
-- @param src string: Pathname of source
-- @param dest string: Pathname of destination
-- @param perms string or nil: Permissions for destination file,
-- or nil to use the source filename permissions.
-- @return boolean or (boolean, string): true on success, false on failure,
-- plus an error message.
function fs_lua.move(src, dest, perms)
assert(src and dest)
if fs.exists(dest) and not fs.is_dir(dest) then
return false, "File already exists: "..dest
end
local ok, err = fs.copy(src, dest, perms)
if not ok then
return false, err
end
fs.delete(src)
if fs.exists(src) then
return false, "Failed move: could not delete "..src.." after copy."
end
return true
end
--- Check if user has write permissions for the command.
-- Assumes the configuration variables under cfg have been previously set up.
-- @param flags table: the flags table passed to run() drivers.
-- @return boolean or (boolean, string): true on success, false on failure,
-- plus an error message.
function fs_lua.check_command_permissions(flags)
local root_dir = path.root_dir(cfg.rocks_dir)
local ok = true
local err = ""
for _, dir in ipairs { cfg.rocks_dir, root_dir } do
if fs.exists(dir) and not fs.is_writable(dir) then
ok = false
err = "Your user does not have write permissions in " .. dir
break
end
end
if ok and not fs.exists(root_dir) then
local root = fs.root_of(root_dir)
local parent = root_dir
repeat
parent = dir.dir_name(parent)
if parent == "" then
parent = root
end
until parent == root or fs.exists(parent)
if not fs.is_writable(parent) then
ok = false
err = root_dir.." does not exist and your user does not have write permissions in " .. parent
end
end
if ok then
return true
else
if flags["local"] then
err = err .. " \n-- please check your permissions."
else
err = err .. " \n-- you may want to run as a privileged user or use your local tree with --local."
end
return nil, err
end
end
--- Check whether a file is a Lua script
-- When the file can be succesfully compiled by the configured
-- Lua interpreter, it's considered to be a valid Lua file.
-- @param name filename of file to check
-- @return boolean true, if it is a Lua script, false otherwise
function fs_lua.is_lua(name)
name = name:gsub([[%\]],"/") -- normalize on fw slash to prevent escaping issues
local lua = fs.Q(dir.path(cfg.variables["LUA_BINDIR"], cfg.lua_interpreter)) -- get lua interpreter configured
-- execute on configured interpreter, might not be the same as the interpreter LR is run on
local result = fs.execute_string(lua..[[ -e "if loadfile(']]..name..[[') then os.exit() else os.exit(1) end"]])
return (result == true)
end
return fs_lua

View File

@ -0,0 +1,156 @@
--- Common fs operations implemented with third-party tools.
local tools = {}
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local cfg = require("luarocks.cfg")
local vars = cfg.variables
local dir_stack = {}
--- Obtain current directory.
-- Uses the module's internal directory stack.
-- @return string: the absolute pathname of the current directory.
function tools.current_dir()
local current = cfg.cache_pwd
if not current then
local pipe = io.popen(fs.quiet_stderr(fs.Q(vars.PWD)))
current = pipe:read("*l")
pipe:close()
cfg.cache_pwd = current
end
for _, directory in ipairs(dir_stack) do
current = fs.absolute_name(directory, current)
end
return current
end
--- Change the current directory.
-- Uses the module's internal directory 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 directory string: The directory to switch to.
-- @return boolean or (nil, string): true if successful, (nil, error message) if failed.
function tools.change_dir(directory)
assert(type(directory) == "string")
if fs.is_dir(directory) then
table.insert(dir_stack, directory)
return true
end
return nil, "directory not found: "..directory
end
--- Change directory to root.
-- Allows leaving a directory (e.g. for deleting it) in
-- a crossplatform way.
function tools.change_dir_to_root()
table.insert(dir_stack, "/")
end
--- Change working directory to the previous in the directory stack.
function tools.pop_dir()
local directory = table.remove(dir_stack)
return directory ~= nil
end
--- Run the given command.
-- The command is executed in the current directory in the directory stack.
-- @param cmd string: No quoting/escaping is applied to the command.
-- @return boolean: true if command succeeds (status code 0), false
-- otherwise.
function tools.execute_string(cmd)
local current = fs.current_dir()
if not current then return false end
cmd = fs.command_at(current, cmd)
local code = os.execute(cmd)
if code == 0 or code == true then
return true
else
return false
end
end
--- Internal implementation function for fs.dir.
-- Yields a filename on each iteration.
-- @param at string: directory to list
-- @return nil
function tools.dir_iterator(at)
local pipe = io.popen(fs.command_at(at, fs.Q(vars.LS)))
for file in pipe:lines() do
if file ~= "." and file ~= ".." then
coroutine.yield(file)
end
end
pipe:close()
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, string): true and the filename on success,
-- false and the error message on failure.
function tools.use_downloader(url, filename, cache)
assert(type(url) == "string")
assert(type(filename) == "string" or not filename)
filename = fs.absolute_name(filename or dir.base_name(url))
local ok
if cfg.downloader == "wget" then
local wget_cmd = fs.Q(vars.WGET).." "..vars.WGETNOCERTFLAG.." --no-cache --user-agent=\""..cfg.user_agent.." via wget\" --quiet "
if cfg.connection_timeout and cfg.connection_timeout > 0 then
wget_cmd = wget_cmd .. "--timeout="..tonumber(cfg.connection_timeout).." --tries=1 "
end
if cache then
-- --timestamping is incompatible with --output-document,
-- but that's not a problem for our use cases.
fs.change_dir(dir.dir_name(filename))
ok = fs.execute_quiet(wget_cmd.." --timestamping ", url)
fs.pop_dir()
elseif filename then
ok = fs.execute_quiet(wget_cmd.." --output-document ", filename, url)
else
ok = fs.execute_quiet(wget_cmd, url)
end
elseif cfg.downloader == "curl" then
local curl_cmd = fs.Q(vars.CURL).." "..vars.CURLNOCERTFLAG.." -f -L --user-agent \""..cfg.user_agent.." via curl\" "
if cfg.connection_timeout and cfg.connection_timeout > 0 then
curl_cmd = curl_cmd .. "--connect-timeout "..tonumber(cfg.connection_timeout).." "
end
ok = fs.execute_string(fs.quiet_stderr(curl_cmd..fs.Q(url).." > "..fs.Q(filename)))
end
if ok then
return true, filename
else
return false
end
end
local md5_cmd = {
md5sum = fs.Q(vars.MD5SUM),
openssl = fs.Q(vars.OPENSSL).." md5",
md5 = fs.Q(vars.MD5),
}
--- Get the MD5 checksum for a file.
-- @param file string: The file to be computed.
-- @return string: The MD5 checksum or nil + message
function tools.get_md5(file)
local cmd = md5_cmd[cfg.md5checker]
if not cmd then return nil, "no MD5 checker command configured" end
local pipe = io.popen(cmd.." "..fs.Q(fs.absolute_name(file)))
local computed = pipe:read("*a")
pipe:close()
if computed then
computed = computed:match("("..("%x"):rep(32)..")")
end
if computed then return computed end
return nil, "Failed to compute MD5 hash for file "..tostring(fs.absolute_name(file))
end
return tools

View File

@ -0,0 +1,135 @@
--- Unix implementation of filesystem and platform abstractions.
local unix = {}
local fs = require("luarocks.fs")
local cfg = require("luarocks.cfg")
local dir = require("luarocks.dir")
local util = require("luarocks.util")
--- Annotate command string for quiet execution.
-- @param cmd string: A command-line string.
-- @return string: The command-line, with silencing annotation.
function unix.quiet(cmd)
return cmd.." 1> /dev/null 2> /dev/null"
end
--- Annotate command string for execution with quiet stderr.
-- @param cmd string: A command-line string.
-- @return string: The command-line, with stderr silencing annotation.
function unix.quiet_stderr(cmd)
return cmd.." 2> /dev/null"
end
--- Quote argument for shell processing.
-- Adds single quotes and escapes.
-- @param arg string: Unquoted argument.
-- @return string: Quoted argument.
function unix.Q(arg)
assert(type(arg) == "string")
return "'" .. arg:gsub("'", "'\\''") .. "'"
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 unix.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
--- Return the root directory for the given path.
-- In Unix, root is always "/".
-- @param pathname string: pathname to use.
-- @return string: The root of the given pathname.
function unix.root_of(_)
return "/"
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.
-- @param name string: rock name to be used in loader context.
-- @param version string: rock version to be used in loader context.
-- @return boolean or (nil, string): True if succeeded, or nil and
-- an error message.
function unix.wrap_script(file, dest, name, version)
assert(type(file) == "string")
assert(type(dest) == "string")
local base = dir.base_name(file)
local wrapname = fs.is_dir(dest) and dest.."/"..base or dest
local lpath, lcpath = cfg.package_paths()
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")
local lua = dir.path(cfg.variables["LUA_BINDIR"], cfg.lua_interpreter)
local ppaths = "package.path="..util.LQ(lpath..";").."..package.path; package.cpath="..util.LQ(lcpath..";").."..package.cpath"
local addctx = "local k,l,_=pcall(require,"..util.LQ("luarocks.loader")..") _=k and l.add_context("..util.LQ(name)..","..util.LQ(version)..")"
wrapper:write('exec '..fs.Q(lua)..' -e '..fs.Q(ppaths)..' -e '..fs.Q(addctx)..' '..fs.Q(file)..' "$@"\n')
wrapper:close()
if fs.chmod(wrapname, cfg.perm_exec) 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.
function unix.is_actual_binary(filename)
if filename:match("%.lua$") then
return false
end
local file = io.open(filename)
if not file then
return true
end
local first = file:read(2)
file:close()
if not first then
util.printerr("Warning: could not read "..filename)
return true
end
return first ~= "#!"
end
function unix.copy_binary(filename, dest)
return fs.copy(filename, dest, cfg.perm_exec)
end
--- Move a file on top of the other.
-- The new file ceases to exist under its original name,
-- and takes over the name of the old file.
-- On Unix this is done through a single rename operation.
-- @param old_file The name of the original file,
-- which will be the new name of new_file.
-- @param new_file The name of the new file,
-- which will replace old_file.
-- @return boolean or (nil, string): True if succeeded, or nil and
-- an error message.
function unix.replace_file(old_file, new_file)
return os.rename(new_file, old_file)
end
function unix.tmpname()
return os.tmpname()
end
return unix

View File

@ -0,0 +1,237 @@
--- fs operations implemented with third-party tools for Unix platform abstractions.
local tools = {}
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local cfg = require("luarocks.cfg")
local vars = cfg.variables
--- Adds prefix to command to make it run from a directory.
-- @param directory string: Path to a directory.
-- @param cmd string: A command-line string.
-- @return string: The command-line with prefix.
function tools.command_at(directory, cmd)
return "cd " .. fs.Q(fs.absolute_name(directory)) .. " && " .. cmd
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 directory string: pathname of directory to create.
-- @return boolean: true on success, false on failure.
function tools.make_dir(directory)
assert(directory)
local ok, err = fs.execute(vars.MKDIR.." -p", directory)
if not ok then
err = "failed making directory "..directory
end
return ok, err
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 directory string: pathname of directory to remove.
function tools.remove_dir_if_empty(directory)
assert(directory)
fs.execute_quiet(vars.RMDIR, directory)
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 directory string: pathname of directory to remove.
function tools.remove_dir_tree_if_empty(directory)
assert(directory)
fs.execute_quiet(vars.RMDIR, "-p", directory)
end
--- Copy a file.
-- @param src string: Pathname of source
-- @param dest string: Pathname of destination
-- @param perm string or nil: Permissions for destination file,
-- @return boolean or (boolean, string): true on success, false on failure,
-- plus an error message.
function tools.copy(src, dest, perm)
assert(src and dest)
if fs.execute(vars.CP, src, dest) then
if perm then
if fs.is_dir(dest) then
dest = dir.path(dest, dir.base_name(src))
end
if fs.chmod(dest, perm) then
return true
else
return false, "Failed setting permissions of "..dest
end
end
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 tools.copy_contents(src, dest)
assert(src and dest)
if fs.execute_quiet(vars.CP.." -pPR "..fs.Q(src).."/* "..fs.Q(dest)) 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 nil
function tools.delete(arg)
assert(arg)
assert(arg:sub(1,1) == "/")
fs.execute_quiet(vars.RM, "-rf", arg)
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 tools.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(fs.command_at(at, fs.quiet_stderr(vars.FIND.." *")))
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 tools.zip(zipfile, ...)
return fs.execute(vars.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 tools.unzip(zipfile)
assert(zipfile)
return fs.execute_quiet(vars.UNZIP, zipfile)
end
--- Test is file/directory exists
-- @param file string: filename to test
-- @return boolean: true if file exists, false otherwise.
function tools.exists(file)
assert(file)
return fs.execute(vars.TEST, "-e", 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 tools.is_dir(file)
assert(file)
return fs.execute(vars.TEST, "-d", file)
end
--- Test is pathname is a regular file.
-- @param file string: pathname to test
-- @return boolean: true if it is a regular file, false otherwise.
function tools.is_file(file)
assert(file)
return fs.execute(vars.TEST, "-f", file)
end
function tools.chmod(pathname, mode)
if mode then
return fs.execute(vars.CHMOD, mode, pathname)
else
return false
end
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 tools.unpack_archive(archive)
assert(type(archive) == "string")
local pipe_to_tar = " | "..vars.TAR.." -xf -"
if not cfg.verbose then
pipe_to_tar = " 2> /dev/null"..fs.quiet(pipe_to_tar)
end
local ok
if archive:match("%.tar%.gz$") or archive:match("%.tgz$") then
ok = fs.execute_string(vars.GUNZIP.." -c "..fs.Q(archive)..pipe_to_tar)
elseif archive:match("%.tar%.bz2$") then
ok = fs.execute_string(vars.BUNZIP2.." -c "..fs.Q(archive)..pipe_to_tar)
elseif archive:match("%.zip$") then
ok = fs.execute_quiet(vars.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
return false, "Couldn't extract archive "..archive..": unrecognized filename extension"
end
if not ok then
return false, "Failed extracting "..archive
end
return true
end
function tools.get_permissions(filename)
local pipe = io.popen(vars.STAT.." "..vars.STATFLAG.." "..fs.Q(filename))
local ret = pipe:read("*l")
pipe:close()
return ret
end
function tools.browser(url)
return fs.execute(cfg.web_browser, url)
end
function tools.set_time(file, time)
file = dir.normalize(file)
return fs.execute(vars.TOUCH, "-d", "@"..tostring(time), file)
end
--- Create a temporary directory.
-- @param name string: name pattern to use for avoiding conflicts
-- when creating temporary directory.
-- @return string or (nil, string): name of temporary directory or (nil, error message) on failure.
function tools.make_temp_dir(name)
assert(type(name) == "string")
name = dir.normalize(name)
local template = (os.getenv("TMPDIR") or "/tmp") .. "/luarocks_" .. name:gsub(dir.separator, "_") .. "-XXXXXX"
local pipe = io.popen(vars.MKTEMP.." -d "..fs.Q(template))
local dirname = pipe:read("*l")
pipe:close()
if dirname and dirname:match("^/") then
return dirname
end
return nil, "Failed to create temporary directory "..tostring(dirname)
end
return tools

View File

@ -0,0 +1,266 @@
--- Windows implementation of filesystem and platform abstractions.
-- Download http://unxutils.sourceforge.net/ for Windows GNU utilities
-- used by this module.
local win32 = {}
local fs = require("luarocks.fs")
local cfg = require("luarocks.cfg")
local dir = require("luarocks.dir")
local util = require("luarocks.util")
math.randomseed(os.time())
-- Monkey patch io.popen and os.execute to make sure quoting
-- works as expected.
-- See http://lua-users.org/lists/lua-l/2013-11/msg00367.html
local _prefix = "type NUL && "
local _popen, _execute = io.popen, os.execute
io.popen = function(cmd, ...) return _popen(_prefix..cmd, ...) end
os.execute = function(cmd, ...) return _execute(_prefix..cmd, ...) end
--- Annotate command string for quiet execution.
-- @param cmd string: A command-line string.
-- @return string: The command-line, with silencing annotation.
function win32.quiet(cmd)
return cmd.." 2> NUL 1> NUL"
end
--- Annotate command string for execution with quiet stderr.
-- @param cmd string: A command-line string.
-- @return string: The command-line, with stderr silencing annotation.
function win32.quiet_stderr(cmd)
return cmd.." 2> NUL"
end
-- Split path into root and the rest.
-- Root part consists of an optional drive letter (e.g. "C:")
-- and an optional directory separator.
local function split_root(path)
local root = ""
if path:match("^.:") then
root = path:sub(1, 2)
path = path:sub(3)
end
if path:match("^[\\/]") then
root = path:sub(1, 1)
path = path:sub(2)
end
return root, path
end
--- Quote argument for shell processing. Fixes paths on Windows.
-- Adds double quotes and escapes.
-- @param arg string: Unquoted argument.
-- @return string: Quoted argument.
function win32.Q(arg)
assert(type(arg) == "string")
-- Use Windows-specific directory separator for paths.
-- Paths should be converted to absolute by now.
if split_root(arg) ~= "" then
arg = arg:gsub("/", "\\")
end
if arg == "\\" then
return '\\' -- CHDIR needs special handling for root dir
end
-- URLs and anything else
arg = arg:gsub('\\(\\*)"', '\\%1%1"')
arg = arg:gsub('\\+$', '%0%0')
arg = arg:gsub('"', '\\"')
arg = arg:gsub('(\\*)%%', '%1%1"%%"')
return '"' .. arg .. '"'
end
--- Quote argument for shell processing in batch files.
-- Adds double quotes and escapes.
-- @param arg string: Unquoted argument.
-- @return string: Quoted argument.
function win32.Qb(arg)
assert(type(arg) == "string")
-- Use Windows-specific directory separator for paths.
-- Paths should be converted to absolute by now.
if split_root(arg) ~= "" then
arg = arg:gsub("/", "\\")
end
if arg == "\\" then
return '\\' -- CHDIR needs special handling for root dir
end
-- URLs and anything else
arg = arg:gsub('\\(\\*)"', '\\%1%1"')
arg = arg:gsub('\\+$', '%0%0')
arg = arg:gsub('"', '\\"')
arg = arg:gsub('%%', '%%%%')
return '"' .. arg .. '"'
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 win32.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()
local root, rest = split_root(pathname)
if root:match("[\\/]$") then
-- It's an absolute path already.
return pathname
else
-- It's a relative path, join it with base path.
-- This drops drive letter from paths like "C:foo".
return relative_to .. "/" .. rest
end
end
--- Return the root directory for the given path.
-- For example, for "c:\hello", returns "c:\"
-- @param pathname string: pathname to use.
-- @return string: The root of the given pathname.
function win32.root_of(pathname)
return (split_root(fs.absolute_name(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.
-- @param name string: rock name to be used in loader context.
-- @param version string: rock version to be used in loader context.
-- @return boolean or (nil, string): True if succeeded, or nil and
-- an error message.
function win32.wrap_script(file, dest, name, version)
assert(type(file) == "string")
assert(type(dest) == "string")
local base = dir.base_name(file)
local wrapname = fs.is_dir(dest) and dest.."/"..base or dest
wrapname = wrapname..".bat"
local lpath, lcpath = cfg.package_paths()
lpath = util.remove_path_dupes(lpath, ";")
lcpath = util.remove_path_dupes(lcpath, ";")
local wrapper = io.open(wrapname, "w")
if not wrapper then
return nil, "Could not open "..wrapname.." for writing."
end
wrapper:write("@echo off\n")
local lua = dir.path(cfg.variables["LUA_BINDIR"], cfg.lua_interpreter)
local ppaths = "package.path="..util.LQ(lpath..";").."..package.path; package.cpath="..util.LQ(lcpath..";").."..package.cpath"
local addctx = "local k,l,_=pcall(require,"..util.LQ("luarocks.loader")..") _=k and l.add_context("..util.LQ(name)..","..util.LQ(version)..")"
wrapper:write(fs.Qb(lua)..' -e '..fs.Qb(ppaths)..' -e '..fs.Qb(addctx)..' '..fs.Qb(file)..' %*\n')
wrapper:write("exit /b %ERRORLEVEL%\n")
wrapper:close()
return true
end
function win32.is_actual_binary(name)
name = name:lower()
if name:match("%.bat$") or name:match("%.exe$") then
return true
end
return false
end
function win32.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 = dir.base_name(filename)
dest = dir.dir_name(dest)
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
function win32.chmod(filename, mode)
return true
end
function win32.get_permissions(filename)
return ""
end
--- Move a file on top of the other.
-- The new file ceases to exist under its original name,
-- and takes over the name of the old file.
-- On Windows this is done by removing the original file and
-- renaming the new file to its original name.
-- @param old_file The name of the original file,
-- which will be the new name of new_file.
-- @param new_file The name of the new file,
-- which will replace old_file.
-- @return boolean or (nil, string): True if succeeded, or nil and
-- an error message.
function win32.replace_file(old_file, new_file)
os.remove(old_file)
return os.rename(new_file, old_file)
end
--- Test is file/dir is writable.
-- Warning: testing if a file/dir is writable does not guarantee
-- that it will remain writable and therefore it is no replacement
-- for checking the result of subsequent operations.
-- @param file string: filename to test
-- @return boolean: true if file exists, false otherwise.
function win32.is_writable(file)
assert(file)
file = dir.normalize(file)
local result
local tmpname = 'tmpluarockstestwritable.deleteme'
if fs.is_dir(file) then
local file2 = dir.path(file, tmpname)
local fh = io.open(file2, 'wb')
result = fh ~= nil
if fh then fh:close() end
if result then
-- the above test might give a false positive when writing to
-- c:\program files\ because of VirtualStore redirection on Vista and up
-- So check whether it's really there
result = fs.exists(file2)
end
os.remove(file2)
else
local fh = io.open(file, 'r+b')
result = fh ~= nil
if fh then fh:close() end
end
return result
end
--- Create a temporary directory.
-- @param name string: name pattern to use for avoiding conflicts
-- when creating temporary directory.
-- @return string or (nil, string): name of temporary directory or (nil, error message) on failure.
function win32.make_temp_dir(name)
assert(type(name) == "string")
name = dir.normalize(name)
local temp_dir = os.getenv("TMP") .. "/luarocks_" .. name:gsub(dir.separator, "_") .. "-" .. tostring(math.floor(math.random() * 10000))
local ok, err = fs.make_dir(temp_dir)
if ok then
return temp_dir
else
return nil, err
end
end
function win32.tmpname()
return os.getenv("TMP")..os.tmpname()
end
return win32

View File

@ -0,0 +1,227 @@
--- fs operations implemented with third-party tools for Windows platform abstractions.
-- Download http://unxutils.sourceforge.net/ for Windows GNU utilities
-- used by this module.
local tools = {}
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local cfg = require("luarocks.cfg")
local vars = cfg.variables
--- Adds prefix to command to make it run from a directory.
-- @param directory string: Path to a directory.
-- @param cmd string: A command-line string.
-- @return string: The command-line with prefix.
function tools.command_at(directory, cmd)
local drive = directory:match("^([A-Za-z]:)")
cmd = "cd " .. fs.Q(directory) .. " & " .. cmd
if drive then
cmd = drive .. " & " .. cmd
end
return cmd
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 directory string: pathname of directory to create.
-- @return boolean: true on success, false on failure.
function tools.make_dir(directory)
assert(directory)
directory = dir.normalize(directory)
fs.execute_quiet(fs.Q(vars.MKDIR).." -p ", directory)
if not fs.is_dir(directory) then
return false, "failed making directory "..directory
end
return true
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 directory string: pathname of directory to remove.
function tools.remove_dir_if_empty(directory)
assert(directory)
fs.execute_quiet(fs.Q(vars.RMDIR), directory)
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 directory string: pathname of directory to remove.
function tools.remove_dir_tree_if_empty(directory)
assert(directory)
fs.execute_quiet(fs.Q(vars.RMDIR), directory)
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 tools.copy(src, dest)
assert(src and dest)
if dest:match("[/\\]$") then dest = dest:sub(1, -2) end
local ok = fs.execute(fs.Q(vars.CP), src, dest)
if ok 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 tools.copy_contents(src, dest)
assert(src and dest)
if fs.execute_quiet(fs.Q(vars.CP), "-dR", src.."\\*.*", dest) 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 nil
function tools.delete(arg)
assert(arg)
assert(arg:match("^[a-zA-Z]?:?[\\/]"))
fs.execute_quiet("if exist "..fs.Q(arg.."\\").." ( RMDIR /S /Q "..fs.Q(arg).." ) else ( DEL /Q /F "..fs.Q(arg).." )")
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 tools.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(fs.command_at(at, fs.quiet_stderr(fs.Q(vars.FIND))))
for file in pipe:lines() do
-- Windows find is a bit different
local first_two = file:sub(1,2)
if first_two == ".\\" or first_two == "./" then file=file:sub(3) end
if file ~= "." then
table.insert(result, (file:gsub("\\", "/")))
end
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 tools.zip(zipfile, ...)
return fs.execute_quiet(fs.Q(vars.SEVENZ).." -aoa a -tzip", 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 tools.unzip(zipfile)
assert(zipfile)
return fs.execute_quiet(fs.Q(vars.SEVENZ).." -aoa x", zipfile)
end
--- Test is pathname is a directory.
-- @param file string: pathname to test
-- @return boolean: true if it is a directory, false otherwise.
function tools.is_dir(file)
assert(file)
return fs.execute_quiet("if not exist " .. fs.Q(file.."\\").." invalidcommandname")
end
--- Test is pathname is a regular file.
-- @param file string: pathname to test
-- @return boolean: true if it is a regular file, false otherwise.
function tools.is_file(file)
assert(file)
return fs.execute(fs.Q(vars.TEST).." -f", file)
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)
return fs.execute_quiet(fs.Q(vars.SEVENZ).." -aoa x", archive)
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 tools.unpack_archive(archive)
assert(type(archive) == "string")
local ok
local sevenzx = fs.Q(vars.SEVENZ).." -aoa x"
if archive:match("%.tar%.gz$") then
ok = gunzip(archive)
if ok then
ok = fs.execute_quiet(sevenzx, strip_extension(archive))
end
elseif archive:match("%.tgz$") then
ok = gunzip(archive)
if ok then
ok = fs.execute_quiet(sevenzx, strip_extension(archive)..".tar")
end
elseif archive:match("%.tar%.bz2$") then
ok = fs.execute_quiet(sevenzx, archive)
if ok then
ok = fs.execute_quiet(sevenzx, strip_extension(archive))
end
elseif archive:match("%.zip$") then
ok = fs.execute_quiet(sevenzx, 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
return false, "Couldn't extract archive "..archive..": unrecognized filename extension"
end
if not ok then
return false, "Failed extracting "..archive
end
return true
end
--- Test for existance of a file.
-- @param file string: filename to test
-- @return boolean: true if file exists, false otherwise.
function tools.exists(file)
assert(file)
return fs.execute_quiet("if not exist " .. fs.Q(file) .. " invalidcommandname")
end
function tools.browser(url)
return fs.execute(cfg.web_browser..' "Starting docs..." '..fs.Q(url))
end
return tools

View File

@ -0,0 +1,118 @@
--- 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.
local help = {}
local util = require("luarocks.util")
local cfg = require("luarocks.cfg")
local dir = require("luarocks.dir")
local program = util.this_program("luarocks")
util.add_run_function(help)
help.help_summary = "Help on commands. Type '"..program.." help <command>' for more."
help.help_arguments = "[<command>]"
help.help = [[
<command> is the command to show help for.
]]
local function print_banner()
util.printout("\nLuaRocks "..cfg.program_version..", a module deployment system for Lua")
end
local function print_section(section)
util.printout("\n"..section)
end
local function get_status(status)
if status then
return "ok"
else
return "not found"
end
end
--- 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 help.command(flags, command)
if not command then
local conf = cfg.which_config()
print_banner()
print_section("NAME")
util.printout("\t"..program..[[ - ]]..program_description)
print_section("SYNOPSIS")
util.printout("\t"..program..[[ [--from=<server> | --only-from=<server>] [--to=<tree>] [VAR=VALUE]... <command> [<argument>] ]])
print_section("GENERAL OPTIONS")
util.printout([[
These apply to all commands, as appropriate:
--server=<server> Fetch rocks/rockspecs from this server
(takes priority over config file)
--only-server=<server> Fetch rocks/rockspecs from this server only
(overrides any entries in the config file)
--only-sources=<url> Restrict downloads to paths matching the
given URL.
--tree=<tree> Which tree to operate on.
--local Use the tree in the user's home directory.
To enable it, see ']]..program..[[ help path'.
--verbose Display verbose output of commands executed.
--timeout=<seconds> Timeout on network operations, in seconds.
0 means no timeout (wait forever).
Default is ]]..tostring(cfg.connection_timeout)..[[.]])
print_section("VARIABLES")
util.printout([[
Variables from the "variables" table of the configuration file
can be overriden with VAR=VALUE assignments.]])
print_section("COMMANDS")
for name, command in util.sortedpairs(commands) do
local cmd = require(command)
util.printout("", name)
util.printout("\t", cmd.help_summary)
end
print_section("CONFIGURATION")
util.printout("\tLua version: " .. cfg.lua_version)
util.printout("\tConfiguration files:")
util.printout("\t\tSystem: ".. dir.normalize(conf.system.file) .. " (" .. get_status(conf.system.ok) ..")")
if conf.user.file then
util.printout("\t\tUser : ".. dir.normalize(conf.user.file) .. " (" .. get_status(conf.user.ok) ..")\n")
else
util.printout("\t\tUser : disabled in this LuaRocks installation.\n")
end
util.printout("\tRocks trees in use: ")
for _, tree in ipairs(cfg.rocks_trees) do
if type(tree) == "string" then
util.printout("\t\t"..dir.normalize(tree))
else
local name = tree.name and " (\""..tree.name.."\")" or ""
util.printout("\t\t"..dir.normalize(tree.root)..name)
end
end
else
command = command:gsub("-", "_")
local cmd = commands[command] and require(commands[command])
if cmd then
local arguments = cmd.help_arguments or "<argument>"
print_banner()
print_section("NAME")
util.printout("\t"..program.." "..command.." - "..cmd.help_summary)
print_section("SYNOPSIS")
util.printout("\t"..program.." "..command.." "..arguments)
print_section("DESCRIPTION")
util.printout("",(cmd.help:gsub("\n","\n\t"):gsub("\n\t$","")))
print_section("SEE ALSO")
util.printout("","'"..program.." help' for general options and configuration.\n")
else
return nil, "Unknown command: "..command
end
end
return true
end
return help

View File

@ -0,0 +1,186 @@
--- Module which builds the index.html page to be used in rocks servers.
local index = {}
package.loaded["luarocks.index"] = index
local util = require("luarocks.util")
local fs = require("luarocks.fs")
local deps = require("luarocks.deps")
local persist = require("luarocks.persist")
local dir = require("luarocks.dir")
local manif = require("luarocks.manif")
local ext_url_target = ' target="_blank"'
local index_header = [[
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Available rocks</title>
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
<style>
body {
background-color: white;
font-family: "bitstream vera sans", "verdana", "sans";
font-size: 14px;
}
a {
color: #0000c0;
text-decoration: none;
}
a.pkg {
color: black;
}
a:hover {
text-decoration: underline;
}
td.main {
border-style: none;
}
blockquote {
font-size: 12px;
}
td.package {
background-color: #f0f0f0;
vertical-align: top;
}
td.spacer {
height: 5px;
}
td.version {
background-color: #d0d0d0;
vertical-align: top;
text-align: left;
padding: 5px;
width: 100px;
}
p.manifest {
font-size: 8px;
}
</style>
</head>
<body>
<h1>Available rocks</h1>
<p>
Lua modules available from this location for use with <a href="http://www.luarocks.org">LuaRocks</a>:
</p>
<table class="main">
]]
local index_package_begin = [[
<td class="package">
<p><a name="$anchor"></a><a href="#$anchor" class="pkg"><b>$package</b></a> - $summary<br/>
</p><blockquote><p>$detailed<br/>
$externaldependencies
<font size="-1"><a href="$original">latest sources</a> $homepage | License: $license</font></p>
</blockquote></a></td>
<td class="version">
]]
local index_package_end = [[
</td></tr>
<tr><td colspan="2" class="spacer"></td></tr>
]]
local index_footer_begin = [[
</table>
<p class="manifest">
<a href="manifest">manifest file</a>
]]
local index_manifest_ver = [[
&bull; <a href="manifest-$VER">Lua $VER manifest file</a> (<a href="manifest-$VER.zip">zip</a>)
]]
local index_footer_end = [[
</p>
</body>
</html>
]]
function index.format_external_dependencies(rockspec)
if rockspec.external_dependencies then
local deplist = {}
local listed_set = {}
local plats = nil
for name, desc in util.sortedpairs(rockspec.external_dependencies) do
if name ~= "platforms" then
table.insert(deplist, name:lower())
listed_set[name] = true
else
plats = desc
end
end
if plats then
for plat, entries in util.sortedpairs(plats) do
for name, desc in util.sortedpairs(entries) do
if not listed_set[name] then
table.insert(deplist, name:lower() .. " (on "..plat..")")
end
end
end
end
return '<p><b>External dependencies:</b> ' .. table.concat(deplist, ',&nbsp;').. '</p>'
else
return ""
end
end
function index.make_index(repo)
if not fs.is_dir(repo) then
return nil, "Cannot access repository at "..repo
end
local manifest = manif.load_manifest(repo)
local out = io.open(dir.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_begin
for version, data in util.sortedpairs(version_list, deps.compare_versions) do
local versions = {}
output = output..version..':&nbsp;'
table.sort(data, function(a,b) return a.arch < b.arch end)
for _, item in ipairs(data) do
local file
if item.arch == 'rockspec' then
file = ("%s-%s.rockspec"):format(package, version)
if not latest_rockspec then latest_rockspec = file end
else
file = ("%s-%s.%s.rock"):format(package, version, item.arch)
end
table.insert(versions, '<a href="'..file..'">'..item.arch..'</a>')
end
output = output .. table.concat(versions, ',&nbsp;') .. '<br/>'
end
output = output .. index_package_end
if latest_rockspec then
local rockspec = persist.load_into_table(dir.path(repo, latest_rockspec))
local descript = rockspec.description or {}
local vars = {
anchor = package,
package = rockspec.package,
original = rockspec.source.url,
summary = descript.summary or "",
detailed = descript.detailed or "",
license = descript.license or "N/A",
homepage = descript.homepage and ('| <a href="'..descript.homepage..'"'..ext_url_target..'>project homepage</a>') or "",
externaldependencies = index.format_external_dependencies(rockspec)
}
vars.detailed = vars.detailed:gsub("\n\n", "</p><p>"):gsub("%s+", " ")
vars.detailed = vars.detailed:gsub("(https?://[a-zA-Z0-9%.%%-_%+%[%]=%?&/$@;:]+)", '<a href="%1"'..ext_url_target..'>%1</a>')
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_begin)
for ver in util.lua_versions() do
out:write((index_manifest_ver:gsub("$VER", ver)))
end
out:write(index_footer_end)
out:close()
end
return index

View File

@ -0,0 +1,188 @@
--- Module implementing the LuaRocks "install" command.
-- Installs binary rocks.
local install = {}
package.loaded["luarocks.install"] = install
local path = require("luarocks.path")
local repos = require("luarocks.repos")
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 remove = require("luarocks.remove")
local cfg = require("luarocks.cfg")
util.add_run_function(install)
install.help_summary = "Install a rock."
install.help_arguments = "{<rock>|<name> [<version>]}"
install.help = [[
Argument may be the name of a rock to be fetched from a repository
or a filename of a locally available rock.
--keep Do not remove previously installed versions of the
rock after installing a new one. This behavior can
be made permanent by setting keep_other_versions=true
in the configuration file.
--only-deps Installs only the dependencies of the rock.
]]..util.deps_mode_help()
--- Install a binary rock.
-- @param rock_file string: local or remote filename of a rock.
-- @param deps_mode: string: Which trees to check dependencies for:
-- "one" for the current default tree, "all" for all trees,
-- "order" for all trees with priority >= the current default, "none" for no trees.
-- @return (string, string) or (nil, string, [string]): Name and version of
-- installed rock if succeeded or nil and an error message followed by an error code.
function install.install_binary_rock(rock_file, deps_mode)
assert(type(rock_file) == "string")
local name, version, arch = path.parse_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, "arch"
end
if repos.is_installed(name, version) then
repos.delete_version(name, version, deps_mode)
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, errcode = fetch.fetch_and_unpack_rock(rock_file, path.install_dir(name, version))
if not ok then return nil, err, errcode end
local rockspec, err, errcode = fetch.load_rockspec(path.rockspec_file(name, version))
if err then
return nil, "Failed loading rockspec for installed package: "..err, errcode
end
if deps_mode == "none" then
util.printerr("Warning: skipping dependency checks.")
else
ok, err, errcode = deps.check_external_deps(rockspec, "install")
if err then return nil, err, errcode end
end
-- For compatibility with .rock files built with LuaRocks 1
if not fs.exists(path.rock_manifest_file(name, version)) then
ok, err = manif.make_rock_manifest(name, version)
if err then return nil, err end
end
if deps_mode ~= "none" then
ok, err, errcode = deps.fulfill_dependencies(rockspec, deps_mode)
if err then return nil, err, errcode end
end
ok, err = repos.deploy_files(name, version, repos.should_wrap_bin_scripts(rockspec), deps_mode)
if err then return nil, err end
util.remove_scheduled_function(rollback)
rollback = util.schedule_function(function()
repos.delete_version(name, version, deps_mode)
end)
ok, err = repos.run_hook(rockspec, "post_install")
if err then return nil, err end
util.announce_install(rockspec)
util.remove_scheduled_function(rollback)
return name, version
end
--- Installs the dependencies of a binary rock.
-- @param rock_file string: local or remote filename of a rock.
-- @param deps_mode: string: Which trees to check dependencies for:
-- "one" for the current default tree, "all" for all trees,
-- "order" for all trees with priority >= the current default, "none" for no trees.
-- @return (string, string) or (nil, string, [string]): Name and version of
-- the rock whose dependencies were installed if succeeded or nil and an error message
-- followed by an error code.
function install.install_binary_rock_deps(rock_file, deps_mode)
assert(type(rock_file) == "string")
local name, version, arch = path.parse_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, "arch"
end
local ok, err, errcode = fetch.fetch_and_unpack_rock(rock_file, path.install_dir(name, version))
if not ok then return nil, err, errcode end
local rockspec, err, errcode = fetch.load_rockspec(path.rockspec_file(name, version))
if err then
return nil, "Failed loading rockspec for installed package: "..err, errcode
end
ok, err, errcode = deps.fulfill_dependencies(rockspec, deps_mode)
if err then return nil, err, errcode end
util.printout()
util.printout("Successfully installed dependencies for " ..name.." "..version)
return name, version
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, exitcode): True if installation was
-- successful, nil and an error message otherwise. exitcode is optionally returned.
function install.command(flags, name, version)
if type(name) ~= "string" then
return nil, "Argument missing. "..util.see_help("install")
end
local ok, err = fs.check_command_permissions(flags)
if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end
if name:match("%.rockspec$") or name:match("%.src%.rock$") then
local build = require("luarocks.build")
return build.command(flags, name)
elseif name:match("%.rock$") then
if flags["only-deps"] then
ok, err = install.install_binary_rock_deps(name, deps.get_deps_mode(flags))
else
ok, err = install.install_binary_rock(name, deps.get_deps_mode(flags))
end
if not ok then return nil, err end
name, version = ok, err
if (not flags["only-deps"]) and (not flags["keep"]) and not cfg.keep_other_versions then
local ok, err = remove.remove_other_versions(name, version, flags["force"], flags["force-fast"])
if not ok then util.printerr(err) end
end
manif.check_dependencies(nil, deps.get_deps_mode(flags))
return name, version
else
local search = require("luarocks.search")
local url, err = search.find_suitable_rock(search.make_query(name:lower(), version))
if not url then
return nil, err
end
util.printout("Installing "..url)
return install.command(flags, url)
end
end
return install

View File

@ -0,0 +1,55 @@
--- Module implementing the LuaRocks "lint" command.
-- Utility function that checks syntax of the rockspec.
local lint = {}
package.loaded["luarocks.lint"] = lint
local util = require("luarocks.util")
local download = require("luarocks.download")
local fetch = require("luarocks.fetch")
util.add_run_function(lint)
lint.help_summary = "Check syntax of a rockspec."
lint.help_arguments = "<rockspec>"
lint.help = [[
This is a utility function that checks the syntax of a rockspec.
It returns success or failure if the text of a rockspec is
syntactically correct.
]]
function lint.command(flags, input)
if not input then
return nil, "Argument missing. "..util.see_help("lint")
end
local filename = input
if not input:match(".rockspec$") then
local err
filename, err = download.download("rockspec", input:lower())
if not filename then
return nil, err
end
end
local rs, err = fetch.load_local_rockspec(filename)
if not rs then
return nil, "Failed loading rockspec: "..err
end
local ok = true
-- This should have been done in the type checker,
-- but it would break compatibility of other commands.
-- Making 'lint' alone be stricter shouldn't be a problem,
-- because extra-strict checks is what lint-type commands
-- are all about.
if not rs.description.license then
util.printerr("Rockspec has no license field.")
ok = false
end
return ok, ok or filename.." failed consistency checks."
end
return lint

View File

@ -0,0 +1,97 @@
--- Module implementing the LuaRocks "list" command.
-- Lists currently installed rocks.
local list = {}
package.loaded["luarocks.list"] = list
local search = require("luarocks.search")
local deps = require("luarocks.deps")
local cfg = require("luarocks.cfg")
local util = require("luarocks.util")
local path = require("luarocks.path")
util.add_run_function(list)
list.help_summary = "List currently installed rocks."
list.help_arguments = "[--porcelain] <filter>"
list.help = [[
<filter> is a substring of a rock name to filter by.
--outdated List only rocks for which there is a
higher version available in the rocks server.
--porcelain Produce machine-friendly output.
]]
local function check_outdated(trees, query)
local results_installed = {}
for _, tree in ipairs(trees) do
search.manifest_search(results_installed, path.rocks_dir(tree), query)
end
local outdated = {}
for name, versions in util.sortedpairs(results_installed) do
versions = util.keys(versions)
table.sort(versions, deps.compare_versions)
local latest_installed = versions[1]
local query_available = search.make_query(name:lower())
query.exact_name = true
local results_available, err = search.search_repos(query_available)
if results_available[name] then
local available_versions = util.keys(results_available[name])
table.sort(available_versions, deps.compare_versions)
local latest_available = available_versions[1]
local latest_available_repo = results_available[name][latest_available][1].repo
if deps.compare_versions(latest_available, latest_installed) then
table.insert(outdated, { name = name, installed = latest_installed, available = latest_available, repo = latest_available_repo })
end
end
end
return outdated
end
local function list_outdated(trees, query, porcelain)
util.title("Outdated rocks:", porcelain)
local outdated = check_outdated(trees, query)
for _, item in ipairs(outdated) do
if porcelain then
util.printout(item.name, item.installed, item.available, item.repo)
else
util.printout(item.name)
util.printout(" "..item.installed.." < "..item.available.." at "..item.repo)
util.printout()
end
end
return true
end
--- 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 list.command(flags, filter, version)
local query = search.make_query(filter and filter:lower() or "", version)
query.exact_name = false
local trees = cfg.rocks_trees
if flags["tree"] then
trees = { flags["tree"] }
end
if flags["outdated"] then
return list_outdated(trees, query, flags["porcelain"])
end
local results = {}
for _, tree in ipairs(trees) do
local ok, err, errcode = search.manifest_search(results, path.rocks_dir(tree), query)
if not ok and errcode ~= "open" then
util.warning(err)
end
end
util.title("Installed rocks:", flags["porcelain"])
search.print_results(results, flags["porcelain"])
return true
end
return list

View File

@ -0,0 +1,249 @@
--- A module which installs a Lua package loader that is LuaRocks-aware.
-- This loader uses dependency information from the LuaRocks tree to load
-- correct versions of modules. It does this by constructing a "context"
-- table in the environment, which records which versions of packages were
-- used to load previous modules, so that the loader chooses versions
-- that are declared to be compatible with the ones loaded earlier.
local loaders = package.loaders or package.searchers
local package, require, ipairs, table, type, next, tostring, error =
package, require, ipairs, table, type, next, tostring, error
local unpack = unpack or table.unpack
--module("luarocks.loader")
local loader = {}
package.loaded["luarocks.loader"] = loader
local cfg = require("luarocks.cfg")
cfg.init_package_paths()
local path = require("luarocks.path")
local manif_core = require("luarocks.manif_core")
local deps = require("luarocks.deps")
local util = require("luarocks.util")
-- Workaround for wrappers produced by older versions of LuaRocks
local temporary_global = false
if luarocks then
-- The site_config.lua file generated by old versions uses module(),
-- so it produces a global `luarocks` table. Since we have the table,
-- add the `loader` field to make the old wrappers happy.
luarocks.loader = loader
else
-- When a new version is installed on top of an old version,
-- site_config.lua may be replaced, and then it no longer creates
-- a global.
-- Detect when being called via -lluarocks.loader; this is
-- most likely a wrapper.
local info = debug.getinfo(2, "nS")
if info.what == "C" and not info.name then
luarocks = { loader = loader }
temporary_global = true
-- For the other half of this hack,
-- see the next use of `temporary_global` below.
end
end
loader.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.
loader.rocks_trees = nil
local function load_rocks_trees()
local any_ok = false
local trees = {}
for _, tree in ipairs(cfg.rocks_trees) do
local manifest, err = manif_core.load_local_manifest(path.rocks_dir(tree))
if manifest then
any_ok = true
table.insert(trees, {tree=tree, manifest=manifest})
end
end
if not any_ok then
loader.rocks_trees = false
return false
end
loader.rocks_trees = trees
return true
end
--- Process the dependencies of a package to determine its dependency
-- chain for loading modules.
-- @param name string: The name of an installed rock.
-- @param version string: The version of the rock, in string format
function loader.add_context(name, version)
-- assert(type(name) == "string")
-- assert(type(version) == "string")
if temporary_global then
-- The first thing a wrapper does is to call add_context.
-- From here on, it's safe to clean the global environment.
luarocks = nil
temporary_global = false
end
if loader.context[name] then
return
end
loader.context[name] = version
if not loader.rocks_trees and not load_rocks_trees() then
return nil
end
for _, tree in ipairs(loader.rocks_trees) do
local manifest = tree.manifest
local pkgdeps
if manifest.dependencies and manifest.dependencies[name] then
pkgdeps = manifest.dependencies[name][version]
end
if not pkgdeps then
return nil
end
for _, dep in ipairs(pkgdeps) do
local pkg, constraints = dep.name, dep.constraints
for _, tree in ipairs(loader.rocks_trees) do
local entries = tree.manifest.repository[pkg]
if entries then
for version, pkgs in util.sortedpairs(entries, deps.compare_versions) do
if (not constraints) or deps.match_constraints(deps.parse_version(version), constraints) then
loader.add_context(pkg, version)
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
--- Request module to be loaded through other loaders,
-- once the proper name of the module has been determined.
-- For example, in case the module "socket.core" has been requested
-- to the LuaRocks loader and it determined based on context that
-- the version 2.0.2 needs to be loaded and it is not the current
-- version, the module requested for the other loaders will be
-- "socket.core_2_0_2".
-- @param module The module name requested by the user, such as "socket.core"
-- @param name The rock name, such as "luasocket"
-- @param version The rock version, such as "2.0.2-1"
-- @param module_name The actual module name, such as "socket.core" or "socket.core_2_0_2".
-- @return table or (nil, string): The module table as returned by some other loader,
-- or nil followed by an error message if no other loader managed to load the module.
local function call_other_loaders(module, name, version, module_name)
for i, a_loader in ipairs(loaders) do
if a_loader ~= loader.luarocks_loader then
local results = { a_loader(module_name) }
if type(results[1]) == "function" then
return unpack(results)
end
end
end
return "Failed loading module "..module.." in LuaRocks rock "..name.." "..version
end
--- Search for a module in the rocks trees
-- @param module string: module name (eg. "socket.core")
-- @param filter_file_name function(string, string, string, string, number):
-- a function that takes the module file name (eg "socket/core.so"), the rock name
-- (eg "luasocket"), the version (eg "2.0.2-1"), the path of the rocks tree
-- (eg "/usr/local"), and the numeric index of the matching entry, so the
-- filter function can know if the matching module was the first entry or not.
-- @return string, string, string, (string or table):
-- * name of the rock containing the module (eg. "luasocket")
-- * version of the rock (eg. "2.0.2-1")
-- * return value of filter_file_name
-- * tree of the module (string or table in `rocks_trees` format)
local function select_module(module, filter_file_name)
--assert(type(module) == "string")
--assert(type(filter_module_name) == "function")
if not loader.rocks_trees and not load_rocks_trees() then
return nil
end
local providers = {}
for _, tree in ipairs(loader.rocks_trees) do
local entries = tree.manifest.modules[module]
if entries then
for i, entry in ipairs(entries) do
local name, version = entry:match("^([^/]*)/(.*)$")
local file_name = tree.manifest.repository[name][version][1].modules[module]
if type(file_name) ~= "string" then
error("Invalid data in manifest file for module "..tostring(module).." (invalid data for "..tostring(name).." "..tostring(version)..")")
end
file_name = filter_file_name(file_name, name, version, tree.tree, i)
if loader.context[name] == version then
return name, version, file_name
end
version = deps.parse_version(version)
table.insert(providers, {name = name, version = version, module_name = file_name, tree = tree})
end
end
end
if next(providers) then
table.sort(providers, sort_versions)
local first = providers[1]
return first.name, first.version.string, first.module_name, first.tree
end
end
--- Search for a module
-- @param module string: module name (eg. "socket.core")
-- @return string, string, string, (string or table):
-- * name of the rock containing the module (eg. "luasocket")
-- * version of the rock (eg. "2.0.2-1")
-- * name of the module (eg. "socket.core", or "socket.core_2_0_2" if file is stored versioned).
-- * tree of the module (string or table in `rocks_trees` format)
local function pick_module(module)
return
select_module(module, function(file_name, name, version, tree, i)
if i > 1 then
file_name = path.versioned_name(file_name, "", name, version)
end
return path.path_to_module(file_name)
end)
end
--- Return the pathname of the file that would be loaded for a module.
-- @param module string: module name (eg. "socket.core")
-- @return string: filename of the module (eg. "/usr/local/lib/lua/5.1/socket/core.so")
function loader.which(module)
local _, _, file_name = select_module(module, path.which_i)
return file_name
end
--- Package loader for LuaRocks support.
-- 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.
-- @param module string: The module name, like in plain require().
-- @return table: The module table (typically), like in plain
-- require(). See <a href="http://www.lua.org/manual/5.1/manual.html#pdf-require">require()</a>
-- in the Lua reference manual for details.
function loader.luarocks_loader(module)
local name, version, module_name = pick_module(module)
if not name then
return "No LuaRocks module found for "..module
else
loader.add_context(name, version)
return call_other_loaders(module, name, version, module_name)
end
end
table.insert(loaders, 1, loader.luarocks_loader)
return loader

View File

@ -0,0 +1,92 @@
--- 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.
local make = {}
package.loaded["luarocks.make"] = make
local build = require("luarocks.build")
local fs = require("luarocks.fs")
local util = require("luarocks.util")
local cfg = require("luarocks.cfg")
local fetch = require("luarocks.fetch")
local pack = require("luarocks.pack")
local remove = require("luarocks.remove")
local deps = require("luarocks.deps")
local manif = require("luarocks.manif")
util.add_run_function(make)
make.help_summary = "Compile package in current directory using a rockspec."
make.help_arguments = "[--pack-binary-rock] [<rockspec>]"
make.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,
it looks for a rockspec in the current directory and in "rockspec/"
and "rockspecs/" subdirectories, picking the rockspec with newest version
or without version name. If rockspecs for different rocks are found
or there are several rockspecs without version, 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.
--pack-binary-rock Do not install rock. Instead, produce a .rock file
with the contents of compilation in the current
directory.
--keep Do not remove previously installed versions of the
rock after installing a new one. This behavior can
be made permanent by setting keep_other_versions=true
in the configuration file.
--branch=<name> Override the `source.branch` field in the loaded
rockspec. Allows to specify a different branch to
fetch. Particularly for SCM rocks.
]]
--- Driver function for "make" command.
-- @param name string: A local rockspec.
-- @return boolean or (nil, string, exitcode): True if build was successful; nil and an
-- error message otherwise. exitcode is optionally returned.
function make.command(flags, rockspec)
assert(type(rockspec) == "string" or not rockspec)
if not rockspec then
local err
rockspec, err = util.get_default_rockspec()
if not rockspec then
return nil, err
end
end
if not rockspec:match("rockspec$") then
return nil, "Invalid argument: 'make' takes a rockspec as a parameter. "..util.see_help("make")
end
if flags["pack-binary-rock"] then
local rspec, err, errcode = fetch.load_rockspec(rockspec)
if not rspec then
return nil, err
end
return pack.pack_binary_rock(rspec.name, rspec.version, build.build_rockspec, rockspec, false, true, deps.get_deps_mode(flags))
else
local ok, err = fs.check_command_permissions(flags)
if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end
ok, err = build.build_rockspec(rockspec, false, true, deps.get_deps_mode(flags))
if not ok then return nil, err end
local name, version = ok, err
if (not flags["keep"]) and not cfg.keep_other_versions then
local ok, err = remove.remove_other_versions(name, version, flags["force"], flags["force-fast"])
if not ok then util.printerr(err) end
end
manif.check_dependencies(nil, deps.get_deps_mode(flags))
return name, version
end
end
return make

View File

@ -0,0 +1,53 @@
--- Module implementing the luarocks-admin "make_manifest" command.
-- Compile a manifest file for a repository.
local make_manifest = {}
package.loaded["luarocks.make_manifest"] = make_manifest
local manif = require("luarocks.manif")
local index = require("luarocks.index")
local cfg = require("luarocks.cfg")
local util = require("luarocks.util")
local deps = require("luarocks.deps")
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
util.add_run_function(make_manifest)
make_manifest.help_summary = "Compile a manifest file for a repository."
make_manifest.help = [[
<argument>, if given, is a local repository pathname.
--local-tree If given, do not write versioned versions of the manifest file.
Use this when rebuilding the manifest of a local rocks tree.
]]
--- 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 make_manifest.command(flags, repo)
assert(type(repo) == "string" or not repo)
repo = repo or cfg.rocks_dir
util.printout("Making manifest for "..repo)
if repo:match("/lib/luarocks") and not flags["local-tree"] then
util.warning("This looks like a local rocks tree, but you did not pass --local-tree.")
end
local ok, err = manif.make_manifest(repo, deps.get_deps_mode(flags), not flags["local-tree"])
if ok and not flags["local-tree"] then
util.printout("Generating index.html for "..repo)
index.make_index(repo)
end
if flags["local-tree"] then
for luaver in util.lua_versions() do
fs.delete(dir.path(repo, "manifest-"..luaver))
end
end
return ok, err
end
return make_manifest

View File

@ -0,0 +1,628 @@
--- Module for handling manifest files and tables.
-- Manifest files describe the contents of a LuaRocks tree or server.
-- They are loaded into manifest tables, which are then used for
-- performing searches, matching dependencies, etc.
local manif = {}
package.loaded["luarocks.manif"] = manif
local manif_core = require("luarocks.manif_core")
local persist = require("luarocks.persist")
local fetch = require("luarocks.fetch")
local dir = require("luarocks.dir")
local fs = require("luarocks.fs")
local search = require("luarocks.search")
local util = require("luarocks.util")
local cfg = require("luarocks.cfg")
local path = require("luarocks.path")
local repos = require("luarocks.repos")
local deps = require("luarocks.deps")
manif.rock_manifest_cache = {}
--- Commit a table to disk in given local path.
-- @param where string: The directory where the table should be saved.
-- @param name string: The filename.
-- @param tbl table: The table to be saved.
-- @return boolean or (nil, string): true if successful, or nil and a
-- message in case of errors.
local function save_table(where, name, tbl)
assert(type(where) == "string")
assert(type(name) == "string")
assert(type(tbl) == "table")
local filename = dir.path(where, name)
local ok, err = persist.save_from_table(filename..".tmp", tbl)
if ok then
ok, err = fs.replace_file(filename, filename..".tmp")
end
return ok, err
end
function manif.load_rock_manifest(name, version, root)
assert(type(name) == "string")
assert(type(version) == "string")
local name_version = name.."/"..version
if manif.rock_manifest_cache[name_version] then
return manif.rock_manifest_cache[name_version].rock_manifest
end
local pathname = path.rock_manifest_file(name, version, root)
local rock_manifest = persist.load_into_table(pathname)
if not rock_manifest then return nil end
manif.rock_manifest_cache[name_version] = rock_manifest
return rock_manifest.rock_manifest
end
function manif.make_rock_manifest(name, version)
local install_dir = path.install_dir(name, version)
local tree = {}
for _, file in ipairs(fs.find(install_dir)) do
local full_path = dir.path(install_dir, file)
local walk = tree
local last
local last_name
for name in file:gmatch("[^/]+") do
local next = walk[name]
if not next then
next = {}
walk[name] = next
end
last = walk
last_name = name
walk = next
end
if fs.is_file(full_path) then
local sum, err = fs.get_md5(full_path)
if not sum then
return nil, "Failed producing checksum: "..tostring(err)
end
last[last_name] = sum
end
end
local rock_manifest = { rock_manifest=tree }
manif.rock_manifest_cache[name.."/"..version] = rock_manifest
save_table(install_dir, "rock_manifest", rock_manifest )
end
local function fetch_manifest_from(repo_url, filename)
local url = dir.path(repo_url, filename)
local name = repo_url:gsub("[/:]","_")
local cache_dir = dir.path(cfg.local_cache, name)
local ok = fs.make_dir(cache_dir)
if not ok then
return nil, "Failed creating temporary cache directory "..cache_dir
end
local file, err, errcode = fetch.fetch_url(url, dir.path(cache_dir, filename), true)
if not file then
return nil, "Failed fetching manifest for "..repo_url..(err and " - "..err or ""), errcode
end
return file
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.
-- @param lua_version string: Lua version in "5.x" format, defaults to installed version.
-- @return table or (nil, string, [string]): A table representing the manifest,
-- or nil followed by an error message and an optional error code.
function manif.load_manifest(repo_url, lua_version)
assert(type(repo_url) == "string")
assert(type(lua_version) == "string" or not lua_version)
lua_version = lua_version or cfg.lua_version
local cached_manifest = manif_core.get_cached_manifest(repo_url, lua_version)
if cached_manifest then
return cached_manifest
end
local filenames = {
"manifest-"..lua_version..".zip",
"manifest-"..lua_version,
"manifest",
}
local protocol, repodir = dir.split_url(repo_url)
local pathname
if protocol == "file" then
for _, filename in ipairs(filenames) do
pathname = dir.path(repodir, filename)
if fs.exists(pathname) then
break
end
end
else
local err, errcode
for _, filename in ipairs(filenames) do
pathname, err, errcode = fetch_manifest_from(repo_url, filename)
if pathname then
break
end
end
if not pathname then
return nil, err, errcode
end
end
if pathname:match(".*%.zip$") then
pathname = fs.absolute_name(pathname)
local dir = dir.dir_name(pathname)
fs.change_dir(dir)
local nozip = pathname:match("(.*)%.zip$")
fs.delete(nozip)
local ok = fs.unzip(pathname)
fs.pop_dir()
if not ok then
fs.delete(pathname)
fs.delete(pathname..".timestamp")
return nil, "Failed extracting manifest file"
end
pathname = nozip
end
return manif_core.manifest_loader(pathname, repo_url, lua_version)
end
--- Update storage table to account for items provided by a package.
-- @param storage table: a table storing items in the following format:
-- keys are item names and values are arrays of packages providing each item,
-- where a package is specified as string `name/version`.
-- @param items table: a table mapping item names to paths.
-- @param name string: package name.
-- @param version string: package version.
local function store_package_items(storage, name, version, items)
assert(type(storage) == "table")
assert(type(items) == "table")
assert(type(name) == "string")
assert(type(version) == "string")
local package_identifier = name.."/"..version
for item_name, path in pairs(items) do
if not storage[item_name] then
storage[item_name] = {}
end
table.insert(storage[item_name], package_identifier)
end
end
--- Update storage table removing items provided by a package.
-- @param storage table: a table storing items in the following format:
-- keys are item names and values are arrays of packages providing each item,
-- where a package is specified as string `name/version`.
-- @param items table: a table mapping item names to paths.
-- @param name string: package name.
-- @param version string: package version.
local function remove_package_items(storage, name, version, items)
assert(type(storage) == "table")
assert(type(items) == "table")
assert(type(name) == "string")
assert(type(version) == "string")
local package_identifier = name.."/"..version
for item_name, path in pairs(items) do
local all_identifiers = storage[item_name]
for i, identifier in ipairs(all_identifiers) do
if identifier == package_identifier then
table.remove(all_identifiers, i)
break
end
end
if #all_identifiers == 0 then
storage[item_name] = nil
end
end
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
--- 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
--- Process the dependencies of a manifest table to determine its dependency
-- chains for loading modules. The manifest dependencies information is filled
-- and any dependency inconsistencies or missing dependencies are reported to
-- standard error.
-- @param manifest table: a manifest table.
-- @param deps_mode string: Dependency mode: "one" for the current default tree,
-- "all" for all trees, "order" for all trees with priority >= the current default,
-- "none" for no trees.
local function update_dependencies(manifest, deps_mode)
assert(type(manifest) == "table")
assert(type(deps_mode) == "string")
for pkg, versions in pairs(manifest.repository) do
for version, repositories in pairs(versions) do
for _, repo in ipairs(repositories) do
if repo.arch == "installed" then
repo.dependencies = {}
deps.scan_deps(repo.dependencies, manifest, pkg, version, deps_mode)
repo.dependencies[pkg] = nil
end
end
end
end
end
--- Filter manifest table by Lua version, removing rockspecs whose Lua version
-- does not match.
-- @param manifest table: a manifest table.
-- @param lua_version string or nil: filter by Lua version
-- @param repodir string: directory of repository being scanned
-- @param cache table: temporary rockspec cache table
local function filter_by_lua_version(manifest, lua_version, repodir, cache)
assert(type(manifest) == "table")
assert(type(repodir) == "string")
assert((not cache) or type(cache) == "table")
cache = cache or {}
lua_version = deps.parse_version(lua_version)
for pkg, versions in pairs(manifest.repository) do
local to_remove = {}
for version, repositories in pairs(versions) do
for _, repo in ipairs(repositories) do
if repo.arch == "rockspec" then
local pathname = dir.path(repodir, pkg.."-"..version..".rockspec")
local rockspec, err = cache[pathname]
if not rockspec then
rockspec, err = fetch.load_local_rockspec(pathname, true)
end
if rockspec then
cache[pathname] = rockspec
for _, dep in ipairs(rockspec.dependencies) do
if dep.name == "lua" then
if not deps.match_constraints(lua_version, dep.constraints) then
table.insert(to_remove, version)
end
break
end
end
else
util.printerr("Error loading rockspec for "..pkg.." "..version..": "..err)
end
end
end
end
if next(to_remove) then
for _, incompat in ipairs(to_remove) do
versions[incompat] = nil
end
if not next(versions) then
manifest.repository[pkg] = nil
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).
-- It will be altered to include the search results.
-- @return boolean or (nil, string): true in case of success, or nil followed by an error message.
local function store_results(results, manifest)
assert(type(results) == "table")
assert(type(manifest) == "table")
for name, versions in pairs(results) do
local pkgtable = manifest.repository[name] or {}
for version, entries in pairs(versions) do
local versiontable = {}
for _, entry in ipairs(entries) do
local entrytable = {}
entrytable.arch = entry.arch
if entry.arch == "installed" then
local rock_manifest = manif.load_rock_manifest(name, version)
if not rock_manifest then
return nil, "rock_manifest file not found for "..name.." "..version.." - not a LuaRocks 2 tree?"
end
entrytable.modules = repos.package_modules(name, version)
store_package_items(manifest.modules, name, version, entrytable.modules)
entrytable.commands = repos.package_commands(name, version)
store_package_items(manifest.commands, name, version, entrytable.commands)
end
table.insert(versiontable, entrytable)
end
pkgtable[version] = versiontable
end
manifest.repository[name] = pkgtable
end
sort_package_matching_table(manifest.modules)
sort_package_matching_table(manifest.commands)
return true
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.
-- @param deps_mode string: Dependency mode: "one" for the current default tree,
-- "all" for all trees, "order" for all trees with priority >= the current default,
-- "none" for the default dependency mode from the configuration.
-- @param remote boolean: 'true' if making a manifest for a rocks server.
-- @return boolean or (nil, string): True if manifest was generated,
-- or nil and an error message.
function manif.make_manifest(repo, deps_mode, remote)
assert(type(repo) == "string")
assert(type(deps_mode) == "string")
if deps_mode == "none" then deps_mode = cfg.deps_mode end
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 = {} }
manif_core.cache_manifest(repo, nil, manifest)
local ok, err = store_results(results, manifest)
if not ok then return nil, err end
if remote then
local cache = {}
for luaver in util.lua_versions() do
local vmanifest = { repository = {}, modules = {}, commands = {} }
local ok, err = store_results(results, vmanifest)
filter_by_lua_version(vmanifest, luaver, repo, cache)
save_table(repo, "manifest-"..luaver, vmanifest)
end
else
update_dependencies(manifest, deps_mode)
end
return save_table(repo, "manifest", manifest)
end
--- Update manifest file for a local repository
-- adding information about a version of a package installed in that repository.
-- @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 is used.
-- @param deps_mode string: Dependency mode: "one" for the current default tree,
-- "all" for all trees, "order" for all trees with priority >= the current default,
-- "none" for using the default dependency mode from the configuration.
-- @return boolean or (nil, string): True if manifest was updated successfully,
-- or nil and an error message.
function manif.add_to_manifest(name, version, repo, deps_mode)
assert(type(name) == "string")
assert(type(version) == "string")
local rocks_dir = path.rocks_dir(repo or cfg.root_dir)
assert(type(deps_mode) == "string")
if deps_mode == "none" then deps_mode = cfg.deps_mode end
local manifest, err = manif_core.load_local_manifest(rocks_dir)
if not manifest then
util.printerr("No existing manifest. Attempting to rebuild...")
-- Manifest built by `manif.make_manifest` should already
-- include information about given name and version,
-- no need to update it.
return manif.make_manifest(rocks_dir, deps_mode)
end
local results = {[name] = {[version] = {{arch = "installed", repo = rocks_dir}}}}
local ok, err = store_results(results, manifest)
if not ok then return nil, err end
update_dependencies(manifest, deps_mode)
return save_table(rocks_dir, "manifest", manifest)
end
--- Update manifest file for a local repository
-- removing information about a version of a package.
-- @param name string: Name of a package removed from the repository.
-- @param version string: Version of a package removed from the repository.
-- @param repo string or nil: Pathname of a local repository. If not given,
-- the default local repository is used.
-- @param deps_mode string: Dependency mode: "one" for the current default tree,
-- "all" for all trees, "order" for all trees with priority >= the current default,
-- "none" for using the default dependency mode from the configuration.
-- @return boolean or (nil, string): True if manifest was updated successfully,
-- or nil and an error message.
function manif.remove_from_manifest(name, version, repo, deps_mode)
assert(type(name) == "string")
assert(type(version) == "string")
local rocks_dir = path.rocks_dir(repo or cfg.root_dir)
assert(type(deps_mode) == "string")
if deps_mode == "none" then deps_mode = cfg.deps_mode end
local manifest, err = manif_core.load_local_manifest(rocks_dir)
if not manifest then
util.printerr("No existing manifest. Attempting to rebuild...")
-- Manifest built by `manif.make_manifest` should already
-- include up-to-date information, no need to update it.
return manif.make_manifest(rocks_dir, deps_mode)
end
local package_entry = manifest.repository[name]
local version_entry = package_entry[version][1]
remove_package_items(manifest.modules, name, version, version_entry.modules)
remove_package_items(manifest.commands, name, version, version_entry.commands)
package_entry[version] = nil
manifest.dependencies[name][version] = nil
if not next(package_entry) then
-- No more versions of this package.
manifest.repository[name] = nil
manifest.dependencies[name] = nil
end
update_dependencies(manifest, deps_mode)
return save_table(rocks_dir, "manifest", manifest)
end
--- Report missing dependencies for all rocks installed in a repository.
-- @param repo string or nil: Pathname of a local repository. If not given,
-- the default local repository is used.
-- @param deps_mode string: Dependency mode: "one" for the current default tree,
-- "all" for all trees, "order" for all trees with priority >= the current default,
-- "none" for using the default dependency mode from the configuration.
function manif.check_dependencies(repo, deps_mode)
local rocks_dir = path.rocks_dir(repo or cfg.root_dir)
assert(type(deps_mode) == "string")
if deps_mode == "none" then deps_mode = cfg.deps_mode end
local manifest = manif_core.load_local_manifest(rocks_dir)
if not manifest then
return
end
for name, versions in util.sortedpairs(manifest.repository) do
for version, version_entries in util.sortedpairs(versions, deps.compare_versions) do
for _, entry in ipairs(version_entries) do
if entry.arch == "installed" then
if manifest.dependencies[name] and manifest.dependencies[name][version] then
deps.report_missing_dependencies(name, version, manifest.dependencies[name][version], deps_mode)
end
end
end
end
end
end
function manif.zip_manifests()
for ver in util.lua_versions() do
local file = "manifest-"..ver
local zip = file..".zip"
fs.delete(dir.path(fs.current_dir(), zip))
fs.zip(zip, file)
end
end
--- Get type and name of an item (a module or a command) provided by a file.
-- @param deploy_type string: rock manifest subtree the file comes from ("bin", "lua", or "lib").
-- @param file_path string: path to the file relatively to deploy_type subdirectory.
-- @return (string, string): item type ("module" or "command") and name.
function manif.get_provided_item(deploy_type, file_path)
assert(type(deploy_type) == "string")
assert(type(file_path) == "string")
local item_type = deploy_type == "bin" and "command" or "module"
local item_name = item_type == "command" and file_path or path.path_to_module(file_path)
return item_type, item_name
end
local function get_providers(item_type, item_name, repo)
assert(type(item_type) == "string")
assert(type(item_name) == "string")
local rocks_dir = path.rocks_dir(repo or cfg.root_dir)
local manifest = manif_core.load_local_manifest(rocks_dir)
return manifest and manifest[item_type .. "s"][item_name]
end
--- Given a name of a module or a command, figure out which rock name and version
-- correspond to it in the rock tree manifest.
-- @param item_type string: "module" or "command".
-- @param item_name string: module or command name.
-- @param root string or nil: A local root dir for a rocks tree. If not given, the default is used.
-- @return (string, string) or nil: name and version of the provider rock or nil if there
-- is no provider.
function manif.get_current_provider(item_type, item_name, repo)
local providers = get_providers(item_type, item_name, repo)
if providers then
return providers[1]:match("([^/]*)/([^/]*)")
end
end
function manif.get_next_provider(item_type, item_name, repo)
local providers = get_providers(item_type, item_name, repo)
if providers and providers[2] then
return providers[2]:match("([^/]*)/([^/]*)")
end
end
--- Given a name of a module or a command provided by a package, figure out
-- which file provides it.
-- @param name string: package name.
-- @param version string: package version.
-- @param item_type string: "module" or "command".
-- @param item_name string: module or command name.
-- @param root string or nil: A local root dir for a rocks tree. If not given, the default is used.
-- @return (string, string): rock manifest subtree the file comes from ("bin", "lua", or "lib")
-- and path to the providing file relatively to that subtree.
function manif.get_providing_file(name, version, item_type, item_name, repo)
local rocks_dir = path.rocks_dir(repo or cfg.root_dir)
local manifest = manif_core.load_local_manifest(rocks_dir)
local entry_table = manifest.repository[name][version][1]
local file_path = entry_table[item_type .. "s"][item_name]
if item_type == "command" then
return "bin", file_path
end
-- A module can be in "lua" or "lib". Decide based on extension first:
-- most likely Lua modules are in "lua/" and C modules are in "lib/".
if file_path:match("%." .. cfg.lua_extension .. "$") then
return "lua", file_path
elseif file_path:match("%." .. cfg.lib_extension .. "$") then
return "lib", file_path
end
-- Fallback to rock manifest scanning.
local rock_manifest = manif.load_rock_manifest(name, version)
local subtree = rock_manifest.lib
for path_part in file_path:gmatch("[^/]+") do
if type(subtree) == "table" then
subtree = subtree[path_part]
else
-- Assume it's in "lua/" if it's not in "lib/".
return "lua", file_path
end
end
return type(subtree) == "string" and "lib" or "lua", file_path
end
return manif

View File

@ -0,0 +1,106 @@
--- Core functions for querying manifest files.
-- This module requires no specific 'fs' functionality.
local manif_core = {}
package.loaded["luarocks.manif_core"] = manif_core
local persist = require("luarocks.persist")
local type_check = require("luarocks.type_check")
local cfg = require("luarocks.cfg")
local dir = require("luarocks.dir")
local util = require("luarocks.util")
local path = require("luarocks.path")
-- Table with repository identifiers as keys and tables mapping
-- Lua versions to cached loaded manifests as values.
local manifest_cache = {}
--- Cache a loaded manifest.
-- @param repo_url string: The repository identifier.
-- @param lua_version string: Lua version in "5.x" format, defaults to installed version.
-- @param manifest table: the manifest to be cached.
function manif_core.cache_manifest(repo_url, lua_version, manifest)
lua_version = lua_version or cfg.lua_version
manifest_cache[repo_url] = manifest_cache[repo_url] or {}
manifest_cache[repo_url][lua_version] = manifest
end
--- Attempt to get cached loaded manifest.
-- @param repo_url string: The repository identifier.
-- @param lua_version string: Lua version in "5.x" format, defaults to installed version.
-- @return table or nil: loaded manifest or nil if cache is empty.
function manif_core.get_cached_manifest(repo_url, lua_version)
lua_version = lua_version or cfg.lua_version
return manifest_cache[repo_url] and manifest_cache[repo_url][lua_version]
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.
-- @param lua_version string: Lua version in "5.x" format, defaults to installed version.
-- @param quick boolean: If given, skips type checking.
-- @return table or (nil, string, string): the manifest or nil,
-- error message and error code ("open", "load", "run" or "type").
function manif_core.manifest_loader(file, repo_url, lua_version, quick)
local manifest, err, errcode = persist.load_into_table(file)
if not manifest then
return nil, "Failed loading manifest for "..repo_url..": "..err, errcode
end
local globals = err
if not quick then
local ok, err = type_check.type_check_manifest(manifest, globals)
if not ok then
return nil, "Error checking manifest: "..err, "type"
end
end
manif_core.cache_manifest(repo_url, lua_version, manifest)
return manifest
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, string): A table representing the manifest,
-- or nil followed by an error message and an error code, see manifest_loader.
function manif_core.load_local_manifest(repo_url)
assert(type(repo_url) == "string")
local cached_manifest = manif_core.get_cached_manifest(repo_url)
if cached_manifest then
return cached_manifest
end
local pathname = dir.path(repo_url, "manifest")
return manif_core.manifest_loader(pathname, repo_url, nil, true)
end
--- Get all versions of a package listed in a manifest file.
-- @param name string: a package name.
-- @param deps_mode string: "one", to use only the currently
-- configured tree; "order" to select trees based on order
-- (use the current tree and all trees below it on the list)
-- or "all", to use all trees.
-- @return table: An array of strings listing installed
-- versions of a package.
function manif_core.get_versions(name, deps_mode)
assert(type(name) == "string")
assert(type(deps_mode) == "string")
local version_set = {}
path.map_trees(deps_mode, function(tree)
local manifest = manif_core.load_local_manifest(path.rocks_dir(tree))
if manifest and manifest.repository[name] then
for version in pairs(manifest.repository[name]) do
version_set[version] = true
end
end
end)
return util.keys(version_set)
end
return manif_core

View File

@ -0,0 +1,200 @@
--- Module implementing the LuaRocks "new_version" command.
-- Utility function that writes a new rockspec, updating data from a previous one.
local new_version = {}
local util = require("luarocks.util")
local download = require("luarocks.download")
local fetch = require("luarocks.fetch")
local persist = require("luarocks.persist")
local fs = require("luarocks.fs")
local type_check = require("luarocks.type_check")
util.add_run_function(new_version)
new_version.help_summary = "Auto-write a rockspec for a new version of a rock."
new_version.help_arguments = "[--tag=<tag>] [<package>|<rockspec>] [<new_version>] [<new_url>]"
new_version.help = [[
This is a utility function that writes a new rockspec, updating data
from a previous one.
If a package name is given, it downloads the latest rockspec from the
default server. If a rockspec is given, it uses it instead. If no argument
is given, it looks for a rockspec same way 'luarocks make' does.
If the version number is not given and tag is passed using --tag,
it is used as the version, with 'v' removed from beginning.
Otherwise, it only increments the revision number of the given
(or downloaded) rockspec.
If a URL is given, it replaces the one from the old rockspec with the
given URL. If a URL is not given and a new version is given, it tries
to guess the new URL by replacing occurrences of the version number
in the URL or tag. It also tries to download the new URL to determine
the new MD5 checksum.
If a tag is given, it replaces the one from the old rockspec. If there is
an old tag but no new one passed, it is guessed in the same way URL is.
WARNING: it writes the new rockspec to the current directory,
overwriting the file if it already exists.
]]
local function try_replace(tbl, field, old, new)
if not tbl[field] then
return false
end
local old_field = tbl[field]
local new_field = tbl[field]:gsub(old, new)
if new_field ~= old_field then
util.printout("Guessing new '"..field.."' field as "..new_field)
tbl[field] = new_field
return true
end
return false
end
-- Try to download source file using URL from a rockspec.
-- If it specified MD5, update it.
-- @return (true, false) if MD5 was not specified or it stayed same,
-- (true, true) if MD5 changed, (nil, string) on error.
local function check_url_and_update_md5(out_rs)
local file, temp_dir = fetch.fetch_url_at_temp_dir(out_rs.source.url, "luarocks-new-version-"..out_rs.package)
if not file then
util.printerr("Warning: invalid URL - "..temp_dir)
return true, false
end
local inferred_dir, found_dir = fetch.find_base_dir(file, temp_dir, out_rs.source.url, out_rs.source.dir)
if not inferred_dir then
return nil, found_dir
end
if found_dir and found_dir ~= inferred_dir then
out_rs.source.dir = found_dir
end
if file then
if out_rs.source.md5 then
util.printout("File successfully downloaded. Updating MD5 checksum...")
local new_md5, err = fs.get_md5(file)
if not new_md5 then
return nil, err
end
local old_md5 = out_rs.source.md5
out_rs.source.md5 = new_md5
return true, new_md5 ~= old_md5
else
util.printout("File successfully downloaded.")
return true, false
end
end
end
local function update_source_section(out_rs, url, tag, old_ver, new_ver)
if tag then
out_rs.source.tag = tag
end
if url then
out_rs.source.url = url
return check_url_and_update_md5(out_rs)
end
if new_ver == old_ver then
return true
end
if out_rs.source.dir then
try_replace(out_rs.source, "dir", old_ver, new_ver)
end
if out_rs.source.file then
try_replace(out_rs.source, "file", old_ver, new_ver)
end
if try_replace(out_rs.source, "url", old_ver, new_ver) then
return check_url_and_update_md5(out_rs)
end
if tag or try_replace(out_rs.source, "tag", old_ver, new_ver) then
return true
end
-- Couldn't replace anything significant, use the old URL.
local ok, md5_changed = check_url_and_update_md5(out_rs)
if not ok then
return nil, md5_changed
end
if md5_changed then
util.printerr("Warning: URL is the same, but MD5 has changed. Old rockspec is broken.")
end
return true
end
function new_version.command(flags, input, version, url)
if not input then
local err
input, err = util.get_default_rockspec()
if not input then
return nil, err
end
end
assert(type(input) == "string")
local filename, err
if input:match("rockspec$") then
filename, err = fetch.fetch_url(input)
if not filename then
return nil, err
end
else
filename, err = download.download("rockspec", input:lower())
if not filename then
return nil, err
end
end
local valid_rs, err = fetch.load_rockspec(filename)
if not valid_rs then
return nil, err
end
local old_ver, old_rev = valid_rs.version:match("(.*)%-(%d+)$")
local new_ver, new_rev
if flags.tag and not version then
version = flags.tag:gsub("^v", "")
end
if version then
new_ver, new_rev = version:match("(.*)%-(%d+)$")
new_rev = tonumber(new_rev)
if not new_rev then
new_ver = version
new_rev = 1
end
else
new_ver = old_ver
new_rev = tonumber(old_rev) + 1
end
local new_rockver = new_ver:gsub("-", "")
local out_rs, err = persist.load_into_table(filename)
local out_name = out_rs.package:lower()
out_rs.version = new_rockver.."-"..new_rev
local ok, err = update_source_section(out_rs, url, flags.tag, old_ver, new_ver)
if not ok then return nil, err end
if out_rs.build and out_rs.build.type == "module" then
out_rs.build.type = "builtin"
end
local out_filename = out_name.."-"..new_rockver.."-"..new_rev..".rockspec"
persist.save_from_table(out_filename, out_rs, type_check.rockspec_order)
util.printout("Wrote "..out_filename)
local valid_out_rs, err = fetch.load_local_rockspec(out_filename)
if not valid_out_rs then
return nil, "Failed loading generated rockspec: "..err
end
return true
end
return new_version

View File

@ -0,0 +1,197 @@
--- Module implementing the LuaRocks "pack" command.
-- Creates a rock, packing sources or binaries.
local pack = {}
package.loaded["luarocks.pack"] = pack
local unpack = unpack or table.unpack
local path = require("luarocks.path")
local repos = require("luarocks.repos")
local fetch = require("luarocks.fetch")
local fs = require("luarocks.fs")
local cfg = require("luarocks.cfg")
local util = require("luarocks.util")
local dir = require("luarocks.dir")
local manif = require("luarocks.manif")
local search = require("luarocks.search")
util.add_run_function(pack)
pack.help_summary = "Create a rock, packing sources or binaries."
pack.help_arguments = "{<rockspec>|<name> [<version>]}"
pack.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.
function pack.pack_source_rock(rockspec_file)
assert(type(rockspec_file) == "string")
local rockspec, err = fetch.load_rockspec(rockspec_file)
if err then
return nil, "Error loading rockspec: "..err
end
rockspec_file = rockspec.local_filename
local name_version = rockspec.name .. "-" .. rockspec.version
local rock_file = fs.absolute_name(name_version .. ".src.rock")
local source_file, source_dir = fetch.fetch_sources(rockspec, false)
if not source_file then
return nil, source_dir
end
local ok, err = fs.change_dir(source_dir)
if not ok then return nil, err end
fs.delete(rock_file)
fs.copy(rockspec_file, source_dir, cfg.perm_read)
if not fs.zip(rock_file, dir.base_name(rockspec_file), dir.base_name(source_file)) then
return nil, "Failed packing "..rock_file
end
fs.pop_dir()
return rock_file
end
local function copy_back_files(name, version, file_tree, deploy_dir, pack_dir, perms)
local ok, err = fs.make_dir(pack_dir)
if not ok then return nil, err end
for file, sub in pairs(file_tree) do
local source = dir.path(deploy_dir, file)
local target = dir.path(pack_dir, file)
if type(sub) == "table" then
local ok, err = copy_back_files(name, version, sub, source, target)
if not ok then return nil, err end
else
local versioned = path.versioned_name(source, deploy_dir, name, version)
if fs.exists(versioned) then
fs.copy(versioned, target, perms)
else
fs.copy(source, target, perms)
end
end
end
return true
end
-- @param name string: Name of package to pack.
-- @param version string or nil: A version number may also be passed.
-- @param tree string or nil: An optional tree to pick the package from.
-- @return string or (nil, string): The filename of the resulting
-- .src.rock file; or nil and an error message.
local function do_pack_binary_rock(name, version, tree)
assert(type(name) == "string")
assert(type(version) == "string" or not version)
local repo, repo_url
name, version, repo, repo_url = search.pick_installed_rock(name, version, tree)
if not name then
return nil, version
end
local root = path.root_dir(repo_url)
local prefix = path.install_dir(name, version, root)
if not fs.exists(prefix) then
return nil, "'"..name.." "..version.."' does not seem to be an installed rock."
end
local rock_manifest = manif.load_rock_manifest(name, version, root)
if not rock_manifest then
return nil, "rock_manifest file not found for "..name.." "..version.." - not a LuaRocks 2 tree?"
end
local name_version = name .. "-" .. version
local rock_file = fs.absolute_name(name_version .. "."..cfg.arch..".rock")
local temp_dir = fs.make_temp_dir("pack")
fs.copy_contents(prefix, temp_dir)
local is_binary = false
if rock_manifest.lib then
local ok, err = copy_back_files(name, version, rock_manifest.lib, path.deploy_lib_dir(root), dir.path(temp_dir, "lib"), cfg.perm_exec)
if not ok then return nil, "Failed copying back files: " .. err end
is_binary = true
end
if rock_manifest.lua then
local ok, err = copy_back_files(name, version, rock_manifest.lua, path.deploy_lua_dir(root), dir.path(temp_dir, "lua"), cfg.perm_read)
if not ok then return nil, "Failed copying back files: " .. err end
end
local ok, err = fs.change_dir(temp_dir)
if not ok then return nil, err end
if not is_binary and not repos.has_binaries(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.list_dir())) then
return nil, "Failed packing "..rock_file
end
fs.pop_dir()
fs.delete(temp_dir)
return rock_file
end
function pack.pack_binary_rock(name, version, cmd, ...)
-- The --pack-binary-rock option for "luarocks build" basically performs
-- "luarocks build" on a temporary tree and then "luarocks pack". The
-- alternative would require refactoring parts of luarocks.build and
-- luarocks.pack, which would save a few file operations: the idea would be
-- to shave off the final deploy steps from the build phase and the initial
-- collect steps from the pack phase.
local temp_dir, err = fs.make_temp_dir("luarocks-build-pack-"..dir.base_name(name))
if not temp_dir then
return nil, "Failed creating temporary directory: "..err
end
util.schedule_function(fs.delete, temp_dir)
path.use_tree(temp_dir)
local ok, err = cmd(...)
if not ok then
return nil, err
end
local rname, rversion = path.parse_name(name)
if not rname then
rname, rversion = name, version
end
return do_pack_binary_rock(rname, rversion, temp_dir)
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 pack.command(flags, arg, version)
assert(type(version) == "string" or not version)
if type(arg) ~= "string" then
return nil, "Argument missing. "..util.see_help("pack")
end
local file, err
if arg:match(".*%.rockspec") then
file, err = pack.pack_source_rock(arg)
else
file, err = do_pack_binary_rock(arg:lower(), version, flags["tree"])
end
if err then
return nil, err
else
util.printout("Packed: "..file)
return true
end
end
return pack

View File

@ -0,0 +1,388 @@
--- LuaRocks-specific path 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.
local path = {}
local dir = require("luarocks.dir")
local cfg = require("luarocks.cfg")
local util = require("luarocks.util")
--- 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 path.rockspec_name_from_rock(rock_name)
assert(type(rock_name) == "string")
local base_name = dir.base_name(rock_name)
return base_name:match("(.*)%.[^.]*.rock") .. ".rockspec"
end
function path.rocks_dir(tree)
if type(tree) == "string" then
return dir.path(tree, cfg.rocks_subdir)
else
assert(type(tree) == "table")
return tree.rocks_dir or dir.path(tree.root, cfg.rocks_subdir)
end
end
function path.root_dir(rocks_dir)
assert(type(rocks_dir) == "string")
return rocks_dir:match("(.*)" .. util.matchquote(cfg.rocks_subdir) .. ".*$")
end
function path.rocks_tree_to_string(tree)
if type(tree) == "string" then
return tree
else
assert(type(tree) == "table")
return tree.root
end
end
function path.deploy_bin_dir(tree)
if type(tree) == "string" then
return dir.path(tree, "bin")
else
assert(type(tree) == "table")
return tree.bin_dir or dir.path(tree.root, "bin")
end
end
function path.deploy_lua_dir(tree)
if type(tree) == "string" then
return dir.path(tree, cfg.lua_modules_path)
else
assert(type(tree) == "table")
return tree.lua_dir or dir.path(tree.root, cfg.lua_modules_path)
end
end
function path.deploy_lib_dir(tree)
if type(tree) == "string" then
return dir.path(tree, cfg.lib_modules_path)
else
assert(type(tree) == "table")
return tree.lib_dir or dir.path(tree.root, cfg.lib_modules_path)
end
end
function path.manifest_file(tree)
if type(tree) == "string" then
return dir.path(tree, cfg.rocks_subdir, "manifest")
else
assert(type(tree) == "table")
return (tree.rocks_dir and dir.path(tree.rocks_dir, "manifest")) or dir.path(tree.root, cfg.rocks_subdir, "manifest")
end
end
--- Get the directory for all versions of a package in a tree.
-- @param name string: The package name.
-- @return string: The resulting path -- does not guarantee that
-- @param tree string or nil: If given, specifies the local tree to use.
-- the package (and by extension, the path) exists.
function path.versions_dir(name, tree)
assert(type(name) == "string")
tree = tree or cfg.root_dir
return dir.path(path.rocks_dir(tree), name)
end
--- Get the local installation directory (prefix) for a package.
-- @param name string: The package name.
-- @param version string: The package version.
-- @param tree string or nil: If given, specifies the local tree to use.
-- @return string: The resulting path -- does not guarantee that
-- the package (and by extension, the path) exists.
function path.install_dir(name, version, tree)
assert(type(name) == "string")
assert(type(version) == "string")
tree = tree or cfg.root_dir
return dir.path(path.rocks_dir(tree), 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 tree string or nil: If given, specifies the local tree to use.
-- @return string: The resulting path -- does not guarantee that
-- the package (and by extension, the file) exists.
function path.rockspec_file(name, version, tree)
assert(type(name) == "string")
assert(type(version) == "string")
tree = tree or cfg.root_dir
return dir.path(path.rocks_dir(tree), name, version, name.."-"..version..".rockspec")
end
--- Get the local filename of the rock_manifest file of an installed rock.
-- @param name string: The package name.
-- @param version string: The package version.
-- @param tree string or nil: If given, specifies the local tree to use.
-- @return string: The resulting path -- does not guarantee that
-- the package (and by extension, the file) exists.
function path.rock_manifest_file(name, version, tree)
assert(type(name) == "string")
assert(type(version) == "string")
tree = tree or cfg.root_dir
return dir.path(path.rocks_dir(tree), name, version, "rock_manifest")
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 tree string or nil: If given, specifies the local tree to use.
-- @return string: The resulting path -- does not guarantee that
-- the package (and by extension, the path) exists.
function path.lib_dir(name, version, tree)
assert(type(name) == "string")
assert(type(version) == "string")
tree = tree or cfg.root_dir
return dir.path(path.rocks_dir(tree), 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 tree string or nil: If given, specifies the local tree to use.
-- @return string: The resulting path -- does not guarantee that
-- the package (and by extension, the path) exists.
function path.lua_dir(name, version, tree)
assert(type(name) == "string")
assert(type(version) == "string")
tree = tree or cfg.root_dir
return dir.path(path.rocks_dir(tree), 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 tree string or nil: If given, specifies the local tree to use.
-- @return string: The resulting path -- does not guarantee that
-- the package (and by extension, the path) exists.
function path.doc_dir(name, version, tree)
assert(type(name) == "string")
assert(type(version) == "string")
tree = tree or cfg.root_dir
return dir.path(path.rocks_dir(tree), 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 tree string or nil: If given, specifies the local tree to use.
-- @return string: The resulting path -- does not guarantee that
-- the package (and by extension, the path) exists.
function path.conf_dir(name, version, tree)
assert(type(name) == "string")
assert(type(version) == "string")
tree = tree or cfg.root_dir
return dir.path(path.rocks_dir(tree), 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 tree string or nil: If given, specifies the local tree to use.
-- @return string: The resulting path -- does not guarantee that
-- the package (and by extension, the path) exists.
function path.bin_dir(name, version, tree)
assert(type(name) == "string")
assert(type(version) == "string")
tree = tree or cfg.root_dir
return dir.path(path.rocks_dir(tree), name, version, "bin")
end
--- Extract name, version and arch of a rock filename,
-- or name, version and "rockspec" from a rockspec name.
-- @param file_name string: pathname of a rock or rockspec
-- @return (string, string, string) or nil: name, version and arch
-- or nil if name could not be parsed
function path.parse_name(file_name)
assert(type(file_name) == "string")
if file_name:match("%.rock$") then
return dir.base_name(file_name):match("(.*)-([^-]+-%d+)%.([^.]+)%.rock$")
else
return dir.base_name(file_name):match("(.*)-([^-]+-%d+)%.(rockspec)")
end
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 path.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 = dir.path(name, version, filename..".rockspec")
elseif arch == "rockspec" then
filename = filename..".rockspec"
else
filename = filename.."."..arch..".rock"
end
return dir.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.path_to_module(file)
assert(type(file) == "string")
local name = file:match("(.*)%."..cfg.lua_extension.."$")
if name then
name = name:gsub(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(dir.separator, ".")
end
end
if not name then name = file end
name = name:gsub("^%.+", ""):gsub("%.+$", "")
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 path.module_to_path(mod)
assert(type(mod) == "string")
return (mod:gsub("[^.]*$", ""):gsub("%.", 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 path.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 = path.install_dir(name, version)
vars.LUADIR = path.lua_dir(name, version)
vars.LIBDIR = path.lib_dir(name, version)
vars.CONFDIR = path.conf_dir(name, version)
vars.BINDIR = path.bin_dir(name, version)
vars.DOCDIR = path.doc_dir(name, version)
rockspec.variables = vars
end
--- Produce a versioned version of a filename.
-- @param file string: filename (must start with prefix)
-- @param prefix string: Path prefix for file
-- @param name string: Rock name
-- @param version string: Rock version
-- @return string: a pathname with the same directory parts and a versioned basename.
function path.versioned_name(file, prefix, name, version)
assert(type(file) == "string")
assert(type(name) == "string")
assert(type(version) == "string")
local rest = file:sub(#prefix+1):gsub("^/*", "")
local name_version = (name.."_"..version):gsub("%-", "_"):gsub("%.", "_")
return dir.path(prefix, name_version.."-"..rest)
end
function path.use_tree(tree)
cfg.root_dir = tree
cfg.rocks_dir = path.rocks_dir(tree)
cfg.deploy_bin_dir = path.deploy_bin_dir(tree)
cfg.deploy_lua_dir = path.deploy_lua_dir(tree)
cfg.deploy_lib_dir = path.deploy_lib_dir(tree)
end
--- Apply a given function to the active rocks trees based on chosen dependency mode.
-- @param deps_mode string: Dependency mode: "one" for the current default tree,
-- "all" for all trees, "order" for all trees with priority >= the current default,
-- "none" for no trees (this function becomes a nop).
-- @param fn function: function to be applied, with the tree dir (string) as the first
-- argument and the remaining varargs of map_trees as the following arguments.
-- @return a table with all results of invocations of fn collected.
function path.map_trees(deps_mode, fn, ...)
local result = {}
if deps_mode == "one" then
table.insert(result, (fn(cfg.root_dir, ...)) or 0)
elseif deps_mode == "all" or deps_mode == "order" then
local use = false
if deps_mode == "all" then
use = true
end
for _, tree in ipairs(cfg.rocks_trees) do
if dir.normalize(path.rocks_tree_to_string(tree)) == dir.normalize(path.rocks_tree_to_string(cfg.root_dir)) then
use = true
end
if use then
table.insert(result, (fn(tree, ...)) or 0)
end
end
end
return result
end
local is_src_extension = { [".lua"] = true, [".tl"] = true, [".tld"] = true, [".moon"] = true }
--- Return the pathname of the file that would be loaded for a module, indexed.
-- @param file_name string: module file name as in manifest (eg. "socket/core.so")
-- @param name string: name of the package (eg. "luasocket")
-- @param version string: version number (eg. "2.0.2-1")
-- @param tree string: repository path (eg. "/usr/local")
-- @param i number: the index, 1 if version is the current default, > 1 otherwise.
-- This is done this way for use by select_module in luarocks.loader.
-- @return string: filename of the module (eg. "/usr/local/lib/lua/5.1/socket/core.so")
function path.which_i(file_name, name, version, tree, i)
local deploy_dir
local extension = file_name:match("%.[a-z]+$")
if is_src_extension[extension] then
deploy_dir = path.deploy_lua_dir(tree)
file_name = dir.path(deploy_dir, file_name)
else
deploy_dir = path.deploy_lib_dir(tree)
file_name = dir.path(deploy_dir, file_name)
end
if i > 1 then
file_name = path.versioned_name(file_name, deploy_dir, name, version)
end
return file_name
end
--- Return the pathname of the file that would be loaded for a module,
-- returning the versioned pathname if given version is not the default version
-- in the given manifest.
-- @param module_name string: module name (eg. "socket.core")
-- @param file_name string: module file name as in manifest (eg. "socket/core.so")
-- @param name string: name of the package (eg. "luasocket")
-- @param version string: version number (eg. "2.0.2-1")
-- @param tree string: repository path (eg. "/usr/local")
-- @param manifest table: the manifest table for the tree.
-- @return string: filename of the module (eg. "/usr/local/lib/lua/5.1/socket/core.so")
function path.which(module_name, file_name, name, version, tree, manifest)
local versions = manifest.modules[module_name]
assert(versions)
for i, name_version in ipairs(versions) do
if name_version == name.."/"..version then
return path.which_i(file_name, name, version, tree, i):gsub("//", "/")
end
end
assert(false)
end
return path

View File

@ -0,0 +1,69 @@
--- @module luarocks.path_cmd
-- Driver for the `luarocks path` command.
local path_cmd = {}
local util = require("luarocks.util")
local cfg = require("luarocks.cfg")
util.add_run_function(path_cmd)
path_cmd.help_summary = "Return the currently configured package path."
path_cmd.help_arguments = ""
path_cmd.help = [[
Returns the package path currently configured for this installation
of LuaRocks, formatted as shell commands to update LUA_PATH and LUA_CPATH.
--bin Adds the system path to the output
--append Appends the paths to the existing paths. Default is to prefix
the LR paths to the existing paths.
--lr-path Exports the Lua path (not formatted as shell command)
--lr-cpath Exports the Lua cpath (not formatted as shell command)
--lr-bin Exports the system path (not formatted as shell command)
On Unix systems, you may run:
eval `luarocks path`
And on Windows:
luarocks path > "%temp%\_lrp.bat" && call "%temp%\_lrp.bat" && del "%temp%\_lrp.bat"
]]
--- Driver function for "path" command.
-- @return boolean This function always succeeds.
function path_cmd.command(flags)
local lr_path, lr_cpath, lr_bin = cfg.package_paths(flags["tree"])
local path_sep = cfg.export_path_separator
if flags["lr-path"] then
util.printout(util.remove_path_dupes(lr_path, ';'))
return true
elseif flags["lr-cpath"] then
util.printout(util.remove_path_dupes(lr_cpath, ';'))
return true
elseif flags["lr-bin"] then
util.printout(util.remove_path_dupes(lr_bin, path_sep))
return true
end
if flags["append"] then
lr_path = package.path .. ";" .. lr_path
lr_cpath = package.cpath .. ";" .. lr_cpath
lr_bin = os.getenv("PATH") .. path_sep .. lr_bin
else
lr_path = lr_path.. ";" .. package.path
lr_cpath = lr_cpath .. ";" .. package.cpath
lr_bin = lr_bin .. path_sep .. os.getenv("PATH")
end
util.printout(cfg.export_lua_path:format(util.remove_path_dupes(lr_path, ';')))
util.printout(cfg.export_lua_cpath:format(util.remove_path_dupes(lr_cpath, ';')))
if flags["bin"] then
util.printout(cfg.export_path:format(util.remove_path_dupes(lr_bin, path_sep)))
end
return true
end
return path_cmd

View File

@ -0,0 +1,210 @@
--- 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.
local persist = {}
package.loaded["luarocks.persist"] = persist
local util = require("luarocks.util")
--- Load and run a Lua file in an environment.
-- @param filename string: the name of the file.
-- @param env table: the environment table.
-- @return (true, any) or (nil, string, string): true and the return value
-- of the file, or nil, an error message and an error code ("open", "load"
-- or "run") in case of errors.
local function run_file(filename, env)
local fd, err = io.open(filename)
if not fd then
return nil, err, "open"
end
local str, err = fd:read("*a")
fd:close()
if not str then
return nil, err, "open"
end
str = str:gsub("^#![^\n]*\n", "")
local chunk, ran
if _VERSION == "Lua 5.1" then -- Lua 5.1
chunk, err = loadstring(str, filename)
if chunk then
setfenv(chunk, env)
ran, err = pcall(chunk)
end
else -- Lua 5.2
chunk, err = load(str, filename, "t", env)
if chunk then
ran, err = pcall(chunk)
end
end
if not chunk then
return nil, "Error loading file: "..err, "load"
end
if not ran then
return nil, "Error running file: "..err, "run"
end
return true, err
end
--- 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, table) or (nil, string, string): a table with the file's assignments
-- as fields and set of undefined globals accessed in file,
-- or nil, an error message and an error code ("open"; couldn't open the file,
-- "load"; compile-time error, or "run"; run-time error)
-- in case of errors.
function persist.load_into_table(filename, tbl)
assert(type(filename) == "string")
assert(type(tbl) == "table" or not tbl)
local result = tbl or {}
local globals = {}
local globals_mt = {
__index = function(t, k)
globals[k] = true
end
}
local save_mt = getmetatable(result)
setmetatable(result, globals_mt)
local ok, err, errcode = run_file(filename, result)
setmetatable(result, save_mt)
if not ok then
return nil, err, errcode
end
return result, globals
end
local write_table
--- Write a value as Lua code.
-- This function handles only numbers and strings, invoking write_table
-- to write tables.
-- @param out table or userdata: a writer object supporting :write() method.
-- @param v: the value to be written.
-- @param level number: the indentation level
-- @param sub_order table: optional prioritization table
-- @see write_table
local function write_value(out, v, level, sub_order)
if type(v) == "table" then
write_table(out, v, level + 1, sub_order)
elseif type(v) == "string" then
if v:match("[\r\n]") then
local open, close = "[[", "]]"
local equals = 0
local v_with_bracket = v.."]"
while v_with_bracket:find(close, 1, true) do
equals = equals + 1
local eqs = ("="):rep(equals)
open, close = "["..eqs.."[", "]"..eqs.."]"
end
out:write(open.."\n"..v..close)
else
out:write(("%q"):format(v))
end
else
out:write(tostring(v))
end
end
--- Write a table as Lua code in curly brackets notation to a writer object.
-- Only numbers, strings and tables (containing numbers, strings
-- or other recursively processed tables) are supported.
-- @param out table or userdata: a writer object supporting :write() method.
-- @param tbl table: the table to be written.
-- @param level number: the indentation level
-- @param field_order table: optional prioritization table
write_table = function(out, tbl, level, field_order)
out:write("{")
local sep = "\n"
local indentation = " "
local indent = true
local i = 1
for k, v, sub_order in util.sortedpairs(tbl, field_order) do
out:write(sep)
if indent then
for n = 1,level do out:write(indentation) end
end
if k == i then
i = i + 1
else
if type(k) == "string" and k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then
out:write(k)
else
out:write("[")
write_value(out, k, level)
out:write("]")
end
out:write(" = ")
end
write_value(out, v, level, sub_order)
if type(v) == "number" then
sep = ", "
indent = false
else
sep = ",\n"
indent = true
end
end
if sep ~= "\n" then
out:write("\n")
for n = 1,level-1 do out:write(indentation) end
end
out:write("}")
end
--- Write a table as series of assignments to a writer object.
-- @param out table or userdata: a writer object supporting :write() method.
-- @param tbl table: the table to be written.
-- @param field_order table: optional prioritization table
local function write_table_as_assignments(out, tbl, field_order)
for k, v, sub_order in util.sortedpairs(tbl, field_order) do
out:write(k.." = ")
write_value(out, v, 0, sub_order)
out:write("\n")
end
end
--- Save the contents of a table to a string.
-- 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.
-- @param tbl table: the table containing the data to be written
-- @param field_order table: an optional array indicating the order of top-level fields.
-- @return string
function persist.save_from_table_to_string(tbl, field_order)
local out = {buffer = {}}
function out:write(data) table.insert(self.buffer, data) end
write_table_as_assignments(out, tbl, field_order)
return table.concat(out.buffer)
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.
-- @param filename string: the output filename
-- @param tbl table: the table containing the data to be written
-- @param field_order table: an optional array indicating the order of top-level fields.
-- @return boolean or (nil, string): true if successful, or nil and a
-- message in case of errors.
function persist.save_from_table(filename, tbl, field_order)
local out = io.open(filename, "w")
if not out then
return nil, "Cannot create file at "..filename
end
write_table_as_assignments(out, tbl, field_order)
out:close()
return true
end
return persist

View File

@ -0,0 +1,79 @@
--- Module implementing the LuaRocks "purge" command.
-- Remove all rocks from a given tree.
local purge = {}
package.loaded["luarocks.purge"] = purge
local util = require("luarocks.util")
local fs = require("luarocks.fs")
local path = require("luarocks.path")
local search = require("luarocks.search")
local deps = require("luarocks.deps")
local repos = require("luarocks.repos")
local manif = require("luarocks.manif")
local cfg = require("luarocks.cfg")
local remove = require("luarocks.remove")
util.add_run_function(purge)
purge.help_summary = "Remove all installed rocks from a tree."
purge.help_arguments = "--tree=<tree> [--old-versions]"
purge.help = [[
This command removes rocks en masse from a given tree.
By default, it removes all rocks from a tree.
The --tree argument is mandatory: luarocks purge does not
assume a default tree.
--old-versions Keep the highest-numbered version of each
rock and remove the other ones. By default
it only removes old versions if they are
not needed as dependencies. This can be
overridden with the flag --force.
]]
function purge.command(flags)
local tree = flags["tree"]
if type(tree) ~= "string" then
return nil, "The --tree argument is mandatory. "..util.see_help("purge")
end
local results = {}
local query = search.make_query("")
query.exact_name = false
if not fs.is_dir(tree) then
return nil, "Directory not found: "..tree
end
local ok, err = fs.check_command_permissions(flags)
if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end
search.manifest_search(results, path.rocks_dir(tree), query)
local sort = function(a,b) return deps.compare_versions(b,a) end
if flags["old-versions"] then
sort = deps.compare_versions
end
for package, versions in util.sortedpairs(results) do
for version, repositories in util.sortedpairs(versions, sort) do
if flags["old-versions"] then
util.printout("Keeping "..package.." "..version.."...")
local ok, err = remove.remove_other_versions(package, version, flags["force"], flags["force-fast"])
if not ok then
util.printerr(err)
end
break
else
util.printout("Removing "..package.." "..version.."...")
local ok, err = repos.delete_version(package, version, "none", true)
if not ok then
util.printerr(err)
end
end
end
end
return manif.make_manifest(cfg.rocks_dir, "one")
end
return purge

View File

@ -0,0 +1,33 @@
--- Module implementing the luarocks-admin "refresh_cache" command.
local refresh_cache = {}
package.loaded["luarocks.refresh_cache"] = refresh_cache
local util = require("luarocks.util")
local cfg = require("luarocks.cfg")
local cache = require("luarocks.cache")
util.add_run_function(refresh_cache)
refresh_cache.help_summary = "Refresh local cache of a remote rocks server."
refresh_cache.help_arguments = "[--from=<server>]"
refresh_cache.help = [[
The flag --from indicates which server to use.
If not given, the default server set in the upload_server variable
from the configuration file is used instead.
]]
function refresh_cache.command(flags)
local server, upload_server = cache.get_upload_server(flags["server"])
if not server then return nil, upload_server end
local download_url = cache.get_server_urls(server, upload_server)
local ok, err = cache.refresh_local_cache(server, download_url, cfg.upload_user, cfg.upload_password)
if not ok then
return nil, err
else
return true
end
end
return refresh_cache

View File

@ -0,0 +1,174 @@
--- Module implementing the LuaRocks "remove" command.
-- Uninstalls rocks.
local remove = {}
package.loaded["luarocks.remove"] = remove
local search = require("luarocks.search")
local deps = require("luarocks.deps")
local fetch = require("luarocks.fetch")
local repos = require("luarocks.repos")
local path = require("luarocks.path")
local util = require("luarocks.util")
local cfg = require("luarocks.cfg")
local fs = require("luarocks.fs")
local manif = require("luarocks.manif")
util.add_run_function(remove)
remove.help_summary = "Uninstall a rock."
remove.help_arguments = "[--force|--force-fast] <name> [<version>]"
remove.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.
To perform a forced removal without reporting dependency issues,
use --force-fast.
]]..util.deps_mode_help()
--- 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, deps_mode)
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, deps_mode)
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.
-- @param deps_mode: string: Which trees to check dependencies for:
-- "one" for the current default tree, "all" for all trees,
-- "order" for all trees with priority >= the current default, "none" for no trees.
-- @return boolean or (nil, string): true on success or nil and an error message.
local function delete_versions(name, versions, deps_mode)
for version, _ in pairs(versions) do
util.printout("Removing "..name.." "..version.."...")
local ok, err = repos.delete_version(name, version, deps_mode)
if not ok then return nil, err end
end
return true
end
function remove.remove_search_results(results, name, deps_mode, force, fast)
local versions = results[name]
local version = next(versions)
local second = next(versions, version)
local dependents = {}
if not fast then
util.printout("Checking stability of dependencies in the absence of")
util.printout(name.." "..table.concat(util.keys(versions), ", ").."...")
util.printout()
dependents = check_dependents(name, versions, deps_mode)
end
if #dependents > 0 then
if force or fast then
util.printerr("The following packages may be broken by this forced removal:")
for _, dependent in ipairs(dependents) do
util.printerr(dependent.name.." "..dependent.version)
end
util.printerr()
else
if not second then
util.printerr("Will not remove "..name.." "..version..".")
util.printerr("Removing it would break dependencies for: ")
else
util.printerr("Will not remove installed versions of "..name..".")
util.printerr("Removing them would break dependencies for: ")
end
for _, dependent in ipairs(dependents) do
util.printerr(dependent.name.." "..dependent.version)
end
util.printerr()
util.printerr("Use --force to force removal (warning: this may break modules).")
return nil, "Failed removing."
end
end
local ok, err = delete_versions(name, versions, deps_mode)
if not ok then return nil, err end
util.printout("Removal successful.")
return true
end
function remove.remove_other_versions(name, version, force, fast)
local results = {}
search.manifest_search(results, cfg.rocks_dir, { name = name, exact_name = true, constraints = {{ op = "~=", version = version}} })
if results[name] then
return remove.remove_search_results(results, name, cfg.deps_mode, force, fast)
end
return true
end
--- Driver function for the "remove" 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, exitcode): True if removal was
-- successful, nil and an error message otherwise. exitcode is optionally returned.
function remove.command(flags, name, version)
if type(name) ~= "string" then
return nil, "Argument missing. "..util.see_help("remove")
end
local deps_mode = flags["deps-mode"] or cfg.deps_mode
local ok, err = fs.check_command_permissions(flags)
if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end
local rock_type = name:match("%.(rock)$") or name:match("%.(rockspec)$")
local filename = name
if rock_type then
name, version = path.parse_name(filename)
if not name then return nil, "Invalid "..rock_type.." filename: "..filename end
end
local results = {}
name = name:lower()
search.manifest_search(results, cfg.rocks_dir, search.make_query(name, version))
if not results[name] then
return nil, "Could not find rock '"..name..(version and " "..version or "").."' in "..path.rocks_tree_to_string(cfg.root_dir)
end
local ok, err = remove.remove_search_results(results, name, deps_mode, flags["force"], flags["force-fast"])
if not ok then
return nil, err
end
manif.check_dependencies(nil, deps.get_deps_mode(flags))
return true
end
return remove

View File

@ -0,0 +1,422 @@
--- Functions for managing the repository on disk.
local repos = {}
package.loaded["luarocks.repos"] = repos
local fs = require("luarocks.fs")
local path = require("luarocks.path")
local cfg = require("luarocks.cfg")
local util = require("luarocks.util")
local dir = require("luarocks.dir")
local manif = require("luarocks.manif")
local deps = require("luarocks.deps")
-- Tree of files installed by a package are stored
-- in its rock manifest. Some of these files have to
-- be deployed to locations where Lua can load them as
-- modules or where they can be used as commands.
-- These files are characterised by pair
-- (deploy_type, file_path), where deploy_type is the first
-- component of the file path and file_path is the rest of the
-- path. Only files with deploy_type in {"lua", "lib", "bin"}
-- are deployed somewhere.
-- Each deployed file provides an "item". An item is
-- characterised by pair (item_type, item_name).
-- item_type is "command" for files with deploy_type
-- "bin" and "module" for deploy_type in {"lua", "lib"}.
-- item_name is same as file_path for commands
-- and is produced using path.path_to_module(file_path)
-- for modules.
--- 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.
local function get_installed_versions(name)
assert(type(name) == "string")
local dirs = fs.list_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 repos.is_installed(name, version)
assert(type(name) == "string")
assert(type(version) == "string")
return fs.is_dir(path.install_dir(name, version))
end
local function recurse_rock_manifest_tree(file_tree, action)
assert(type(file_tree) == "table")
assert(type(action) == "function")
local function do_recurse_rock_manifest_tree(tree, parent_path, parent_module)
for file, sub in pairs(tree) do
if type(sub) == "table" then
local ok, err = do_recurse_rock_manifest_tree(sub, parent_path..file.."/", parent_module..file..".")
if not ok then return nil, err end
else
local ok, err = action(parent_path, parent_module, file)
if not ok then return nil, err end
end
end
return true
end
return do_recurse_rock_manifest_tree(file_tree, "", "")
end
local function store_package_data(result, name, file_tree)
if not file_tree then return end
return recurse_rock_manifest_tree(file_tree,
function(parent_path, parent_module, file)
local pathname = parent_path..file
result[path.path_to_module(pathname)] = pathname
return true
end
)
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 repos.package_modules(package, version)
assert(type(package) == "string")
assert(type(version) == "string")
local result = {}
local rock_manifest = manif.load_rock_manifest(package, version)
store_package_data(result, package, rock_manifest.lib)
store_package_data(result, package, rock_manifest.lua)
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 repos.package_commands(package, version)
assert(type(package) == "string")
assert(type(version) == "string")
local result = {}
local rock_manifest = manif.load_rock_manifest(package, version)
store_package_data(result, package, rock_manifest.bin)
return result
end
--- Check if a rock contains binary executables.
-- @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 executables, or false if it is a pure-Lua rock.
function repos.has_binaries(name, version)
assert(type(name) == "string")
assert(type(version) == "string")
local rock_manifest = manif.load_rock_manifest(name, version)
if rock_manifest.bin then
for name, md5 in pairs(rock_manifest.bin) do
-- TODO verify that it is the same file. If it isn't, find the actual command.
if fs.is_actual_binary(dir.path(cfg.deploy_bin_dir, name)) then
return true
end
end
end
return false
end
function repos.run_hook(rockspec, hook_name)
assert(type(rockspec) == "table")
assert(type(hook_name) == "string")
local hooks = rockspec.hooks
if not hooks then
return true
end
if cfg.hooks_enabled == false then
return nil, "This rockspec contains hooks, which are blocked by the 'hooks_enabled' setting in your LuaRocks configuration."
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
util.printout(hook)
if not fs.execute(hook) then
return nil, "Failed running "..hook_name.." hook."
end
end
return true
end
function repos.should_wrap_bin_scripts(rockspec)
assert(type(rockspec) == "table")
if cfg.wrap_bin_scripts ~= nil then
return cfg.wrap_bin_scripts
end
if rockspec.deploy and rockspec.deploy.wrap_bin_scripts == false then
return false
end
return true
end
local function find_suffixed(file, suffix)
local filenames = {file}
if suffix and suffix ~= "" then
table.insert(filenames, 1, file .. suffix)
end
for _, filename in ipairs(filenames) do
if fs.exists(filename) then
return filename
end
end
return nil, table.concat(filenames, ", ") .. " not found"
end
local function move_suffixed(from_file, to_file, suffix)
local suffixed_from_file, err = find_suffixed(from_file, suffix)
if not suffixed_from_file then
return nil, "Could not move " .. from_file .. " to " .. to_file .. ": " .. err
end
suffix = suffixed_from_file:sub(#from_file + 1)
local suffixed_to_file = to_file .. suffix
return fs.move(suffixed_from_file, suffixed_to_file)
end
local function delete_suffixed(file, suffix)
local suffixed_file, err = find_suffixed(file, suffix)
if not suffixed_file then
return nil, "Could not remove " .. file .. ": " .. err
end
fs.delete(suffixed_file)
if fs.exists(suffixed_file) then
return nil, "Failed deleting " .. suffixed_file .. ": file still exists"
end
return true
end
-- Files can be deployed using versioned and non-versioned names.
-- Several items with same type and name can exist if they are
-- provided by different packages or versions. In any case
-- item from the newest version of lexicographically smallest package
-- is deployed using non-versioned name and others use versioned names.
local function get_deploy_paths(name, version, deploy_type, file_path)
local deploy_dir = cfg["deploy_" .. deploy_type .. "_dir"]
local non_versioned = dir.path(deploy_dir, file_path)
local versioned = path.versioned_name(non_versioned, deploy_dir, name, version)
return non_versioned, versioned
end
local function prepare_target(name, version, deploy_type, file_path, suffix)
local non_versioned, versioned = get_deploy_paths(name, version, deploy_type, file_path)
local item_type, item_name = manif.get_provided_item(deploy_type, file_path)
local cur_name, cur_version = manif.get_current_provider(item_type, item_name)
if not cur_name then
return non_versioned
elseif name < cur_name or (name == cur_name and deps.compare_versions(version, cur_version)) then
-- New version has priority. Move currently provided version back using versioned name.
local cur_deploy_type, cur_file_path = manif.get_providing_file(cur_name, cur_version, item_type, item_name)
local cur_non_versioned, cur_versioned = get_deploy_paths(cur_name, cur_version, cur_deploy_type, cur_file_path)
local dir_ok, dir_err = fs.make_dir(dir.dir_name(cur_versioned))
if not dir_ok then return nil, dir_err end
local move_ok, move_err = move_suffixed(cur_non_versioned, cur_versioned, suffix)
if not move_ok then return nil, move_err end
return non_versioned
else
-- Current version has priority, deploy new version using versioned name.
return versioned
end
end
--- Deploy a package from the rocks subdirectory.
-- @param name string: name of package
-- @param version string: exact package version in string format
-- @param wrap_bin_scripts bool: whether commands written in Lua should be wrapped.
-- @param deps_mode: string: Which trees to check dependencies for:
-- "one" for the current default tree, "all" for all trees,
-- "order" for all trees with priority >= the current default, "none" for no trees.
function repos.deploy_files(name, version, wrap_bin_scripts, deps_mode)
assert(type(name) == "string")
assert(type(version) == "string")
assert(type(wrap_bin_scripts) == "boolean")
local rock_manifest = manif.load_rock_manifest(name, version)
local function deploy_file_tree(deploy_type, source_dir, move_fn, suffix)
if not rock_manifest[deploy_type] then
return true
end
return recurse_rock_manifest_tree(rock_manifest[deploy_type], function(parent_path, parent_module, file)
local file_path = parent_path .. file
local source = dir.path(source_dir, file_path)
local target, prepare_err = prepare_target(name, version, deploy_type, file_path, suffix)
if not target then return nil, prepare_err end
local dir_ok, dir_err = fs.make_dir(dir.dir_name(target))
if not dir_ok then return nil, dir_err end
local suffixed_target, mover = move_fn(source, target)
if fs.exists(suffixed_target) then
local backup = suffixed_target
repeat
backup = backup.."~"
until not fs.exists(backup) -- Slight race condition here, but shouldn't be a problem.
util.printerr("Warning: "..suffixed_target.." is not tracked by this installation of LuaRocks. Moving it to "..backup)
local move_ok, move_err = fs.move(suffixed_target, backup)
if not move_ok then return nil, move_err end
end
local move_ok, move_err = mover()
if not move_ok then return nil, move_err end
fs.remove_dir_tree_if_empty(dir.dir_name(source))
return true
end)
end
local function install_binary(source, target)
if wrap_bin_scripts and fs.is_lua(source) then
return target .. (cfg.wrapper_suffix or ""), function() return fs.wrap_script(source, target, name, version) end
else
return target, function() return fs.copy_binary(source, target) end
end
end
local function make_mover(perms)
return function(source, target)
return target, function() return fs.move(source, target, perms) end
end
end
local ok, err = deploy_file_tree("bin", path.bin_dir(name, version), install_binary, cfg.wrapper_suffix)
if not ok then return nil, err end
ok, err = deploy_file_tree("lua", path.lua_dir(name, version), make_mover(cfg.perm_read))
if not ok then return nil, err end
ok, err = deploy_file_tree("lib", path.lib_dir(name, version), make_mover(cfg.perm_exec))
if not ok then return nil, err end
return manif.add_to_manifest(name, version, nil, deps_mode)
end
--- Delete a package from the local repository.
-- @param name string: name of package
-- @param version string: exact package version in string format
-- @param deps_mode: string: Which trees to check dependencies for:
-- "one" for the current default tree, "all" for all trees,
-- "order" for all trees with priority >= the current default, "none" for no trees.
-- @param quick boolean: do not try to fix the versioned name
-- of another version that provides the same module that
-- was deleted. This is used during 'purge', as every module
-- will be eventually deleted.
function repos.delete_version(name, version, deps_mode, quick)
assert(type(name) == "string")
assert(type(version) == "string")
assert(type(deps_mode) == "string")
local rock_manifest = manif.load_rock_manifest(name, version)
if not rock_manifest then
return nil, "rock_manifest file not found for "..name.." "..version.." - not a LuaRocks 2 tree?"
end
local function delete_deployed_file_tree(deploy_type, suffix)
if not rock_manifest[deploy_type] then
return true
end
return recurse_rock_manifest_tree(rock_manifest[deploy_type], function(parent_path, parent_module, file)
local file_path = parent_path .. file
local non_versioned, versioned = get_deploy_paths(name, version, deploy_type, file_path)
-- Figure out if the file is deployed using versioned or non-versioned name.
local target
local item_type, item_name = manif.get_provided_item(deploy_type, file_path)
local cur_name, cur_version = manif.get_current_provider(item_type, item_name)
if cur_name == name and cur_version == version then
-- This package has highest priority, should be in non-versioned location.
target = non_versioned
else
target = versioned
end
local ok, err = delete_suffixed(target, suffix)
if not ok then return nil, err end
if not quick and target == non_versioned then
-- If another package provides this file, move its version
-- into non-versioned location instead.
local next_name, next_version = manif.get_next_provider(item_type, item_name)
if next_name then
local next_deploy_type, next_file_path = manif.get_providing_file(next_name, next_version, item_type, item_name)
local next_non_versioned, next_versioned = get_deploy_paths(next_name, next_version, next_deploy_type, next_file_path)
local move_ok, move_err = move_suffixed(next_versioned, next_non_versioned, suffix)
if not move_ok then return nil, move_err end
fs.remove_dir_tree_if_empty(dir.dir_name(next_versioned))
end
end
fs.remove_dir_tree_if_empty(dir.dir_name(target))
return true
end)
end
local ok, err = delete_deployed_file_tree("bin", cfg.wrapper_suffix)
if not ok then return nil, err end
ok, err = delete_deployed_file_tree("lua")
if not ok then return nil, err end
ok, err = delete_deployed_file_tree("lib")
if not ok then return nil, err end
fs.delete(path.install_dir(name, version))
if not get_installed_versions(name) then
fs.delete(dir.path(cfg.rocks_dir, name))
end
if quick then
return true
end
return manif.remove_from_manifest(name, version, nil, deps_mode)
end
return repos

View File

@ -0,0 +1,2 @@
--- Retained for compatibility reasons only. Use luarocks.loader instead.
return require("luarocks.loader")

View File

@ -0,0 +1,483 @@
--- Module implementing the LuaRocks "search" command.
-- Queries LuaRocks servers.
local search = {}
package.loaded["luarocks.search"] = search
local dir = require("luarocks.dir")
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")
util.add_run_function(search)
search.help_summary = "Query the LuaRocks servers."
search.help_arguments = "[--source] [--binary] { <name> [<version>] | --all }"
search.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 query.arch:gmatch("[%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
-- values are tables matching version strings to arrays of
-- tables with fields "arch" and "repo".
-- @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
-- values are tables matching version strings to arrays of
-- tables with fields "arch" and "repo".
-- @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.
-- @return table: The results table, where keys are package names and
-- values are tables matching version strings to arrays of
-- tables with fields "arch" and "repo".
-- If a table was given in the "results" parameter, that is the result value.
function search.disk_search(repo, query, results)
assert(type(repo) == "string")
assert(type(query) == "table")
assert(type(results) == "table" or not results)
local fs = require("luarocks.fs")
if not results then
results = {}
end
query_arch_as_table(query)
for name in fs.dir(repo) do
local pathname = dir.path(repo, name)
local rname, rversion, rarch = path.parse_name(name)
if rname and (pathname:match(".rockspec$") or pathname:match(".rock$")) then
store_if_match(results, repo, rname, rversion, rarch, query)
elseif fs.is_dir(pathname) then
for version in fs.dir(pathname) do
if version:match("-%d+$") then
store_if_match(results, repo, name, version, "installed", query)
end
end
end
end
return results
end
--- Perform search on a rocks server or tree.
-- @param results table: The results table, where keys are package names and
-- values are tables matching version strings to arrays of
-- tables with fields "arch" and "repo".
-- @param repo string: The URL of a rocks server or
-- the pathname of a rocks tree (as returned by path.rocks_dir()).
-- @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 lua_version string: Lua version in "5.x" format, defaults to installed version.
-- @return true or, in case of errors, nil, an error message and an optional error code.
function search.manifest_search(results, repo, query, lua_version)
assert(type(results) == "table")
assert(type(repo) == "string")
assert(type(query) == "table")
query_arch_as_table(query)
local manifest, err, errcode = manif.load_manifest(repo, lua_version)
if not manifest then
return nil, err, errcode
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
return true
end
--- Search on all configured rocks servers.
-- @param query table: A dependency query.
-- @param lua_version string: Lua version in "5.x" format, defaults to installed version.
-- @return table: A table where keys are package names
-- and values are tables matching version strings to arrays of
-- tables with fields "arch" and "repo".
function search.search_repos(query, lua_version)
assert(type(query) == "table")
local results = {}
for _, repo in ipairs(cfg.rocks_servers) do
if not cfg.disabled_servers[repo] then
if type(repo) == "string" then
repo = { repo }
end
for _, mirror in ipairs(repo) do
local protocol, pathname = dir.split_url(mirror)
if protocol == "file" then
mirror = pathname
end
local ok, err, errcode = search.manifest_search(results, mirror, query, lua_version)
if errcode == "network" then
cfg.disabled_servers[repo] = true
end
if ok then
break
else
util.warning("Failed searching manifest: "..err)
end
end
end
end
-- search through rocks in cfg.rocks_provided
local provided_repo = "provided by VM or rocks_provided"
for name, versions in pairs(cfg.rocks_provided) do
store_if_match(results, provided_repo, name, versions, "installed", 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 search.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
-- Find out which other Lua versions provide rock versions matching a query,
-- @param query table: A dependency query matching a single rock.
-- @return table: array of Lua versions supported, in "5.x" format.
local function supported_lua_versions(query)
local results = {}
for lua_version in util.lua_versions() do
if lua_version ~= cfg.lua_version then
if search.search_repos(query, lua_version)[query.name] then
table.insert(results, lua_version)
end
end
end
return results
end
--- Attempt to get a single URL for a given search for a rock.
-- @param query table: A dependency query matching a single rock.
-- @return string or (nil, string): URL for latest matching version
-- of the rock if it was found, or nil followed by an error message.
function search.find_suitable_rock(query)
assert(type(query) == "table")
local results = search.search_repos(query)
local first_rock = next(results)
if not first_rock then
if cfg.rocks_provided[query.name] == nil then
-- Check if constraints are satisfiable with other Lua versions.
local lua_versions = supported_lua_versions(query)
if #lua_versions ~= 0 then
-- Build a nice message in "only Lua 5.x and 5.y but not 5.z." format
for i, lua_version in ipairs(lua_versions) do
lua_versions[i] = "Lua "..lua_version
end
local versions_message = "only "..table.concat(lua_versions, " and ")..
" but not Lua "..cfg.lua_version.."."
if #query.constraints == 0 then
return nil, query.name.." supports "..versions_message
elseif #query.constraints == 1 and query.constraints[1].op == "==" then
return nil, query.name.." "..query.constraints[1].version.string.." supports "..versions_message
else
return nil, "Matching "..query.name.." versions support "..versions_message
end
end
end
return nil, "No results matching query were found."
elseif next(results, first_rock) then
-- Shouldn't happen as query must match only one package.
return nil, "Several rocks matched query."
elseif cfg.rocks_provided[query.name] ~= nil then
-- Do not install versions listed in cfg.rocks_provided.
return nil, "Rock "..query.name.." "..cfg.rocks_provided[query.name]..
" was found but it is provided by VM or 'rocks_provided' in the config file."
else
return pick_latest_version(query.name, results[first_rock])
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 porcelain boolean or nil: A flag to force machine-friendly output.
function search.print_results(results, porcelain)
assert(type(results) == "table")
assert(type(porcelain) == "boolean" or not porcelain)
for package, versions in util.sortedpairs(results) do
if not porcelain then
util.printout(package)
end
for version, repos in util.sortedpairs(versions, deps.compare_versions) do
for _, repo in ipairs(repos) do
repo.repo = dir.normalize(repo.repo)
if porcelain then
util.printout(package, version, repo.arch, repo.repo)
else
util.printout(" "..version.." ("..repo.arch..") - "..repo.repo)
end
end
end
if not porcelain then
util.printout()
end
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, repositories in pairs(versions) do
for _, repo in ipairs(repositories) 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.
-- @param name string: A rock name
-- @param 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 search.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 = search.make_query(name, version)
query.arch = "src|rockspec"
local url, err = search.find_suitable_rock(query)
if not url then
return nil, "Could not find a result named "..name..(version and " "..version or "")..": "..err
end
return action(url, ...)
end
function search.pick_installed_rock(name, version, given_tree)
local results = {}
local query = search.make_query(name, version)
query.exact_name = true
local tree_map = {}
local trees = cfg.rocks_trees
if given_tree then
trees = { given_tree }
end
for _, tree in ipairs(trees) do
local rocks_dir = path.rocks_dir(tree)
tree_map[rocks_dir] = tree
search.manifest_search(results, rocks_dir, query)
end
if not next(results) then --
return nil,"cannot find package "..name.." "..(version or "").."\nUse 'list' to find installed rocks."
end
version = nil
local repo_url
local package, versions = util.sortedpairs(results)()
--question: what do we do about multiple versions? This should
--give us the latest version on the last repo (which is usually the global one)
for vs, repositories in util.sortedpairs(versions, deps.compare_versions) do
if not version then version = vs end
for _, rp in ipairs(repositories) do repo_url = rp.repo end
end
local repo = tree_map[repo_url]
return name, version, repo, repo_url
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 search.command(flags, name, version)
if flags["all"] then
name, version = "", nil
end
if type(name) ~= "string" and not flags["all"] then
return nil, "Enter name and version or use --all. "..util.see_help("search")
end
local query = search.make_query(name:lower(), version)
query.exact_name = false
local results, err = search.search_repos(query)
local porcelain = flags["porcelain"]
util.title("Search results:", porcelain, "=")
local sources, binaries = split_source_and_binary_results(results)
if next(sources) and not flags["binary"] then
util.title("Rockspecs and source rocks:", porcelain)
search.print_results(sources, porcelain)
end
if next(binaries) and not flags["source"] then
util.title("Binary and pure-Lua rocks:", porcelain)
search.print_results(binaries, porcelain)
end
return true
end
return search

View File

@ -0,0 +1,152 @@
--- Module implementing the LuaRocks "show" command.
-- Shows information about an installed rock.
local show = {}
package.loaded["luarocks.show"] = show
local search = require("luarocks.search")
local cfg = require("luarocks.cfg")
local util = require("luarocks.util")
local path = require("luarocks.path")
local deps = require("luarocks.deps")
local fetch = require("luarocks.fetch")
local manif = require("luarocks.manif")
util.add_run_function(show)
show.help_summary = "Show information about an installed rock."
show.help = [[
<argument> is an existing package name.
Without any flags, show all module information.
With these flags, return only the desired information:
--home home page of project
--modules all modules provided by this package as used by require()
--deps packages this package depends on
--rockspec the full path of the rockspec file
--mversion the package version
--rock-tree local tree where rock is installed
--rock-dir data directory of the installed rock
]]
local function keys_as_string(t, sep)
local keys = util.keys(t)
table.sort(keys)
return table.concat(keys, sep or " ")
end
local function word_wrap(line)
local width = tonumber(os.getenv("COLUMNS")) or 80
if width > 80 then width = 80 end
if #line > width then
local brk = width
while brk > 0 and line:sub(brk, brk) ~= " " do
brk = brk - 1
end
if brk > 0 then
return line:sub(1, brk-1) .. "\n" .. word_wrap(line:sub(brk+1))
end
end
return line
end
local function format_text(text)
text = text:gsub("^%s*",""):gsub("%s$", ""):gsub("\n[ \t]+","\n"):gsub("([^\n])\n([^\n])","%1 %2")
local paragraphs = util.split_string(text, "\n\n")
for n, line in ipairs(paragraphs) do
paragraphs[n] = word_wrap(line)
end
return (table.concat(paragraphs, "\n\n"):gsub("%s$", ""))
end
local function installed_rock_label(name, tree)
local installed, version
if cfg.rocks_provided[name] then
installed, version = true, cfg.rocks_provided[name]
else
installed, version = search.pick_installed_rock(name, nil, tree)
end
return installed and "(using "..version..")" or "(missing)"
end
--- Driver function for "show" command.
-- @param name or nil: an existing package name.
-- @param version string or nil: a version may also be passed.
-- @return boolean: True if succeeded, nil on errors.
function show.command(flags, name, version)
if not name then
return nil, "Argument missing. "..util.see_help("show")
end
local repo, repo_url
name, version, repo, repo_url = search.pick_installed_rock(name:lower(), version, flags["tree"])
if not name then
return nil, version
end
local directory = path.install_dir(name,version,repo)
local rockspec_file = path.rockspec_file(name, version, repo)
local rockspec, err = fetch.load_local_rockspec(rockspec_file)
if not rockspec then return nil,err end
local descript = rockspec.description or {}
local manifest, err = manif.load_manifest(repo_url)
if not manifest then return nil,err end
local minfo = manifest.repository[name][version][1]
if flags["rock-tree"] then util.printout(path.rocks_tree_to_string(repo))
elseif flags["rock-dir"] then util.printout(directory)
elseif flags["home"] then util.printout(descript.homepage)
elseif flags["modules"] then util.printout(keys_as_string(minfo.modules, "\n"))
elseif flags["deps"] then util.printout(keys_as_string(minfo.dependencies))
elseif flags["rockspec"] then util.printout(rockspec_file)
elseif flags["mversion"] then util.printout(version)
else
util.printout()
util.printout(rockspec.package.." "..rockspec.version.." - "..(descript.summary or ""))
util.printout()
if descript.detailed then
util.printout(format_text(descript.detailed))
util.printout()
end
if descript.license then
util.printout("License: ", descript.license)
end
if descript.homepage then
util.printout("Homepage: ", descript.homepage)
end
util.printout("Installed in: ", path.rocks_tree_to_string(repo))
if next(minfo.modules) then
util.printout()
util.printout("Modules:")
for mod, filename in util.sortedpairs(minfo.modules) do
util.printout("\t"..mod.." ("..path.which(mod, filename, name, version, repo, manifest)..")")
end
end
local direct_deps = {}
if #rockspec.dependencies > 0 then
util.printout()
util.printout("Depends on:")
for _, dep in ipairs(rockspec.dependencies) do
direct_deps[dep.name] = true
util.printout("\t"..deps.show_dep(dep).." "..installed_rock_label(dep.name, flags["tree"]))
end
end
local has_indirect_deps
for dep_name in util.sortedpairs(minfo.dependencies) do
if not direct_deps[dep_name] then
if not has_indirect_deps then
util.printout()
util.printout("Indirectly pulling:")
has_indirect_deps = true
end
util.printout("\t"..dep_name.." "..installed_rock_label(dep_name, flags["tree"]))
end
end
util.printout()
end
return true
end
return show

View File

@ -0,0 +1,14 @@
local site_config = {}
site_config.LUA_INCDIR=[[C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks\include]]
site_config.LUA_LIBDIR=[[C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks]]
site_config.LUA_BINDIR=[[C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks]]
site_config.LUA_INTERPRETER=[[lua5.1]]
site_config.LUAROCKS_UNAME_S=[[MINGW]]
site_config.LUAROCKS_UNAME_M=[[x86]]
site_config.LUAROCKS_ROCKS_TREE=[[C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks\systree]]
site_config.LUAROCKS_PREFIX=[[C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks]]
site_config.LUAROCKS_DOWNLOADER=[[wget]]
site_config.LUAROCKS_MD5CHECKER=[[md5sum]]
site_config.LUAROCKS_FORCE_CONFIG=true
site_config.LUAROCKS_SYSCONFDIR=[[C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks]]
return site_config

View File

@ -0,0 +1,700 @@
--- Patch utility to apply unified diffs.
--
-- http://lua-users.org/wiki/LuaPatch
--
-- (c) 2008 David Manura, Licensed under the same terms as Lua (MIT license).
-- Code is heavilly based on the Python-based patch.py version 8.06-1
-- Copyright (c) 2008 rainforce.org, MIT License
-- Project home: http://code.google.com/p/python-patch/ .
-- Version 0.1
local patch = {}
local fs = require("luarocks.fs")
local util = require("luarocks.util")
local io = io
local os = os
local string = string
local table = table
local format = string.format
-- logging
local debugmode = false
local function debug(_) end
local function info(_) end
local function warning(s) io.stderr:write(s .. '\n') end
-- Returns boolean whether string s2 starts with string s.
local function startswith(s, s2)
return s:sub(1, #s2) == s2
end
-- Returns boolean whether string s2 ends with string s.
local function endswith(s, s2)
return #s >= #s2 and s:sub(#s-#s2+1) == s2
end
-- Returns string s after filtering out any new-line characters from end.
local function endlstrip(s)
return s:gsub('[\r\n]+$', '')
end
-- Returns shallow copy of table t.
local function table_copy(t)
local t2 = {}
for k,v in pairs(t) do t2[k] = v end
return t2
end
local function exists(filename)
local fh = io.open(filename)
local result = fh ~= nil
if fh then fh:close() end
return result
end
local function isfile() return true end --FIX?
local function read_file(filename)
local fh, data, err, oserr
fh, err, oserr = io.open(filename, 'rb')
if not fh then return fh, err, oserr end
data, err, oserr = fh:read'*a'
fh:close()
if not data then return nil, err, oserr end
return data
end
local function write_file(filename, data)
local fh, status, err, oserr
fh, err, oserr = io.open(filename 'wb')
if not fh then return fh, err, oserr end
status, err, oserr = fh:write(data)
fh:close()
if not status then return nil, err, oserr end
return true
end
local function file_copy(src, dest)
local data, status, err, oserr
data, err, oserr = read_file(src)
if not data then return data, err, oserr end
status, err, oserr = write_file(dest)
if not status then return status, err, oserr end
return true
end
local function string_as_file(s)
return {
at = 0,
str = s,
len = #s,
eof = false,
read = function(self, n)
if self.eof then return nil end
local chunk = self.str:sub(self.at, self.at + n - 1)
self.at = self.at + n
if self.at > self.len then
self.eof = true
end
return chunk
end,
close = function(self)
self.eof = true
end,
}
end
--
-- file_lines(f) is similar to f:lines() for file f.
-- The main difference is that read_lines includes
-- new-line character sequences ("\n", "\r\n", "\r"),
-- if any, at the end of each line. Embedded "\0" are also handled.
-- Caution: The newline behavior can depend on whether f is opened
-- in binary or ASCII mode.
-- (file_lines - version 20080913)
--
local function file_lines(f)
local CHUNK_SIZE = 1024
local buffer = ""
local pos_beg = 1
return function()
local pos, chars
while 1 do
pos, chars = buffer:match('()([\r\n].)', pos_beg)
if pos or not f then
break
elseif f then
local chunk = f:read(CHUNK_SIZE)
if chunk then
buffer = buffer:sub(pos_beg) .. chunk
pos_beg = 1
else
f = nil
end
end
end
if not pos then
pos = #buffer
elseif chars == '\r\n' then
pos = pos + 1
end
local line = buffer:sub(pos_beg, pos)
pos_beg = pos + 1
if #line > 0 then
return line
end
end
end
local function match_linerange(line)
local m1, m2, m3, m4 = line:match("^@@ %-(%d+),(%d+) %+(%d+),(%d+)")
if not m1 then m1, m3, m4 = line:match("^@@ %-(%d+) %+(%d+),(%d+)") end
if not m1 then m1, m2, m3 = line:match("^@@ %-(%d+),(%d+) %+(%d+)") end
if not m1 then m1, m3 = line:match("^@@ %-(%d+) %+(%d+)") end
return m1, m2, m3, m4
end
function patch.read_patch(filename, data)
-- define possible file regions that will direct the parser flow
local state = 'header'
-- 'header' - comments before the patch body
-- 'filenames' - lines starting with --- and +++
-- 'hunkhead' - @@ -R +R @@ sequence
-- 'hunkbody'
-- 'hunkskip' - skipping invalid hunk mode
local all_ok = true
local lineends = {lf=0, crlf=0, cr=0}
local files = {source={}, target={}, hunks={}, fileends={}, hunkends={}}
local nextfileno = 0
local nexthunkno = 0 --: even if index starts with 0 user messages
-- number hunks from 1
-- hunkinfo holds parsed values, hunkactual - calculated
local hunkinfo = {
startsrc=nil, linessrc=nil, starttgt=nil, linestgt=nil,
invalid=false, text={}
}
local hunkactual = {linessrc=nil, linestgt=nil}
info(format("reading patch %s", filename))
local fp
if data then
fp = string_as_file(data)
else
fp = filename == '-' and io.stdin or assert(io.open(filename, "rb"))
end
local lineno = 0
for line in file_lines(fp) do
lineno = lineno + 1
if state == 'header' then
if startswith(line, "--- ") then
state = 'filenames'
end
-- state is 'header' or 'filenames'
end
if state == 'hunkbody' then
-- skip hunkskip and hunkbody code until definition of hunkhead read
if line:match"^[\r\n]*$" then
-- prepend space to empty lines to interpret them as context properly
line = " " .. line
end
-- process line first
if line:match"^[- +\\]" then
-- gather stats about line endings
local he = files.hunkends[nextfileno]
if endswith(line, "\r\n") then
he.crlf = he.crlf + 1
elseif endswith(line, "\n") then
he.lf = he.lf + 1
elseif endswith(line, "\r") then
he.cr = he.cr + 1
end
if startswith(line, "-") then
hunkactual.linessrc = hunkactual.linessrc + 1
elseif startswith(line, "+") then
hunkactual.linestgt = hunkactual.linestgt + 1
elseif startswith(line, "\\") then
-- nothing
else
hunkactual.linessrc = hunkactual.linessrc + 1
hunkactual.linestgt = hunkactual.linestgt + 1
end
table.insert(hunkinfo.text, line)
-- todo: handle \ No newline cases
else
warning(format("invalid hunk no.%d at %d for target file %s",
nexthunkno, lineno, files.target[nextfileno]))
-- add hunk status node
table.insert(files.hunks[nextfileno], table_copy(hunkinfo))
files.hunks[nextfileno][nexthunkno].invalid = true
all_ok = false
state = 'hunkskip'
end
-- check exit conditions
if hunkactual.linessrc > hunkinfo.linessrc or
hunkactual.linestgt > hunkinfo.linestgt
then
warning(format("extra hunk no.%d lines at %d for target %s",
nexthunkno, lineno, files.target[nextfileno]))
-- add hunk status node
table.insert(files.hunks[nextfileno], table_copy(hunkinfo))
files.hunks[nextfileno][nexthunkno].invalid = true
state = 'hunkskip'
elseif hunkinfo.linessrc == hunkactual.linessrc and
hunkinfo.linestgt == hunkactual.linestgt
then
table.insert(files.hunks[nextfileno], table_copy(hunkinfo))
state = 'hunkskip'
-- detect mixed window/unix line ends
local ends = files.hunkends[nextfileno]
if (ends.cr~=0 and 1 or 0) + (ends.crlf~=0 and 1 or 0) +
(ends.lf~=0 and 1 or 0) > 1
then
warning(format("inconsistent line ends in patch hunks for %s",
files.source[nextfileno]))
end
if debugmode then
local debuglines = {crlf=ends.crlf, lf=ends.lf, cr=ends.cr,
file=files.target[nextfileno], hunk=nexthunkno}
debug(format("crlf: %(crlf)d lf: %(lf)d cr: %(cr)d\t " ..
"- file: %(file)s hunk: %(hunk)d", debuglines))
end
end
-- state is 'hunkbody' or 'hunkskip'
end
if state == 'hunkskip' then
if match_linerange(line) then
state = 'hunkhead'
elseif startswith(line, "--- ") then
state = 'filenames'
if debugmode and #files.source > 0 then
debug(format("- %2d hunks for %s", #files.hunks[nextfileno],
files.source[nextfileno]))
end
end
-- state is 'hunkskip', 'hunkhead', or 'filenames'
end
local advance
if state == 'filenames' then
if startswith(line, "--- ") then
if util.array_contains(files.source, nextfileno) then
all_ok = false
warning(format("skipping invalid patch for %s",
files.source[nextfileno+1]))
table.remove(files.source, nextfileno+1)
-- double source filename line is encountered
-- attempt to restart from this second line
end
-- Accept a space as a terminator, like GNU patch does.
-- Breaks patches containing filenames with spaces...
-- FIXME Figure out what does GNU patch do in those cases.
local match = line:match("^%-%-%- ([^ \t\r\n]+)")
if not match then
all_ok = false
warning(format("skipping invalid filename at line %d", lineno+1))
state = 'header'
else
table.insert(files.source, match)
end
elseif not startswith(line, "+++ ") then
if util.array_contains(files.source, nextfileno) then
all_ok = false
warning(format("skipping invalid patch with no target for %s",
files.source[nextfileno+1]))
table.remove(files.source, nextfileno+1)
else
-- this should be unreachable
warning("skipping invalid target patch")
end
state = 'header'
else
if util.array_contains(files.target, nextfileno) then
all_ok = false
warning(format("skipping invalid patch - double target at line %d",
lineno+1))
table.remove(files.source, nextfileno+1)
table.remove(files.target, nextfileno+1)
nextfileno = nextfileno - 1
-- double target filename line is encountered
-- switch back to header state
state = 'header'
else
-- Accept a space as a terminator, like GNU patch does.
-- Breaks patches containing filenames with spaces...
-- FIXME Figure out what does GNU patch do in those cases.
local re_filename = "^%+%+%+ ([^ \t\r\n]+)"
local match = line:match(re_filename)
if not match then
all_ok = false
warning(format(
"skipping invalid patch - no target filename at line %d",
lineno+1))
state = 'header'
else
table.insert(files.target, match)
nextfileno = nextfileno + 1
nexthunkno = 0
table.insert(files.hunks, {})
table.insert(files.hunkends, table_copy(lineends))
table.insert(files.fileends, table_copy(lineends))
state = 'hunkhead'
advance = true
end
end
end
-- state is 'filenames', 'header', or ('hunkhead' with advance)
end
if not advance and state == 'hunkhead' then
local m1, m2, m3, m4 = match_linerange(line)
if not m1 then
if not util.array_contains(files.hunks, nextfileno-1) then
all_ok = false
warning(format("skipping invalid patch with no hunks for file %s",
files.target[nextfileno]))
end
state = 'header'
else
hunkinfo.startsrc = tonumber(m1)
hunkinfo.linessrc = tonumber(m2 or 1)
hunkinfo.starttgt = tonumber(m3)
hunkinfo.linestgt = tonumber(m4 or 1)
hunkinfo.invalid = false
hunkinfo.text = {}
hunkactual.linessrc = 0
hunkactual.linestgt = 0
state = 'hunkbody'
nexthunkno = nexthunkno + 1
end
-- state is 'header' or 'hunkbody'
end
end
if state ~= 'hunkskip' then
warning(format("patch file incomplete - %s", filename))
all_ok = false
-- os.exit(?)
else
-- duplicated message when an eof is reached
if debugmode and #files.source > 0 then
debug(format("- %2d hunks for %s", #files.hunks[nextfileno],
files.source[nextfileno]))
end
end
local sum = 0; for _,hset in ipairs(files.hunks) do sum = sum + #hset end
info(format("total files: %d total hunks: %d", #files.source, sum))
fp:close()
return files, all_ok
end
local function find_hunk(file, h, hno)
for fuzz=0,2 do
local lineno = h.startsrc
for i=0,#file do
local found = true
local location = lineno
for l, hline in ipairs(h.text) do
if l > fuzz then
-- todo: \ No newline at the end of file
if startswith(hline, " ") or startswith(hline, "-") then
local line = file[lineno]
lineno = lineno + 1
if not line or #line == 0 then
found = false
break
end
if endlstrip(line) ~= endlstrip(hline:sub(2)) then
found = false
break
end
end
end
end
if found then
local offset = location - h.startsrc - fuzz
if offset ~= 0 then
warning(format("Hunk %d found at offset %d%s...", hno, offset, fuzz == 0 and "" or format(" (fuzz %d)", fuzz)))
end
h.startsrc = location
h.starttgt = h.starttgt + offset
for _=1,fuzz do
table.remove(h.text, 1)
table.remove(h.text, #h.text)
end
return true
end
lineno = i
end
end
return false
end
local function load_file(filename)
local fp = assert(io.open(filename))
local file = {}
local readline = file_lines(fp)
while true do
local line = readline()
if not line then break end
table.insert(file, line)
end
fp:close()
return file
end
local function find_hunks(file, hunks)
for hno, h in ipairs(hunks) do
find_hunk(file, h, hno)
end
end
local function check_patched(file, hunks)
local lineno = 1
local ok, err = pcall(function()
if #file == 0 then
error('nomatch', 0)
end
for hno, h in ipairs(hunks) do
-- skip to line just before hunk starts
if #file < h.starttgt then
error('nomatch', 0)
end
lineno = h.starttgt
for _, hline in ipairs(h.text) do
-- todo: \ No newline at the end of file
if not startswith(hline, "-") and not startswith(hline, "\\") then
local line = file[lineno]
lineno = lineno + 1
if #line == 0 then
error('nomatch', 0)
end
if endlstrip(line) ~= endlstrip(hline:sub(2)) then
warning(format("file is not patched - failed hunk: %d", hno))
error('nomatch', 0)
end
end
end
end
end)
-- todo: display failed hunk, i.e. expected/found
return err ~= 'nomatch'
end
local function patch_hunks(srcname, tgtname, hunks)
local src = assert(io.open(srcname, "rb"))
local tgt = assert(io.open(tgtname, "wb"))
local src_readline = file_lines(src)
-- todo: detect linefeeds early - in apply_files routine
-- to handle cases when patch starts right from the first
-- line and no lines are processed. At the moment substituted
-- lineends may not be the same at the start and at the end
-- of patching. Also issue a warning about mixed lineends
local srclineno = 1
local lineends = {['\n']=0, ['\r\n']=0, ['\r']=0}
for hno, h in ipairs(hunks) do
debug(format("processing hunk %d for file %s", hno, tgtname))
-- skip to line just before hunk starts
while srclineno < h.startsrc do
local line = src_readline()
-- Python 'U' mode works only with text files
if endswith(line, "\r\n") then
lineends["\r\n"] = lineends["\r\n"] + 1
elseif endswith(line, "\n") then
lineends["\n"] = lineends["\n"] + 1
elseif endswith(line, "\r") then
lineends["\r"] = lineends["\r"] + 1
end
tgt:write(line)
srclineno = srclineno + 1
end
for _,hline in ipairs(h.text) do
-- todo: check \ No newline at the end of file
if startswith(hline, "-") or startswith(hline, "\\") then
src_readline()
srclineno = srclineno + 1
else
if not startswith(hline, "+") then
src_readline()
srclineno = srclineno + 1
end
local line2write = hline:sub(2)
-- detect if line ends are consistent in source file
local sum = 0
for _,v in pairs(lineends) do if v > 0 then sum=sum+1 end end
if sum == 1 then
local newline
for k,v in pairs(lineends) do if v ~= 0 then newline = k end end
tgt:write(endlstrip(line2write) .. newline)
else -- newlines are mixed or unknown
tgt:write(line2write)
end
end
end
end
for line in src_readline do
tgt:write(line)
end
tgt:close()
src:close()
return true
end
local function strip_dirs(filename, strip)
if strip == nil then return filename end
for _=1,strip do
filename=filename:gsub("^[^/]*/", "")
end
return filename
end
function patch.apply_patch(the_patch, strip)
local all_ok = true
local total = #the_patch.source
for fileno, filename in ipairs(the_patch.source) do
filename = strip_dirs(filename, strip)
local continue
local f2patch = filename
if not exists(f2patch) then
f2patch = strip_dirs(the_patch.target[fileno], strip)
f2patch = fs.absolute_name(f2patch)
if not exists(f2patch) then --FIX:if f2patch nil
warning(format("source/target file does not exist\n--- %s\n+++ %s",
filename, f2patch))
all_ok = false
continue = true
end
end
if not continue and not isfile(f2patch) then
warning(format("not a file - %s", f2patch))
all_ok = false
continue = true
end
if not continue then
filename = f2patch
info(format("processing %d/%d:\t %s", fileno, total, filename))
-- validate before patching
local hunks = the_patch.hunks[fileno]
local file = load_file(filename)
local hunkno = 1
local hunk = hunks[hunkno]
local hunkfind = {}
local validhunks = 0
local canpatch = false
local hunklineno
local isbreak
local lineno = 0
find_hunks(file, hunks)
for _, line in ipairs(file) do
lineno = lineno + 1
local continue
if not hunk or lineno < hunk.startsrc then
continue = true
elseif lineno == hunk.startsrc then
hunkfind = {}
for _,x in ipairs(hunk.text) do
if x:sub(1,1) == ' ' or x:sub(1,1) == '-' then
hunkfind[#hunkfind+1] = endlstrip(x:sub(2))
end
end
hunklineno = 1
-- todo \ No newline at end of file
end
-- check hunks in source file
if not continue and lineno < hunk.startsrc + #hunkfind - 1 then
if endlstrip(line) == hunkfind[hunklineno] then
hunklineno = hunklineno + 1
else
debug(format("hunk no.%d doesn't match source file %s",
hunkno, filename))
-- file may be already patched, but check other hunks anyway
hunkno = hunkno + 1
if hunkno <= #hunks then
hunk = hunks[hunkno]
continue = true
else
isbreak = true; break
end
end
end
-- check if processed line is the last line
if not continue and lineno == hunk.startsrc + #hunkfind - 1 then
debug(format("file %s hunk no.%d -- is ready to be patched",
filename, hunkno))
hunkno = hunkno + 1
validhunks = validhunks + 1
if hunkno <= #hunks then
hunk = hunks[hunkno]
else
if validhunks == #hunks then
-- patch file
canpatch = true
isbreak = true; break
end
end
end
end
if not isbreak then
if hunkno <= #hunks then
warning(format("premature end of source file %s at hunk %d",
filename, hunkno))
all_ok = false
end
end
if validhunks < #hunks then
if check_patched(file, hunks) then
warning(format("already patched %s", filename))
else
warning(format("source file is different - %s", filename))
all_ok = false
end
end
if canpatch then
local backupname = filename .. ".orig"
if exists(backupname) then
warning(format("can't backup original file to %s - aborting",
backupname))
all_ok = false
else
assert(os.rename(filename, backupname))
if patch_hunks(backupname, filename, hunks) then
warning(format("successfully patched %s", filename))
assert(os.remove(backupname))
else
warning(format("error patching file %s", filename))
assert(file_copy(filename, filename .. ".invalid"))
warning(format("invalid version is saved to %s",
filename .. ".invalid"))
-- todo: proper rejects
assert(os.rename(backupname, filename))
all_ok = false
end
end
end
end -- if not continue
end -- for
-- todo: check for premature eof
return all_ok
end
return patch

View File

@ -0,0 +1,150 @@
--- A pure-Lua implementation of untar (unpacking .tar archives)
local tar = {}
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local util = require("luarocks.util")
local blocksize = 512
local function get_typeflag(flag)
if flag == "0" or flag == "\0" then return "file"
elseif flag == "1" then return "link"
elseif flag == "2" then return "symlink" -- "reserved" in POSIX, "symlink" in GNU
elseif flag == "3" then return "character"
elseif flag == "4" then return "block"
elseif flag == "5" then return "directory"
elseif flag == "6" then return "fifo"
elseif flag == "7" then return "contiguous" -- "reserved" in POSIX, "contiguous" in GNU
elseif flag == "x" then return "next file"
elseif flag == "g" then return "global extended header"
elseif flag == "L" then return "long name"
elseif flag == "K" then return "long link name"
end
return "unknown"
end
local function octal_to_number(octal)
local exp = 0
local number = 0
for i = #octal,1,-1 do
local digit = tonumber(octal:sub(i,i))
if digit then
number = number + (digit * 8^exp)
exp = exp + 1
end
end
return number
end
local function checksum_header(block)
local sum = 256
for i = 1,148 do
sum = sum + block:byte(i)
end
for i = 157,500 do
sum = sum + block:byte(i)
end
return sum
end
local function nullterm(s)
return s:match("^[^%z]*")
end
local function read_header_block(block)
local header = {}
header.name = nullterm(block:sub(1,100))
header.mode = nullterm(block:sub(101,108))
header.uid = octal_to_number(nullterm(block:sub(109,116)))
header.gid = octal_to_number(nullterm(block:sub(117,124)))
header.size = octal_to_number(nullterm(block:sub(125,136)))
header.mtime = octal_to_number(nullterm(block:sub(137,148)))
header.chksum = octal_to_number(nullterm(block:sub(149,156)))
header.typeflag = get_typeflag(block:sub(157,157))
header.linkname = nullterm(block:sub(158,257))
header.magic = block:sub(258,263)
header.version = block:sub(264,265)
header.uname = nullterm(block:sub(266,297))
header.gname = nullterm(block:sub(298,329))
header.devmajor = octal_to_number(nullterm(block:sub(330,337)))
header.devminor = octal_to_number(nullterm(block:sub(338,345)))
header.prefix = block:sub(346,500)
if header.magic ~= "ustar " and header.magic ~= "ustar\0" then
return false, "Invalid header magic "..header.magic
end
if header.version ~= "00" and header.version ~= " \0" then
return false, "Unknown version "..header.version
end
if not checksum_header(block) == header.chksum then
return false, "Failed header checksum"
end
return header
end
function tar.untar(filename, destdir)
assert(type(filename) == "string")
assert(type(destdir) == "string")
local tar_handle = io.open(filename, "r")
if not tar_handle then return nil, "Error opening file "..filename end
local long_name, long_link_name
while true do
local block
repeat
block = tar_handle:read(blocksize)
until (not block) or checksum_header(block) > 256
if not block then break end
local header, err = read_header_block(block)
if not header then
util.printerr(err)
end
local file_data = tar_handle:read(math.ceil(header.size / blocksize) * blocksize):sub(1,header.size)
if header.typeflag == "long name" then
long_name = nullterm(file_data)
elseif header.typeflag == "long link name" then
long_link_name = nullterm(file_data)
else
if long_name then
header.name = long_name
long_name = nil
end
if long_link_name then
header.name = long_link_name
long_link_name = nil
end
end
local pathname = dir.path(destdir, header.name)
if header.typeflag == "directory" then
local ok, err = fs.make_dir(pathname)
if not ok then return nil, err end
elseif header.typeflag == "file" then
local dirname = dir.dir_name(pathname)
if dirname ~= "" then
local ok, err = fs.make_dir(dirname)
if not ok then return nil, err end
end
local file_handle = io.open(pathname, "wb")
file_handle:write(file_data)
file_handle:close()
fs.set_time(pathname, header.mtime)
if fs.chmod then
fs.chmod(pathname, header.mode)
end
end
--[[
for k,v in pairs(header) do
util.printout("[\""..tostring(k).."\"] = "..(type(v)=="number" and v or "\""..v:gsub("%z", "\\0").."\""))
end
util.printout()
--]]
end
tar_handle:close()
return true
end
return tar

View File

@ -0,0 +1,264 @@
--- A Lua implementation of .zip file archiving (used for creating .rock files),
-- using only lzlib or lua-lzib.
local zip = {}
local zlib = require("zlib")
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
-- zlib module can be provided by both lzlib and lua-lzib packages.
-- Create a compatibility layer.
local zlib_compress, zlib_crc32
if zlib._VERSION:match "^lua%-zlib" then
function zlib_compress(data)
return (zlib.deflate()(data, "finish"))
end
function zlib_crc32(data)
return zlib.crc32()(data)
end
elseif zlib._VERSION:match "^lzlib" then
function zlib_compress(data)
return zlib.compress(data)
end
function zlib_crc32(data)
return zlib.crc32(zlib.crc32(), data)
end
else
error("unknown zlib library", 0)
end
local function number_to_bytestring(number, nbytes)
local out = {}
for _ = 1, nbytes do
local byte = number % 256
table.insert(out, string.char(byte))
number = (number - byte) / 256
end
return table.concat(out)
end
--- Begin a new file to be stored inside the zipfile.
-- @param self handle of the zipfile being written.
-- @param filename filenome of the file to be added to the zipfile.
-- @return true if succeeded, nil in case of failure.
local function zipwriter_open_new_file_in_zip(self, filename)
if self.in_open_file then
self:close_file_in_zip()
return nil
end
local lfh = {}
self.local_file_header = lfh
lfh.last_mod_file_time = 0 -- TODO
lfh.last_mod_file_date = 0 -- TODO
lfh.file_name_length = #filename
lfh.extra_field_length = 0
lfh.file_name = filename:gsub("\\", "/")
lfh.external_attr = 0 -- TODO properly store permissions
self.in_open_file = true
return true
end
--- Write data to the file currently being stored in the zipfile.
-- @param self handle of the zipfile being written.
-- @param data string containing full contents of the file.
-- @return true if succeeded, nil in case of failure.
local function zipwriter_write_file_in_zip(self, data)
if not self.in_open_file then
return nil
end
local lfh = self.local_file_header
local compressed = zlib_compress(data):sub(3, -5)
lfh.crc32 = zlib_crc32(data)
lfh.compressed_size = #compressed
lfh.uncompressed_size = #data
self.data = compressed
return true
end
--- Complete the writing of a file stored in the zipfile.
-- @param self handle of the zipfile being written.
-- @return true if succeeded, nil in case of failure.
local function zipwriter_close_file_in_zip(self)
local zh = self.ziphandle
if not self.in_open_file then
return nil
end
-- Local file header
local lfh = self.local_file_header
lfh.offset = zh:seek()
zh:write(number_to_bytestring(0x04034b50, 4)) -- signature
zh:write(number_to_bytestring(20, 2)) -- version needed to extract: 2.0
zh:write(number_to_bytestring(0, 2)) -- general purpose bit flag
zh:write(number_to_bytestring(8, 2)) -- compression method: deflate
zh:write(number_to_bytestring(lfh.last_mod_file_time, 2))
zh:write(number_to_bytestring(lfh.last_mod_file_date, 2))
zh:write(number_to_bytestring(lfh.crc32, 4))
zh:write(number_to_bytestring(lfh.compressed_size, 4))
zh:write(number_to_bytestring(lfh.uncompressed_size, 4))
zh:write(number_to_bytestring(lfh.file_name_length, 2))
zh:write(number_to_bytestring(lfh.extra_field_length, 2))
zh:write(lfh.file_name)
-- File data
zh:write(self.data)
-- Data descriptor
zh:write(number_to_bytestring(lfh.crc32, 4))
zh:write(number_to_bytestring(lfh.compressed_size, 4))
zh:write(number_to_bytestring(lfh.uncompressed_size, 4))
table.insert(self.files, lfh)
self.in_open_file = false
return true
end
-- @return boolean or (boolean, string): true on success,
-- false and an error message on failure.
local function zipwriter_add(self, file)
local fin
local ok, err = self:open_new_file_in_zip(file)
if not ok then
err = "error in opening "..file.." in zipfile"
else
fin = io.open(fs.absolute_name(file), "rb")
if not fin then
ok = false
err = "error opening "..file.." for reading"
end
end
if ok then
local data = fin:read("*a")
if not data then
err = "error reading "..file
ok = false
else
ok = self:write_file_in_zip(data)
if not ok then
err = "error in writing "..file.." in the zipfile"
end
end
end
if fin then
fin:close()
end
if ok then
ok = self:close_file_in_zip()
if not ok then
err = "error in writing "..file.." in the zipfile"
end
end
return ok == true, err
end
--- Complete the writing of the zipfile.
-- @param self handle of the zipfile being written.
-- @return true if succeeded, nil in case of failure.
local function zipwriter_close(self)
local zh = self.ziphandle
local central_directory_offset = zh:seek()
local size_of_central_directory = 0
-- Central directory structure
for _, lfh in ipairs(self.files) do
zh:write(number_to_bytestring(0x02014b50, 4)) -- signature
zh:write(number_to_bytestring(3, 2)) -- version made by: UNIX
zh:write(number_to_bytestring(20, 2)) -- version needed to extract: 2.0
zh:write(number_to_bytestring(0, 2)) -- general purpose bit flag
zh:write(number_to_bytestring(8, 2)) -- compression method: deflate
zh:write(number_to_bytestring(lfh.last_mod_file_time, 2))
zh:write(number_to_bytestring(lfh.last_mod_file_date, 2))
zh:write(number_to_bytestring(lfh.crc32, 4))
zh:write(number_to_bytestring(lfh.compressed_size, 4))
zh:write(number_to_bytestring(lfh.uncompressed_size, 4))
zh:write(number_to_bytestring(lfh.file_name_length, 2))
zh:write(number_to_bytestring(lfh.extra_field_length, 2))
zh:write(number_to_bytestring(0, 2)) -- file comment length
zh:write(number_to_bytestring(0, 2)) -- disk number start
zh:write(number_to_bytestring(0, 2)) -- internal file attributes
zh:write(number_to_bytestring(lfh.external_attr, 4)) -- external file attributes
zh:write(number_to_bytestring(lfh.offset, 4)) -- relative offset of local header
zh:write(lfh.file_name)
size_of_central_directory = size_of_central_directory + 46 + lfh.file_name_length
end
-- End of central directory record
zh:write(number_to_bytestring(0x06054b50, 4)) -- signature
zh:write(number_to_bytestring(0, 2)) -- number of this disk
zh:write(number_to_bytestring(0, 2)) -- number of disk with start of central directory
zh:write(number_to_bytestring(#self.files, 2)) -- total number of entries in the central dir on this disk
zh:write(number_to_bytestring(#self.files, 2)) -- total number of entries in the central dir
zh:write(number_to_bytestring(size_of_central_directory, 4))
zh:write(number_to_bytestring(central_directory_offset, 4))
zh:write(number_to_bytestring(0, 2)) -- zip file comment length
zh:close()
return true
end
--- Return a zip handle open for writing.
-- @param name filename of the zipfile to be created.
-- @return a zip handle, or nil in case of error.
function zip.new_zipwriter(name)
local zw = {}
zw.ziphandle = io.open(fs.absolute_name(name), "wb")
if not zw.ziphandle then
return nil
end
zw.files = {}
zw.in_open_file = false
zw.add = zipwriter_add
zw.close = zipwriter_close
zw.open_new_file_in_zip = zipwriter_open_new_file_in_zip
zw.write_file_in_zip = zipwriter_write_file_in_zip
zw.close_file_in_zip = zipwriter_close_file_in_zip
return zw
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 or (boolean, string): true on success,
-- false and an error message on failure.
function zip.zip(zipfile, ...)
local zw = zip.new_zipwriter(zipfile)
if not zw then
return nil, "error opening "..zipfile
end
local ok, err
for _, file in pairs({...}) do
if fs.is_dir(file) then
for _, entry in pairs(fs.find(file)) do
local fullname = dir.path(file, entry)
if fs.is_file(fullname) then
ok, err = zw:add(fullname)
if not ok then break end
end
end
else
ok, err = zw:add(file)
if not ok then break end
end
end
ok = zw:close()
if not ok then
return false, "error closing "..zipfile
end
return ok, err
end
return zip

View File

@ -0,0 +1,344 @@
--- Type-checking functions.
-- Functions and definitions for doing a basic lint check on files
-- loaded by LuaRocks.
local type_check = {}
package.loaded["luarocks.type_check"] = type_check
local cfg = require("luarocks.cfg")
local deps = require("luarocks.deps")
type_check.rockspec_format = "1.1"
local string_1 = { _type = "string" }
local number_1 = { _type = "number" }
local mandatory_string_1 = { _type = "string", _mandatory = true }
-- Syntax for type-checking tables:
--
-- A type-checking table describes typing data for a value.
-- Any key starting with an underscore has a special meaning:
-- _type (string) is the Lua type of the value. Default is "table".
-- _version (string) is the minimum rockspec_version that supports this value. Default is "1.0".
-- _mandatory (boolean) indicates if the value is a mandatory key in its container table. Default is false.
-- For "string" types only:
-- _pattern (string) is the string-matching pattern, valid for string types only. Default is ".*".
-- For "table" types only:
-- _any (table) is the type-checking table for unspecified keys, recursively checked.
-- _more (boolean) indicates that the table accepts unspecified keys and does not type-check them.
-- Any other string keys that don't start with an underscore represent known keys and are type-checking tables, recursively checked.
local rockspec_types = {
rockspec_format = string_1,
package = mandatory_string_1,
version = { _type = "string", _pattern = "[%w.]+-[%d]+", _mandatory = true },
description = {
summary = string_1,
detailed = string_1,
homepage = string_1,
license = string_1,
maintainer = string_1,
},
dependencies = {
platforms = {}, -- recursively defined below
_any = string_1,
},
supported_platforms = {
_any = string_1,
},
external_dependencies = {
platforms = {}, -- recursively defined below
_any = {
program = string_1,
header = string_1,
library = string_1,
}
},
source = {
_mandatory = true,
platforms = {}, -- recursively defined below
url = mandatory_string_1,
md5 = string_1,
file = string_1,
dir = string_1,
tag = string_1,
branch = string_1,
module = string_1,
cvs_tag = string_1,
cvs_module = string_1,
},
build = {
platforms = {}, -- recursively defined below
type = string_1,
install = {
lua = {
_more = true
},
lib = {
_more = true
},
conf = {
_more = true
},
bin = {
_more = true
}
},
copy_directories = {
_any = string_1,
},
_more = true,
_mandatory = true
},
hooks = {
platforms = {}, -- recursively defined below
post_install = string_1,
},
deploy = {
_version = "1.1",
wrap_bin_scripts = { _type = "boolean", _version = "1.1" },
}
}
type_check.rockspec_order = {"rockspec_format", "package", "version",
{ "source", { "url", "tag", "branch", "md5" } },
{ "description", {"summary", "detailed", "homepage", "license" } },
"supported_platforms", "dependencies", "external_dependencies",
{ "build", {"type", "modules", "copy_directories", "platforms"} },
"hooks"}
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.source.platforms._any = rockspec_types.source
rockspec_types.hooks.platforms._any = rockspec_types.hooks
local manifest_types = {
repository = {
_mandatory = true,
-- packages
_any = {
-- versions
_any = {
-- items
_any = {
arch = mandatory_string_1,
modules = { _any = string_1 },
commands = { _any = string_1 },
dependencies = { _any = string_1 },
-- TODO: to be extended with more metadata.
}
}
}
},
modules = {
_mandatory = true,
-- modules
_any = {
-- providers
_any = string_1
}
},
commands = {
_mandatory = true,
-- modules
_any = {
-- commands
_any = string_1
}
},
dependencies = {
-- each module
_any = {
-- each version
_any = {
-- each dependency
_any = {
name = string_1,
constraints = {
_any = {
no_upgrade = { _type = "boolean" },
op = string_1,
version = {
string = string_1,
_any = number_1,
}
}
}
}
}
}
}
}
local function check_version(version, typetbl, context)
local typetbl_version = typetbl._version or "1.0"
if deps.compare_versions(typetbl_version, version) then
if context == "" then
return nil, "Invalid rockspec_format version number in rockspec? Please fix rockspec accordingly."
else
return nil, context.." is not supported in rockspec format "..version.." (requires version "..typetbl_version.."), please fix the rockspec_format field accordingly."
end
end
return true
end
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 version string: The version of the item.
-- @param item any: The object being checked.
-- @param typetbl any: The type-checking table for the object.
-- @param context string: A string indicating the "context" where the
-- error occurred (the full table path), for error messages.
-- @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(version, item, typetbl, context)
assert(type(version) == "string")
local ok, err = check_version(version, typetbl, context)
if not ok then
return nil, err
end
local item_type = type(item) or "nil"
local expected_type = typetbl._type or "table"
if expected_type == "number" then
if not tonumber(item) then
return nil, "Type mismatch on field "..context..": expected a number"
end
elseif expected_type == "string" then
if item_type ~= "string" then
return nil, "Type mismatch on field "..context..": expected a string, got "..item_type
end
if typetbl._pattern then
if not item:match("^"..typetbl._pattern.."$") then
return nil, "Type mismatch on field "..context..": invalid value "..item.." does not match '"..typetbl._pattern.."'"
end
end
elseif expected_type == "table" then
if item_type ~= expected_type then
return nil, "Type mismatch on field "..context..": expected a table"
else
return type_check_table(version, item, typetbl, context)
end
elseif item_type ~= expected_type then
return nil, "Type mismatch on field "..context..": expected "..expected_type
end
return true
end
local function mkfield(context, field)
if context == "" then
return tostring(field)
elseif type(field) == "string" then
return context.."."..field
else
return context.."["..tostring(field).."]"
end
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 version string: The version of tbl.
-- @param tbl table: The table to be type checked.
-- @param typetbl table: The type-checking table, containing
-- values for recognized fields in the checked table.
-- @param context string: A string indicating the "context" where the
-- error occurred (such as the name of the table the item is a part of),
-- to be used by error messages.
-- @return boolean or (nil, string): true if type checking
-- succeeded, or nil and an error message if it failed.
type_check_table = function(version, tbl, typetbl, context)
assert(type(version) == "string")
assert(type(tbl) == "table")
assert(type(typetbl) == "table")
local ok, err = check_version(version, typetbl, context)
if not ok then
return nil, err
end
for k, v in pairs(tbl) do
local t = typetbl[k] or typetbl._any
if t then
local ok, err = type_check_item(version, v, t, mkfield(context, k))
if not ok then return nil, err end
elseif typetbl._more then
-- Accept unknown field
else
if not cfg.accept_unknown_fields then
return nil, "Unknown field "..k
end
end
end
for k, v in pairs(typetbl) do
if k:sub(1,1) ~= "_" and v._mandatory then
if not tbl[k] then
return nil, "Mandatory field "..mkfield(context, k).." is missing."
end
end
end
return true
end
local function check_undeclared_globals(globals, typetbl)
local undeclared = {}
for glob, _ in pairs(globals) do
if not (typetbl[glob] or typetbl["MUST_"..glob]) then
table.insert(undeclared, glob)
end
end
if #undeclared == 1 then
return nil, "Unknown variable: "..undeclared[1]
elseif #undeclared > 1 then
return nil, "Unknown variables: "..table.concat(undeclared, ", ")
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.type_check_rockspec(rockspec, globals)
assert(type(rockspec) == "table")
if not rockspec.rockspec_format then
rockspec.rockspec_format = "1.0"
end
local ok, err = check_undeclared_globals(globals, rockspec_types)
if not ok then return nil, err end
return type_check_table(rockspec.rockspec_format, 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.type_check_manifest(manifest, globals)
assert(type(manifest) == "table")
local ok, err = check_undeclared_globals(globals, manifest_types)
if not ok then return nil, err end
return type_check_table("1.0", manifest, manifest_types, "")
end
return type_check

View File

@ -0,0 +1,166 @@
--- Module implementing the LuaRocks "unpack" command.
-- Unpack the contents of a rock.
local unpack = {}
package.loaded["luarocks.unpack"] = unpack
local fetch = require("luarocks.fetch")
local fs = require("luarocks.fs")
local util = require("luarocks.util")
local build = require("luarocks.build")
local dir = require("luarocks.dir")
local cfg = require("luarocks.cfg")
util.add_run_function(unpack)
unpack.help_summary = "Unpack the contents of a rock."
unpack.help_arguments = "[--force] {<rock>|<name> [<version>]}"
unpack.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.
--force Unpack files even if the output directory already exists.
]]
--- 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, err = fetch.load_rockspec(rockspec_file)
if not rockspec then
return nil, "Failed loading rockspec "..rockspec_file..": "..err
end
local ok, err = fs.change_dir(dir_name)
if not ok then return nil, err end
local ok, sources_dir = fetch.fetch_sources(rockspec, true, ".")
if not ok then
return nil, sources_dir
end
ok, err = fs.change_dir(sources_dir)
if not ok then return nil, err end
ok, err = build.apply_patches(rockspec)
fs.pop_dir()
if not ok then return nil, err end
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.
-- @param kind string: the kind of rock file, as in the second-level
-- extension in the rock filename (eg. "src", "all", "linux-x86")
-- @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, errcode = fetch.fetch_and_unpack_rock(rock_file, dir_name)
if not ok then
return nil, "Failed unzipping rock "..rock_file, errcode
end
ok, err = fs.change_dir(dir_name)
if not ok then return nil, err end
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
ok, err = fs.change_dir(rockspec.source.dir)
if not ok then return nil, err end
ok, err = build.apply_patches(rockspec)
fs.pop_dir()
if not ok then return nil, err end
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, force)
assert(type(file) == "string")
local base_name = dir.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
local exists = fs.exists(dir_name)
if exists and not force then
return nil, "Directory "..dir_name.." already exists."
end
if not exists then
local ok, err = fs.make_dir(dir_name)
if not ok then return nil, err end
end
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, cfg.perm_read)
if not ok then
return nil, "Failed copying unpacked rockspec into unpacked source directory."
end
end
util.printout()
util.printout("Done. You may now enter directory ")
util.printout(dir.path(dir_name, rockspec.source.dir))
util.printout("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 unpack.command(flags, name, version)
assert(type(version) == "string" or not version)
if type(name) ~= "string" then
return nil, "Argument missing. "..util.see_help("unpack")
end
if name:match(".*%.rock") or name:match(".*%.rockspec") then
return run_unpacker(name, flags["force"])
else
local search = require("luarocks.search")
return search.act_on_src_or_rockspec(run_unpacker, name:lower(), version)
end
end
return unpack

View File

@ -0,0 +1,95 @@
local upload = {}
local util = require("luarocks.util")
local fetch = require("luarocks.fetch")
local pack = require("luarocks.pack")
local cfg = require("luarocks.cfg")
local Api = require("luarocks.upload.api")
util.add_run_function(upload)
upload.help_summary = "Upload a rockspec to the public rocks repository."
upload.help_arguments = "[--skip-pack] [--api-key=<key>] [--force] <rockspec>"
upload.help = [[
<rockspec> Pack a source rock file (.src.rock extension),
upload rockspec and source rock to server.
--skip-pack Do not pack and send source rock.
--api-key=<key> Give it an API key. It will be stored for subsequent uses.
--force Replace existing rockspec if the same revision of
a module already exists. This should be used only
in case of upload mistakes: when updating a rockspec,
increment the revision number instead.
]]
function upload.command(flags, fname)
if not fname then
return nil, "Missing rockspec. "..util.see_help("upload")
end
local api, err = Api.new(flags)
if not api then
return nil, err
end
if cfg.verbose then
api.debug = true
end
local rockspec, err, errcode = fetch.load_rockspec(fname)
if err then
return nil, err, errcode
end
util.printout("Sending " .. tostring(fname) .. " ...")
local res, err = api:method("check_rockspec", {
package = rockspec.package,
version = rockspec.version
})
if not res then return nil, err end
if not res.module then
util.printout("Will create new module (" .. tostring(rockspec.package) .. ")")
end
if res.version and not flags["force"] then
return nil, "Revision "..rockspec.version.." already exists on the server. "..util.see_help("upload")
end
local rock_fname
if not flags["skip-pack"] and not rockspec.version:match("^scm") then
util.printout("Packing " .. tostring(rockspec.package))
rock_fname, err = pack.pack_source_rock(fname)
if not rock_fname then
return nil, err
end
end
local multipart = require("luarocks.upload.multipart")
res, err = api:method("upload", nil, {
rockspec_file = multipart.new_file(fname)
})
if not res then return nil, err end
if res.is_new and #res.manifests == 0 then
util.printerr("Warning: module not added to root manifest due to name taken.")
end
local module_url = res.module_url
if rock_fname then
if (not res.version) or (not res.version.id) then
return nil, "Invalid response from server."
end
util.printout(("Sending " .. tostring(rock_fname) .. " ..."))
res, err = api:method("upload_rock/" .. ("%d"):format(res.version.id), nil, {
rock_file = multipart.new_file(rock_fname)
})
if not res then return nil, err end
end
util.printout()
util.printout("Done: " .. tostring(module_url))
util.printout()
return true
end
return upload

View File

@ -0,0 +1,284 @@
local api = {}
local cfg = require("luarocks.cfg")
local fs = require("luarocks.fs")
local util = require("luarocks.util")
local persist = require("luarocks.persist")
local multipart = require("luarocks.upload.multipart")
local Api = {}
local function upload_config_file()
local conf = cfg.which_config()
if not conf.user.file then
return nil
end
return (conf.user.file:gsub("/[^/]+$", "/upload_config.lua"))
end
function Api:load_config()
local upload_conf = upload_config_file()
if not upload_conf then return nil end
local cfg, err = persist.load_into_table(upload_conf)
return cfg
end
function Api:save_config()
-- Test configuration before saving it.
local res, err = self:raw_method("status")
if not res then
return nil, err
end
if res.errors then
util.printerr("Server says: " .. tostring(res.errors[1]))
return
end
local upload_conf = upload_config_file()
if not upload_conf then return nil end
persist.save_from_table(upload_conf, self.config)
fs.chmod(upload_conf, "0600")
end
function Api:check_version()
if not self._server_tool_version then
local tool_version = cfg.upload.tool_version
local res, err = self:request(tostring(self.config.server) .. "/api/tool_version", {
current = tool_version
})
if not res then
return nil, err
end
if not res.version then
return nil, "failed to fetch tool version"
end
self._server_tool_version = res.version
if res.force_update then
return nil, "Your upload client is too out of date to continue, please upgrade LuaRocks."
end
if res.version ~= tool_version then
util.printerr("Warning: Your LuaRocks is out of date, consider upgrading.")
end
end
return true
end
function Api:method(...)
local res, err = self:raw_method(...)
if not res then
return nil, err
end
if res.errors then
if res.errors[1] == "Invalid key" then
return nil, res.errors[1] .. " (use the --api-key flag to change)"
end
local msg = table.concat(res.errors, ", ")
return nil, "API Failed: " .. msg
end
return res
end
function Api:raw_method(path, ...)
self:check_version()
local url = tostring(self.config.server) .. "/api/" .. tostring(cfg.upload.api_version) .. "/" .. tostring(self.config.key) .. "/" .. tostring(path)
return self:request(url, ...)
end
local function encode_query_string(t, sep)
if sep == nil then
sep = "&"
end
local i = 0
local buf = { }
for k, v in pairs(t) do
if type(k) == "number" and type(v) == "table" then
k, v = v[1], v[2]
end
buf[i + 1] = multipart.url_escape(k)
buf[i + 2] = "="
buf[i + 3] = multipart.url_escape(v)
buf[i + 4] = sep
i = i + 4
end
buf[i] = nil
return table.concat(buf)
end
-- An ode to the multitude of JSON libraries out there...
local function require_json()
local list = { "cjson", "dkjson", "json" }
for _, lib in ipairs(list) do
local json_ok, json = pcall(require, lib)
if json_ok then
pcall(json.use_lpeg) -- optional feature in dkjson
return json_ok, json
end
end
local errmsg = "Failed loading "
for i, name in ipairs(list) do
if i == #list then
errmsg = errmsg .."and '"..name.."'. Use 'luarocks search <partial-name>' to search for a library and 'luarocks install <name>' to install one."
else
errmsg = errmsg .."'"..name.."', "
end
end
return nil, errmsg
end
local function redact_api_url(url)
url = tostring(url)
return (url:gsub(".*/api/[^/]+/[^/]+", "")) or ""
end
local ltn12_ok, ltn12 = pcall(require, "ltn12")
if not ltn12_ok then -- If not using LuaSocket and/or LuaSec...
function Api:request(url, params, post_params)
local vars = cfg.variables
local json_ok, json = require_json()
if not json_ok then return nil, "A JSON library is required for this command. "..json end
if cfg.downloader == "wget" then
local curl_ok, err = fs.is_tool_available(vars.CURL, "curl")
if not curl_ok then
return nil, err
end
end
if not self.config.key then
return nil, "Must have API key before performing any actions."
end
if params and next(params) then
url = url .. ("?" .. encode_query_string(params))
end
local method = "GET"
local out
local tmpfile = fs.tmpname()
if post_params then
method = "POST"
local curl_cmd = fs.Q(vars.CURL).." -f -k -L --silent --user-agent \""..cfg.user_agent.." via curl\" "
for k,v in pairs(post_params) do
local var = v
if type(v) == "table" then
var = "@"..v.fname
end
curl_cmd = curl_cmd .. "--form \""..k.."="..var.."\" "
end
if cfg.connection_timeout and cfg.connection_timeout > 0 then
curl_cmd = curl_cmd .. "--connect-timeout "..tonumber(cfg.connection_timeout).." "
end
local ok = fs.execute_string(curl_cmd..fs.Q(url).." -o "..fs.Q(tmpfile))
if not ok then
return nil, "API failure: " .. redact_api_url(url)
end
else
local ok, err = fs.download(url, tmpfile)
if not ok then
return nil, "API failure: " .. tostring(err) .. " - " .. redact_api_url(url)
end
end
local tmpfd = io.open(tmpfile)
if not tmpfd then
os.remove(tmpfile)
return nil, "API failure reading temporary file - " .. redact_api_url(url)
end
out = tmpfd:read("*a")
tmpfd:close()
os.remove(tmpfile)
if self.debug then
util.printout("[" .. tostring(method) .. " via curl] " .. redact_api_url(url) .. " ... ")
end
return json.decode(out)
end
else -- use LuaSocket and LuaSec
local warned_luasec = false
function Api:request(url, params, post_params)
local json_ok, json = require_json()
if not json_ok then return nil, "A JSON library is required for this command. "..json end
local server = tostring(self.config.server)
local http_ok, http
local via = "luasocket"
if server:match("^https://") then
http_ok, http = pcall(require, "ssl.https")
if http_ok then
via = "luasec"
else
if not warned_luasec then
util.printerr("LuaSec is not available; using plain HTTP. Install 'luasec' to enable HTTPS.")
warned_luasec = true
end
http_ok, http = pcall(require, "socket.http")
url = url:gsub("^https", "http")
via = "luasocket"
end
else
http_ok, http = pcall(require, "socket.http")
end
if not http_ok then
return nil, "Failed loading socket library!"
end
if not self.config.key then
return nil, "Must have API key before performing any actions."
end
local body
local headers = {}
if params and next(params) then
url = url .. ("?" .. encode_query_string(params))
end
if post_params then
local boundary
body, boundary = multipart.encode(post_params)
headers["Content-length"] = #body
headers["Content-type"] = "multipart/form-data; boundary=" .. tostring(boundary)
end
local method = post_params and "POST" or "GET"
if self.debug then
util.printout("[" .. tostring(method) .. " via "..via.."] " .. redact_api_url(url) .. " ... ")
end
local out = {}
local _, status = http.request({
url = url,
headers = headers,
method = method,
sink = ltn12.sink.table(out),
source = body and ltn12.source.string(body)
})
if self.debug then
util.printout(tostring(status))
end
if status ~= 200 then
return nil, "API returned " .. tostring(status) .. " - " .. redact_api_url(url)
end
return json.decode(table.concat(out))
end
end
function api.new(flags)
local self = {}
setmetatable(self, { __index = Api })
self.config = self:load_config() or {}
self.config.server = flags["server"] or self.config.server or cfg.upload.server
self.config.version = self.config.version or cfg.upload.version
self.config.key = flags["api-key"] or self.config.key
self.debug = flags["debug"]
if not self.config.key then
return nil, "You need an API key to upload rocks.\n" ..
"Navigate to "..self.config.server.."/settings to get a key\n" ..
"and then pass it through the --api-key=<key> flag."
end
if flags["api-key"] then
self:save_config()
end
return self
end
return api

View File

@ -0,0 +1,111 @@
local multipart = {}
local File = {}
local unpack = unpack or table.unpack
math.randomseed(os.time())
-- socket.url.escape(s) from LuaSocket 3.0rc1
function multipart.url_escape(s)
return (string.gsub(s, "([^A-Za-z0-9_])", function(c)
return string.format("%%%02x", string.byte(c))
end))
end
function File:mime()
if not self.mimetype then
local mimetypes_ok, mimetypes = pcall(require, "mimetypes")
if mimetypes_ok then
self.mimetype = mimetypes.guess(self.fname)
end
self.mimetype = self.mimetype or "application/octet-stream"
end
return self.mimetype
end
function File:content()
local fd = io.open(self.fname, "rb")
if not fd then
return nil, "Failed to open file: "..self.fname
end
local data = fd:read("*a")
fd:close()
return data
end
local function rand_string(len)
local shuffled = {}
for i = 1, len do
local r = math.random(97, 122)
if math.random() >= 0.5 then
r = r - 32
end
shuffled[i] = r
end
return string.char(unpack(shuffled))
end
-- multipart encodes params
-- returns encoded string,boundary
-- params is an a table of tuple tables:
-- params = {
-- {key1, value2},
-- {key2, value2},
-- key3: value3
-- }
function multipart.encode(params)
local tuples = { }
for i = 1, #params do
tuples[i] = params[i]
end
for k,v in pairs(params) do
if type(k) == "string" then
table.insert(tuples, {k, v})
end
end
local chunks = {}
for _, tuple in ipairs(tuples) do
local k,v = unpack(tuple)
k = multipart.url_escape(k)
local buffer = { 'Content-Disposition: form-data; name="' .. k .. '"' }
local content
if type(v) == "table" and v.__class == File then
buffer[1] = buffer[1] .. ('; filename="' .. v.fname:gsub(".*/", "") .. '"')
table.insert(buffer, "Content-type: " .. v:mime())
content = v:content()
else
content = v
end
table.insert(buffer, "")
table.insert(buffer, content)
table.insert(chunks, table.concat(buffer, "\r\n"))
end
local boundary
while not boundary do
boundary = "Boundary" .. rand_string(16)
for _, chunk in ipairs(chunks) do
if chunk:find(boundary) then
boundary = nil
break
end
end
end
local inner = "\r\n--" .. boundary .. "\r\n"
return table.concat({ "--", boundary, "\r\n",
table.concat(chunks, inner),
"\r\n", "--", boundary, "--", "\r\n" }), boundary
end
function multipart.new_file(fname, mime)
local self = {}
setmetatable(self, { __index = File })
self.__class = File
self.fname = fname
self.mimetype = mime
return self
end
return multipart

View File

@ -0,0 +1,714 @@
--- Assorted utilities for managing tables, plus a scheduler for rollback functions.
-- 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.
local util = {}
local unpack = unpack or table.unpack
local scheduled_functions = {}
local debug = require("debug")
--- 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 util.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 item table: The token representing the scheduled function that was
-- returned from the schedule_function call.
function util.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 util.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
--- Produce a Lua pattern that matches precisely the given string
-- (this is suitable to be concatenating to other patterns,
-- so it does not include beginning- and end-of-string markers (^$)
-- @param s string: The input string
-- @return string: The equivalent pattern
function util.matchquote(s)
return (s:gsub("[?%-+*%[%].%%()$^]","%%%1"))
end
--- List of supported arguments.
-- Arguments that take no parameters are marked with the boolean true.
-- Arguments that take a parameter are marked with a descriptive string.
-- Arguments that may take an empty string are described in quotes,
-- (as in the value for --detailed="<text>").
-- For all other string values, it means the parameter is mandatory.
local supported_flags = {
["all"] = true,
["api-key"] = "<key>",
["append"] = true,
["arch"] = "<arch>",
["bin"] = true,
["binary"] = true,
["branch"] = "<branch-name>",
["debug"] = true,
["deps"] = true,
["deps-mode"] = "<mode>",
["detailed"] = "\"<text>\"",
["force"] = true,
["force-fast"] = true,
["from"] = "<server>",
["help"] = true,
["home"] = true,
["homepage"] = "\"<url>\"",
["keep"] = true,
["lib"] = "<library>",
["license"] = "\"<text>\"",
["list"] = true,
["local"] = true,
["local-tree"] = true,
["lr-bin"] = true,
["lr-cpath"] = true,
["lr-path"] = true,
["lua-version"] = "<vers>",
["lua-ver"] = true,
["lua-incdir"] = true,
["lua-libdir"] = true,
["modules"] = true,
["mversion"] = true,
["no-refresh"] = true,
["nodeps"] = true,
["old-versions"] = true,
["only-deps"] = true,
["only-from"] = "<server>",
["only-server"] = "<server>",
["only-sources"] = "<url>",
["only-sources-from"] = "<url>",
["outdated"] = true,
["output"] = "<file>",
["pack-binary-rock"] = true,
["porcelain"] = true,
["quick"] = true,
["rock-dir"] = true,
["rock-tree"] = true,
["rock-trees"] = true,
["rockspec"] = true,
["rockspec-format"] = "<ver>",
["server"] = "<server>",
["skip-pack"] = true,
["source"] = true,
["summary"] = "\"<text>\"",
["system-config"] = true,
["tag"] = "<tag>",
["timeout"] = "<seconds>",
["to"] = "<path>",
["tree"] = "<path>",
["user-config"] = true,
["verbose"] = true,
["version"] = true,
}
--- 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 util.parse_flags(...)
local args = {...}
local flags = {}
local i = 1
local out = {}
local ignore_flags = false
while i <= #args do
local flag = args[i]:match("^%-%-(.*)")
if flag == "--" then
ignore_flags = true
end
if flag and not ignore_flags then
local var,val = flag:match("([a-z_%-]*)=(.*)")
if val then
local vartype = supported_flags[var]
if type(vartype) == "string" then
if val == "" and vartype:sub(1,1) ~= '"' then
return { ERROR = "Invalid argument: parameter to flag --"..var.."="..vartype.." cannot be empty." }
end
flags[var] = val
else
if vartype then
return { ERROR = "Invalid argument: flag --"..var.." does not take an parameter." }
else
return { ERROR = "Invalid argument: unknown flag --"..var.."." }
end
end
else
local var = flag
local vartype = supported_flags[var]
if type(vartype) == "string" then
i = i + 1
local val = args[i]
if not val then
return { ERROR = "Invalid argument: flag --"..var.."="..vartype.." expects a parameter." }
end
if val:match("^%-%-.*") then
return { ERROR = "Invalid argument: flag --"..var.."="..vartype.." expects a parameter (if you really want to pass "..val.." as an argument to --"..var..", use --"..var.."="..val..")." }
else
if val == "" and vartype:sub(1,1) ~= '"' then
return { ERROR = "Invalid argument: parameter to flag --"..var.."="..vartype.." cannot be empty." }
end
flags[var] = val
end
elseif vartype == true then
flags[var] = true
else
return { ERROR = "Invalid argument: unknown flag --"..var.."." }
end
end
else
table.insert(out, args[i])
end
i = i + 1
end
return flags, unpack(out)
end
-- Adds legacy 'run' function to a command module.
-- @param command table: command module with 'command' function,
-- the added 'run' function calls it after parseing command-line arguments.
function util.add_run_function(command)
command.run = function(...) return command.command(util.parse_flags(...)) end
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 util.deep_merge(dst, src)
for k, v in pairs(src) do
if type(v) == "table" then
if not dst[k] then
dst[k] = {}
end
if type(dst[k]) == "table" then
util.deep_merge(dst[k], v)
else
dst[k] = v
end
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 util.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
util.deep_merge(tbl, platform_tbl)
end
end
end
tbl.platforms = nil
end
local var_format_pattern = "%$%((%a[%a%d_]+)%)"
--- Create a new shallow copy of a table: a new table with
-- the same keys and values. Keys point to the same objects as
-- the original table (ie, does not copy recursively).
-- @param tbl table: the input table
-- @return table: a new table with the same contents.
function util.make_shallow_copy(tbl)
local copy = {}
for k,v in pairs(tbl) do
copy[k] = v
end
return copy
end
-- Check if a set of needed variables are referenced
-- somewhere in a list of definitions, warning the user
-- about any unused ones. Each key in needed_set should
-- appear as a $(XYZ) variable at least once as a
-- substring of some value of var_defs.
-- @param var_defs: a table with string keys and string
-- values, containing variable definitions.
-- @param needed_set: a set where keys are the names of
-- needed variables.
-- @param msg string: the warning message to display.
function util.warn_if_not_used(var_defs, needed_set, msg)
needed_set = util.make_shallow_copy(needed_set)
for _, val in pairs(var_defs) do
for used in val:gmatch(var_format_pattern) do
needed_set[used] = nil
end
end
for var, _ in pairs(needed_set) do
util.warning(msg:format(var))
end
end
-- Output any entries that might remain in $(XYZ) format,
-- warning the user that substitutions have failed.
-- @param line string: the input string
local function warn_failed_matches(line)
local any_failed = false
if line:match(var_format_pattern) then
for unmatched in line:gmatch(var_format_pattern) do
util.warning("unmatched variable " .. unmatched)
any_failed = true
end
end
return any_failed
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 util.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(var_format_pattern, vars)
if warn_failed_matches(updated[k]) then
updated[k] = updated[k]:gsub(var_format_pattern, "")
end
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 util.keys(tbl)
local ks = {}
for k,_ in pairs(tbl) do
table.insert(ks, k)
end
return ks
end
local function default_sort(a, b)
local ta = type(a)
local tb = type(b)
if ta == "number" and tb == "number" then
return a < b
elseif ta == "number" then
return true
elseif tb == "number" then
return false
else
return tostring(a) < tostring(b)
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 table or nil: An optional comparison function
-- to be used by table.sort when sorting keys, or an array listing an explicit order
-- for keys. If a value itself is an array, it is taken so that the first element
-- is a string representing the field name, and the second element is a priority table
-- for that key, which is returned by the iterator as the third value after the key
-- and the value.
-- @return function: the iterator function.
function util.sortedpairs(tbl, sort_function)
sort_function = sort_function or default_sort
local keys = util.keys(tbl)
local sub_orders = {}
if type(sort_function) == "function" then
table.sort(keys, sort_function)
else
local order = sort_function
local ordered_keys = {}
local all_keys = keys
keys = {}
for _, order_entry in ipairs(order) do
local key, sub_order
if type(order_entry) == "table" then
key = order_entry[1]
sub_order = order_entry[2]
else
key = order_entry
end
if tbl[key] then
ordered_keys[key] = true
sub_orders[key] = sub_order
table.insert(keys, key)
end
end
table.sort(all_keys, default_sort)
for _, key in ipairs(all_keys) do
if not ordered_keys[key] then
table.insert(keys, key)
end
end
end
local i = 1
return function()
local key = keys[i]
i = i + 1
return key, tbl[key], sub_orders[key]
end
end
function util.lua_versions()
local versions = { "5.1", "5.2", "5.3" }
local i = 0
return function()
i = i + 1
return versions[i]
end
end
function util.starts_with(s, prefix)
return s:sub(1,#prefix) == prefix
end
--- Print a line to standard output
function util.printout(...)
io.stdout:write(table.concat({...},"\t"))
io.stdout:write("\n")
end
--- Print a line to standard error
function util.printerr(...)
io.stderr:write(table.concat({...},"\t"))
io.stderr:write("\n")
end
--- Display a warning message.
-- @param msg string: the warning message
function util.warning(msg)
util.printerr("Warning: "..msg)
end
function util.title(msg, porcelain, underline)
if porcelain then return end
util.printout()
util.printout(msg)
util.printout((underline or "-"):rep(#msg))
util.printout()
end
function util.this_program(default)
local i = 1
local last, cur = default, default
while i do
local dbg = debug.getinfo(i,"S")
if not dbg then break end
last = cur
cur = dbg.source
i=i+1
end
return last:sub(2)
end
function util.deps_mode_help(program)
local cfg = require("luarocks.cfg")
return [[
--deps-mode=<mode> How to handle dependencies. Four modes are supported:
* all - use all trees from the rocks_trees list
for finding dependencies
* one - use only the current tree (possibly set
with --tree)
* order - use trees based on order (use the current
tree and all trees below it on the rocks_trees list)
* none - ignore dependencies altogether.
The default mode may be set with the deps_mode entry
in the configuration file.
The current default is "]]..cfg.deps_mode..[[".
Type ']]..util.this_program(program or "luarocks")..[[' with no arguments to see
your list of rocks trees.
]]
end
function util.see_help(command, program)
return "See '"..util.this_program(program or "luarocks")..' help'..(command and " "..command or "").."'."
end
function util.announce_install(rockspec)
local cfg = require("luarocks.cfg")
local path = require("luarocks.path")
local suffix = ""
if rockspec.description and rockspec.description.license then
suffix = " (license: "..rockspec.description.license..")"
end
local root_dir = path.root_dir(cfg.rocks_dir)
util.printout(rockspec.name.." "..rockspec.version.." is now installed in "..root_dir..suffix)
util.printout()
end
--- Collect rockspecs located in a subdirectory.
-- @param versions table: A table mapping rock names to newest rockspec versions.
-- @param paths table: A table mapping rock names to newest rockspec paths.
-- @param unnamed_paths table: An array of rockspec paths that don't contain rock
-- name and version in regular format.
-- @param subdir string: path to subdirectory.
local function collect_rockspecs(versions, paths, unnamed_paths, subdir)
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local path = require("luarocks.path")
local deps = require("luarocks.deps")
if fs.is_dir(subdir) then
for file in fs.dir(subdir) do
file = dir.path(subdir, file)
if file:match("rockspec$") and fs.is_file(file) then
local rock, version = path.parse_name(file)
if rock then
if not versions[rock] or deps.compare_versions(version, versions[rock]) then
versions[rock] = version
paths[rock] = file
end
else
table.insert(unnamed_paths, file)
end
end
end
end
end
--- Get default rockspec name for commands that take optional rockspec name.
-- @return string or (nil, string): path to the rockspec or nil and error message.
function util.get_default_rockspec()
local versions, paths, unnamed_paths = {}, {}, {}
-- Look for rockspecs in some common locations.
collect_rockspecs(versions, paths, unnamed_paths, ".")
collect_rockspecs(versions, paths, unnamed_paths, "rockspec")
collect_rockspecs(versions, paths, unnamed_paths, "rockspecs")
if #unnamed_paths > 0 then
-- There are rockspecs not following "name-version.rockspec" format.
-- More than one are ambiguous.
if #unnamed_paths > 1 then
return nil, "Please specify which rockspec file to use."
else
return unnamed_paths[1]
end
else
local rock = next(versions)
if rock then
-- If there are rockspecs for multiple rocks it's ambiguous.
if next(versions, rock) then
return nil, "Please specify which rockspec file to use."
else
return paths[rock]
end
else
return nil, "Argument missing: please specify a rockspec to use on current directory."
end
end
end
-- from http://lua-users.org/wiki/SplitJoin
-- by PhilippeLhoste
function util.split_string(str, delim, maxNb)
-- Eliminate bad cases...
if string.find(str, delim) == nil then
return { str }
end
if maxNb == nil or maxNb < 1 then
maxNb = 0 -- No limit
end
local result = {}
local pat = "(.-)" .. delim .. "()"
local nb = 0
local lastPos
for part, pos in string.gmatch(str, pat) do
nb = nb + 1
result[nb] = part
lastPos = pos
if nb == maxNb then break end
end
-- Handle the last field
if nb ~= maxNb then
result[nb + 1] = string.sub(str, lastPos)
end
return result
end
--- Remove repeated entries from a path-style string.
-- Example: given ("a;b;c;a;b;d", ";"), returns "a;b;c;d".
-- @param list string: A path string (from $PATH or package.path)
-- @param sep string: The separator
function util.remove_path_dupes(list, sep)
assert(type(list) == "string")
assert(type(sep) == "string")
local parts = util.split_string(list, sep)
local final, entries = {}, {}
for _, part in ipairs(parts) do
part = part:gsub("//", "/")
if not entries[part] then
table.insert(final, part)
entries[part] = true
end
end
return table.concat(final, sep)
end
---
-- Formats tables with cycles recursively to any depth.
-- References to other tables are shown as values.
-- Self references are indicated.
-- The string returned is "Lua code", which can be procesed
-- (in the case in which indent is composed by spaces or "--").
-- Userdata and function keys and values are shown as strings,
-- which logically are exactly not equivalent to the original code.
-- This routine can serve for pretty formating tables with
-- proper indentations, apart from printing them:
-- io.write(table.show(t, "t")) -- a typical use
-- Written by Julio Manuel Fernandez-Diaz,
-- Heavily based on "Saving tables with cycles", PIL2, p. 113.
-- @param t table: is the table.
-- @param name string: is the name of the table (optional)
-- @param indent string: is a first indentation (optional).
-- @return string: the pretty-printed table
function util.show_table(t, name, indent)
local cart -- a container
local autoref -- for self references
local function isemptytable(t) return next(t) == nil end
local function basicSerialize (o)
local so = tostring(o)
if type(o) == "function" then
local info = debug.getinfo(o, "S")
-- info.name is nil because o is not a calling level
if info.what == "C" then
return ("%q"):format(so .. ", C function")
else
-- the information is defined through lines
return ("%q"):format(so .. ", defined in (" .. info.linedefined .. "-" .. info.lastlinedefined .. ")" .. info.source)
end
elseif type(o) == "number" then
return so
else
return ("%q"):format(so)
end
end
local function addtocart (value, name, indent, saved, field)
indent = indent or ""
saved = saved or {}
field = field or name
cart = cart .. indent .. field
if type(value) ~= "table" then
cart = cart .. " = " .. basicSerialize(value) .. ";\n"
else
if saved[value] then
cart = cart .. " = {}; -- " .. saved[value] .. " (self reference)\n"
autoref = autoref .. name .. " = " .. saved[value] .. ";\n"
else
saved[value] = name
--if tablecount(value) == 0 then
if isemptytable(value) then
cart = cart .. " = {};\n"
else
cart = cart .. " = {\n"
for k, v in pairs(value) do
k = basicSerialize(k)
local fname = ("%s[%s]"):format(name, k)
field = ("[%s]"):format(k)
-- three spaces between levels
addtocart(v, fname, indent .. " ", saved, field)
end
cart = cart .. indent .. "};\n"
end
end
end
end
name = name or "__unnamed__"
if type(t) ~= "table" then
return name .. " = " .. basicSerialize(t)
end
cart, autoref = "", ""
addtocart(t, name, indent)
return cart .. autoref
end
function util.array_contains(tbl, value)
for _, v in ipairs(tbl) do
if v == value then
return true
end
end
return false
end
-- Quote Lua string, analogous to fs.Q.
-- @param s A string, such as "hello"
-- @return string: A quoted string, such as '"hello"'
function util.LQ(s)
return ("%q"):format(s)
end
return util

View File

@ -0,0 +1,159 @@
--- Sandboxed test of build/install of all packages in a repository (unfinished and disabled).
local validate = {}
package.loaded["luarocks.validate"] = validate
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local path = require("luarocks.path")
local cfg = require("luarocks.cfg")
local build = require("luarocks.build")
local install = require("luarocks.install")
local util = require("luarocks.util")
util.add_run_function(validate)
validate.help_summary = "Sandboxed test of build/install of all packages in a repository."
validate.help = [[
<argument>, if given, is a local repository pathname.
]]
local function save_settings(repo)
local protocol, path = dir.split_url(repo)
table.insert(cfg.rocks_servers, 1, protocol.."://"..path)
return {
root_dir = cfg.root_dir,
rocks_dir = cfg.rocks_dir,
deploy_bin_dir = cfg.deploy_bin_dir,
deploy_lua_dir = cfg.deploy_lua_dir,
deploy_lib_dir = cfg.deploy_lib_dir,
}
end
local function restore_settings(settings)
cfg.root_dir = settings.root_dir
cfg.rocks_dir = settings.rocks_dir
cfg.deploy_bin_dir = settings.deploy_bin_dir
cfg.deploy_lua_dir = settings.deploy_lua_dir
cfg.deploy_lib_dir = settings.deploy_lib_dir
cfg.variables.ROCKS_TREE = settings.rocks_dir
cfg.variables.SCRIPTS_DIR = settings.deploy_bin_dir
table.remove(cfg.rocks_servers, 1)
end
local function prepare_sandbox(file)
local root_dir = fs.make_temp_dir(file):gsub("/+$", "")
cfg.root_dir = root_dir
cfg.rocks_dir = path.rocks_dir(root_dir)
cfg.deploy_bin_dir = path.deploy_bin_dir(root_dir)
cfg.variables.ROCKS_TREE = cfg.rocks_dir
cfg.variables.SCRIPTS_DIR = cfg.deploy_bin_dir
return root_dir
end
local function validate_rockspec(file)
local ok, err, errcode = build.build_rockspec(file, true, "one")
if not ok then
util.printerr(err)
end
return ok, err, errcode
end
local function validate_src_rock(file)
local ok, err, errcode = build.build_rock(file, false, "one")
if not ok then
util.printerr(err)
end
return ok, err, errcode
end
local function validate_rock(file)
local ok, err, errcode = install.install_binary_rock(file, "one")
if not ok then
util.printerr(err)
end
return ok, err, errcode
end
function validate.command(flags, repo)
repo = repo or cfg.rocks_dir
util.printout("Verifying contents of "..repo)
local results = {
ok = {}
}
local settings = save_settings(repo)
local sandbox
if flags["quick"] then
sandbox = prepare_sandbox("luarocks_validate")
end
if not fs.exists(repo) then
return nil, repo.." is not a local repository."
end
for file in fs.dir(repo) do for _=1,1 do
if file == "manifest" or file == "index.html" then
break -- continue for
end
local pathname = fs.absolute_name(dir.path(repo, file))
if not flags["quick"] then
sandbox = prepare_sandbox(file)
end
local ok, err, errcode
util.printout()
util.printout("Verifying "..pathname)
if file:match("%.rockspec$") then
ok, err, errcode = validate_rockspec(pathname, "one")
elseif file:match("%.src%.rock$") then
ok, err, errcode = validate_src_rock(pathname)
elseif file:match("%.rock$") then
ok, err, errcode = validate_rock(pathname)
end
if ok then
table.insert(results.ok, {file=file} )
else
if not errcode then
errcode = "misc"
end
if not results[errcode] then
results[errcode] = {}
end
table.insert(results[errcode], {file=file, err=err} )
end
util.run_scheduled_functions()
if not flags["quick"] then
fs.delete(sandbox)
end
repeat until not fs.pop_dir()
end end
if flags["quick"] then
fs.delete(sandbox)
end
restore_settings(settings)
util.title("Results:")
util.printout("OK: "..tostring(#results.ok))
for _, entry in ipairs(results.ok) do
util.printout(entry.file)
end
for errcode, errors in pairs(results) do
if errcode ~= "ok" then
util.printout()
util.printout(errcode.." errors: "..tostring(#errors))
for _, entry in ipairs(errors) do
util.printout(entry.file, entry.err)
end
end
end
util.title("Summary:")
local total = 0
for errcode, errors in pairs(results) do
util.printout(errcode..": "..tostring(#errors))
total = total + #errors
end
util.printout("Total: "..total)
return true
end
return validate

View File

@ -0,0 +1,375 @@
local write_rockspec = {}
package.loaded["luarocks.write_rockspec"] = write_rockspec
local cfg = require("luarocks.cfg")
local dir = require("luarocks.dir")
local fetch = require("luarocks.fetch")
local fs = require("luarocks.fs")
local path = require("luarocks.path")
local persist = require("luarocks.persist")
local type_check = require("luarocks.type_check")
local util = require("luarocks.util")
util.add_run_function(write_rockspec)
write_rockspec.help_summary = "Write a template for a rockspec file."
write_rockspec.help_arguments = "[--output=<file> ...] [<name>] [<version>] [<url>|<path>]"
write_rockspec.help = [[
This command writes an initial version of a rockspec file,
based on a name, a version, and a location (an URL or a local path).
If only two arguments are given, the first one is considered the name and the
second one is the location.
If only one argument is given, it must be the location.
If no arguments are given, current directory is used as location.
LuaRocks will attempt to infer name and version if not given,
using 'scm' as default version.
Note that the generated file is a _starting point_ for writing a
rockspec, and is not guaranteed to be complete or correct.
--output=<file> Write the rockspec with the given filename.
If not given, a file is written in the current
directory with a filename based on given name and version.
--license="<string>" A license string, such as "MIT/X11" or "GNU GPL v3".
--summary="<txt>" A short one-line description summary.
--detailed="<txt>" A longer description string.
--homepage=<url> Project homepage.
--lua-version=<ver> Supported Lua versions. Accepted values are "5.1", "5.2",
"5.3", "5.1,5.2", "5.2,5.3", or "5.1,5.2,5.3".
--rockspec-format=<ver> Rockspec format version, such as "1.0" or "1.1".
--tag=<tag> Tag to use. Will attempt to extract version number from it.
--lib=<lib>[,<lib>] A comma-separated list of libraries that C files need to
link to.
]]
local function open_file(name)
return io.open(dir.path(fs.current_dir(), name), "r")
end
local function get_url(rockspec)
local file, temp_dir, err_code, err_file, err_temp_dir = fetch.fetch_sources(rockspec, false)
if err_code == "source.dir" then
file, temp_dir = err_file, err_temp_dir
elseif not file then
util.warning("Could not fetch sources - "..temp_dir)
return false
end
util.printout("File successfully downloaded. Making checksum and checking base dir...")
if fetch.is_basic_protocol(rockspec.source.protocol) then
rockspec.source.md5 = fs.get_md5(file)
end
local inferred_dir, found_dir = fetch.find_base_dir(file, temp_dir, rockspec.source.url)
return true, found_dir or inferred_dir, temp_dir
end
local function configure_lua_version(rockspec, luaver)
if luaver == "5.1" then
table.insert(rockspec.dependencies, "lua ~> 5.1")
elseif luaver == "5.2" then
table.insert(rockspec.dependencies, "lua ~> 5.2")
elseif luaver == "5.3" then
table.insert(rockspec.dependencies, "lua ~> 5.3")
elseif luaver == "5.1,5.2" then
table.insert(rockspec.dependencies, "lua >= 5.1, < 5.3")
elseif luaver == "5.2,5.3" then
table.insert(rockspec.dependencies, "lua >= 5.2, < 5.4")
elseif luaver == "5.1,5.2,5.3" then
table.insert(rockspec.dependencies, "lua >= 5.1, < 5.4")
else
util.warning("Please specify supported Lua version with --lua-version=<ver>. "..util.see_help("write_rockspec"))
end
end
local function detect_description()
local fd = open_file("README.md") or open_file("README")
if not fd then return end
local data = fd:read("*a")
fd:close()
local paragraph = data:match("\n\n([^%[].-)\n\n")
if not paragraph then paragraph = data:match("\n\n(.*)") end
local summary, detailed
if paragraph then
detailed = paragraph
if #paragraph < 80 then
summary = paragraph:gsub("\n", "")
else
summary = paragraph:gsub("\n", " "):match("([^.]*%.) ")
end
end
return summary, detailed
end
local function detect_mit_license(data)
local strip_copyright = (data:gsub("Copyright [^\n]*\n", ""))
local sum = 0
for i = 1, #strip_copyright do
local num = string.byte(strip_copyright:sub(i,i))
if num > 32 and num <= 128 then
sum = sum + num
end
end
return sum == 78656
end
local simple_scm_protocols = {
git = true, ["git+http"] = true, ["git+https"] = true,
hg = true, ["hg+http"] = true, ["hg+https"] = true
}
local function detect_url_from_command(program, args, directory)
local command = fs.Q(cfg.variables[program:upper()]).. " "..args
local pipe = io.popen(fs.command_at(directory, fs.quiet_stderr(command)))
if not pipe then return nil end
local url = pipe:read("*a"):match("^([^\r\n]+)")
pipe:close()
if not url then return nil end
if not util.starts_with(url, program.."://") then
url = program.."+"..url
end
if simple_scm_protocols[dir.split_url(url)] then
return url
end
end
local function detect_scm_url(directory)
return detect_url_from_command("git", "config --get remote.origin.url", directory) or
detect_url_from_command("hg", "paths default", directory)
end
local function show_license(rockspec)
local fd = open_file("COPYING") or open_file("LICENSE") or open_file("MIT-LICENSE.txt")
if not fd then return nil end
local data = fd:read("*a")
fd:close()
local is_mit = detect_mit_license(data)
util.title("License for "..rockspec.package..":")
util.printout(data)
util.printout()
return is_mit
end
local function get_cmod_name(file)
local fd = open_file(file)
if not fd then return nil end
local data = fd:read("*a")
fd:close()
return (data:match("int%s+luaopen_([a-zA-Z0-9_]+)"))
end
local luamod_blacklist = {
test = true,
tests = true,
}
local function fill_as_builtin(rockspec, libs)
rockspec.build.type = "builtin"
rockspec.build.modules = {}
local prefix = ""
for _, parent in ipairs({"src", "lua"}) do
if fs.is_dir(parent) then
fs.change_dir(parent)
prefix = parent.."/"
break
end
end
local incdirs, libdirs
if libs then
incdirs, libdirs = {}, {}
for _, lib in ipairs(libs) do
local upper = lib:upper()
incdirs[#incdirs+1] = "$("..upper.."_INCDIR)"
libdirs[#libdirs+1] = "$("..upper.."_LIBDIR)"
end
end
for _, file in ipairs(fs.find()) do
local luamod = file:match("(.*)%.lua$")
if luamod and not luamod_blacklist[luamod] then
rockspec.build.modules[path.path_to_module(file)] = prefix..file
else
local cmod = file:match("(.*)%.c$")
if cmod then
local modname = get_cmod_name(file) or path.path_to_module(file:gsub("%.c$", ".lua"))
rockspec.build.modules[modname] = {
sources = prefix..file,
libraries = libs,
incdirs = incdirs,
libdirs = libdirs,
}
end
end
end
for _, directory in ipairs({ "doc", "docs", "samples", "tests" }) do
if fs.is_dir(directory) then
if not rockspec.build.copy_directories then
rockspec.build.copy_directories = {}
end
table.insert(rockspec.build.copy_directories, directory)
end
end
if prefix ~= "" then
fs.pop_dir()
end
end
local function rockspec_cleanup(rockspec)
rockspec.source.file = nil
rockspec.source.protocol = nil
rockspec.variables = nil
rockspec.name = nil
end
function write_rockspec.command(flags, name, version, url_or_dir)
if not name then
url_or_dir = "."
elseif not version then
url_or_dir = name
name = nil
elseif not url_or_dir then
url_or_dir = version
version = nil
end
if flags["tag"] then
if not version then
version = flags["tag"]:gsub("^v", "")
end
end
local protocol, pathname = dir.split_url(url_or_dir)
if protocol == "file" then
if pathname == "." then
name = name or dir.base_name(fs.current_dir())
end
elseif fetch.is_basic_protocol(protocol) then
local filename = dir.base_name(url_or_dir)
local newname, newversion = filename:match("(.*)-([^-]+)")
if newname then
name = name or newname
version = version or newversion:gsub("%.[a-z]+$", ""):gsub("%.tar$", "")
end
else
name = name or dir.base_name(url_or_dir):gsub("%.[^.]+$", "")
end
if not name then
return nil, "Could not infer rock name. "..util.see_help("write_rockspec")
end
version = version or "scm"
local filename = flags["output"] or dir.path(fs.current_dir(), name:lower().."-"..version.."-1.rockspec")
local rockspec = {
rockspec_format = flags["rockspec-format"],
package = name,
name = name:lower(),
version = version.."-1",
source = {
url = "*** please add URL for source tarball, zip or repository here ***",
tag = flags["tag"],
},
description = {
summary = flags["summary"] or "*** please specify description summary ***",
detailed = flags["detailed"] or "*** please enter a detailed description ***",
homepage = flags["homepage"] or "*** please enter a project homepage ***",
license = flags["license"] or "*** please specify a license ***",
},
dependencies = {},
build = {},
}
path.configure_paths(rockspec)
rockspec.source.protocol = protocol
configure_lua_version(rockspec, flags["lua-version"])
local local_dir = url_or_dir
if url_or_dir:match("://") then
rockspec.source.url = url_or_dir
rockspec.source.file = dir.base_name(url_or_dir)
rockspec.source.dir = "dummy"
if not fetch.is_basic_protocol(rockspec.source.protocol) then
if version ~= "scm" then
rockspec.source.tag = flags["tag"] or "v" .. version
end
end
rockspec.source.dir = nil
local ok, base_dir, temp_dir = get_url(rockspec)
if ok then
if base_dir ~= dir.base_name(url_or_dir) then
rockspec.source.dir = base_dir
end
end
if base_dir then
local_dir = dir.path(temp_dir, base_dir)
else
local_dir = nil
end
else
rockspec.source.url = detect_scm_url(local_dir) or rockspec.source.url
end
if not local_dir then
local_dir = "."
end
if not flags["homepage"] then
local url_protocol, url_path = dir.split_url(rockspec.source.url)
if simple_scm_protocols[url_protocol] then
for _, domain in ipairs({"github.com", "bitbucket.org", "gitlab.com"}) do
if util.starts_with(url_path, domain) then
rockspec.description.homepage = "https://"..url_path:gsub("%.git$", "")
break
end
end
end
end
local libs = nil
if flags["lib"] then
libs = {}
rockspec.external_dependencies = {}
for lib in flags["lib"]:gmatch("([^,]+)") do
table.insert(libs, lib)
rockspec.external_dependencies[lib:upper()] = {
library = lib
}
end
end
local ok, err = fs.change_dir(local_dir)
if not ok then return nil, "Failed reaching files from project - error entering directory "..local_dir end
if (not flags["summary"]) or (not flags["detailed"]) then
local summary, detailed = detect_description()
rockspec.description.summary = flags["summary"] or summary
rockspec.description.detailed = flags["detailed"] or detailed
end
local is_mit = show_license(rockspec)
if is_mit and not flags["license"] then
rockspec.description.license = "MIT"
end
fill_as_builtin(rockspec, libs)
rockspec_cleanup(rockspec)
persist.save_from_table(filename, rockspec, type_check.rockspec_order)
util.printout()
util.printout("Wrote template at "..filename.." -- you should now edit and finish it.")
util.printout()
return true
end
return write_rockspec

BIN
Utils/luarocks/lua5.1.dll Normal file

Binary file not shown.

BIN
Utils/luarocks/lua5.1.exe Normal file

Binary file not shown.

BIN
Utils/luarocks/lua5.1.lib Normal file

Binary file not shown.

BIN
Utils/luarocks/lua51.dll Normal file

Binary file not shown.

BIN
Utils/luarocks/lua51.lib Normal file

Binary file not shown.

BIN
Utils/luarocks/luac5.1.exe Normal file

Binary file not shown.

View File

@ -0,0 +1,43 @@
@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION ENABLEEXTENSIONS
SET "LUA_PATH=C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks\lua\?.lua;C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks\lua\?\init.lua;%LUA_PATH%"
IF NOT "%LUA_PATH_5_2%"=="" (
SET "LUA_PATH_5_2=C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks\lua\?.lua;C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks\lua\?\init.lua;%LUA_PATH_5_2%"
)
IF NOT "%LUA_PATH_5_3%"=="" (
SET "LUA_PATH_5_3=C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks\lua\?.lua;C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks\lua\?\init.lua;%LUA_PATH_5_3%"
)
SET "PATH=C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks;%PATH%"
"C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks\lua5.1" "C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks\luarocks-admin.lua" %*
SET EXITCODE=%ERRORLEVEL%
IF NOT "%EXITCODE%"=="2" GOTO EXITLR
REM Permission denied error, try and auto elevate...
REM already an admin? (checking to prevent loops)
NET SESSION >NUL 2>&1
IF "%ERRORLEVEL%"=="0" GOTO EXITLR
REM Do we have PowerShell available?
PowerShell /? >NUL 2>&1
IF NOT "%ERRORLEVEL%"=="0" GOTO EXITLR
:GETTEMPNAME
SET TMPFILE=%TEMP%\LuaRocks-Elevator-%RANDOM%.bat
IF EXIST "%TMPFILE%" GOTO :GETTEMPNAME
ECHO @ECHO OFF > "%TMPFILE%"
ECHO CHDIR /D %CD% >> "%TMPFILE%"
ECHO ECHO %0 %* >> "%TMPFILE%"
ECHO ECHO. >> "%TMPFILE%"
ECHO CALL %0 %* >> "%TMPFILE%"
ECHO ECHO. >> "%TMPFILE%"
ECHO ECHO Press any key to close this window... >> "%TMPFILE%"
ECHO PAUSE ^> NUL >> "%TMPFILE%"
ECHO DEL "%TMPFILE%" >> "%TMPFILE%"
ECHO Now retrying as a privileged user...
PowerShell -Command (New-Object -com 'Shell.Application').ShellExecute('%TMPFILE%', '', '', 'runas')
:EXITLR
exit /b %EXITCODE%

View File

@ -0,0 +1,19 @@
#!/usr/bin/env lua
-- this should be loaded first.
local cfg = require("luarocks.cfg")
local loader = require("luarocks.loader")
local command_line = require("luarocks.command_line")
program_description = "LuaRocks repository administration interface"
commands = {
help = "luarocks.help",
make_manifest = "luarocks.make_manifest",
add = "luarocks.add",
remove = "luarocks.admin_remove",
refresh_cache = "luarocks.refresh_cache",
}
command_line.run_command(...)

View File

@ -0,0 +1,43 @@
@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION ENABLEEXTENSIONS
SET "LUA_PATH=C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks\lua\?.lua;C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks\lua\?\init.lua;%LUA_PATH%"
IF NOT "%LUA_PATH_5_2%"=="" (
SET "LUA_PATH_5_2=C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks\lua\?.lua;C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks\lua\?\init.lua;%LUA_PATH_5_2%"
)
IF NOT "%LUA_PATH_5_3%"=="" (
SET "LUA_PATH_5_3=C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks\lua\?.lua;C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks\lua\?\init.lua;%LUA_PATH_5_3%"
)
SET "PATH=C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks;%PATH%"
"C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks\lua5.1" "C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks\luarocks.lua" %*
SET EXITCODE=%ERRORLEVEL%
IF NOT "%EXITCODE%"=="2" GOTO EXITLR
REM Permission denied error, try and auto elevate...
REM already an admin? (checking to prevent loops)
NET SESSION >NUL 2>&1
IF "%ERRORLEVEL%"=="0" GOTO EXITLR
REM Do we have PowerShell available?
PowerShell /? >NUL 2>&1
IF NOT "%ERRORLEVEL%"=="0" GOTO EXITLR
:GETTEMPNAME
SET TMPFILE=%TEMP%\LuaRocks-Elevator-%RANDOM%.bat
IF EXIST "%TMPFILE%" GOTO :GETTEMPNAME
ECHO @ECHO OFF > "%TMPFILE%"
ECHO CHDIR /D %CD% >> "%TMPFILE%"
ECHO ECHO %0 %* >> "%TMPFILE%"
ECHO ECHO. >> "%TMPFILE%"
ECHO CALL %0 %* >> "%TMPFILE%"
ECHO ECHO. >> "%TMPFILE%"
ECHO ECHO Press any key to close this window... >> "%TMPFILE%"
ECHO PAUSE ^> NUL >> "%TMPFILE%"
ECHO DEL "%TMPFILE%" >> "%TMPFILE%"
ECHO Now retrying as a privileged user...
PowerShell -Command (New-Object -com 'Shell.Application').ShellExecute('%TMPFILE%', '', '', 'runas')
:EXITLR
exit /b %EXITCODE%

Some files were not shown because too many files have changed in this diff Show More