aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorhisham <hisham@9ca3f7c1-7366-0410-b1a3-b5c78f85698c>2009-04-01 19:15:49 +0000
committerhisham <hisham@9ca3f7c1-7366-0410-b1a3-b5c78f85698c>2009-04-01 19:15:49 +0000
commit831a3c8a20780cb56a3e6544c62ba7eedf751ed2 (patch)
treea9a100743024d98e347939b1ce75b72f0a178454 /src
parentb25419643e3adab298363c32fe19e4532d1f9c0b (diff)
downloadluarocks-831a3c8a20780cb56a3e6544c62ba7eedf751ed2.tar.gz
luarocks-831a3c8a20780cb56a3e6544c62ba7eedf751ed2.tar.bz2
luarocks-831a3c8a20780cb56a3e6544c62ba7eedf751ed2.zip
LuaRocks 1.x development: reorganization, fixes and work on tool independence
git-svn-id: http://luarocks.org/svn/luarocks/trunk@4 9ca3f7c1-7366-0410-b1a3-b5c78f85698c
Diffstat (limited to 'src')
-rw-r--r--src/luarocks/build.lua75
-rw-r--r--src/luarocks/build/builtin.lua15
-rw-r--r--src/luarocks/command_line.lua2
-rw-r--r--src/luarocks/deps.lua21
-rw-r--r--src/luarocks/dir.lua47
-rw-r--r--src/luarocks/fetch.lua91
-rw-r--r--src/luarocks/fetch/cvs.lua17
-rw-r--r--src/luarocks/fetch/git.lua17
-rw-r--r--src/luarocks/fetch/sscm.lua3
-rw-r--r--src/luarocks/fs.lua7
-rw-r--r--src/luarocks/fs/lua.lua565
-rw-r--r--src/luarocks/fs/unix.lua107
-rw-r--r--src/luarocks/fs/win32.lua55
-rw-r--r--src/luarocks/install.lua24
-rw-r--r--src/luarocks/list.lua4
-rw-r--r--src/luarocks/make.lua2
-rw-r--r--src/luarocks/manif.lua93
-rw-r--r--src/luarocks/manif_core.lua73
-rw-r--r--src/luarocks/pack.lua13
-rw-r--r--src/luarocks/path.lua34
-rw-r--r--src/luarocks/rep.lua15
-rw-r--r--src/luarocks/search.lua7
-rw-r--r--src/luarocks/tools/patch.lua708
-rw-r--r--src/luarocks/tools/tar.lua143
-rw-r--r--src/luarocks/tools/zip.lua230
-rw-r--r--src/luarocks/type_check.lua2
-rw-r--r--src/luarocks/unpack.lua9
-rw-r--r--src/luarocks/validate.lua159
28 files changed, 2189 insertions, 349 deletions
diff --git a/src/luarocks/build.lua b/src/luarocks/build.lua
index f0f7225d..5d527876 100644
--- a/src/luarocks/build.lua
+++ b/src/luarocks/build.lua
@@ -8,6 +8,7 @@ local util = require("luarocks.util")
8local rep = require("luarocks.rep") 8local rep = require("luarocks.rep")
9local fetch = require("luarocks.fetch") 9local fetch = require("luarocks.fetch")
10local fs = require("luarocks.fs") 10local fs = require("luarocks.fs")
11local dir = require("luarocks.dir")
11local deps = require("luarocks.deps") 12local deps = require("luarocks.deps")
12local manif = require("luarocks.manif") 13local manif = require("luarocks.manif")
13 14
@@ -38,10 +39,10 @@ local function install_files(files, location)
38 for k, file in pairs(files) do 39 for k, file in pairs(files) do
39 local dest = location 40 local dest = location
40 if type(k) == "string" then 41 if type(k) == "string" then
41 dest = fs.make_path(location, path.module_to_path(k)) 42 dest = dir.path(location, path.module_to_path(k))
42 end 43 end
43 fs.make_dir(dest) 44 fs.make_dir(dest)
44 local ok = fs.copy(fs.make_path(file), dest) 45 local ok = fs.copy(dir.path(file), dest)
45 if not ok then 46 if not ok then
46 return nil, "Failed copying "..file 47 return nil, "Failed copying "..file
47 end 48 end
@@ -55,7 +56,7 @@ end
55-- @param files table: The table of files to be written. 56-- @param files table: The table of files to be written.
56local function extract_from_rockspec(files) 57local function extract_from_rockspec(files)
57 for name, content in pairs(files) do 58 for name, content in pairs(files) do
58 local fd = io.open(fs.make_path(fs.current_dir(), name), "w+") 59 local fd = io.open(dir.path(fs.current_dir(), name), "w+")
59 fd:write(content) 60 fd:write(content)
60 fd:close() 61 fd:close()
61 end 62 end
@@ -76,9 +77,9 @@ function apply_patches(rockspec)
76 end 77 end
77 if build.patches then 78 if build.patches then
78 extract_from_rockspec(build.patches) 79 extract_from_rockspec(build.patches)
79 for patch, _ in util.sortedpairs(build.patches) do 80 for patch, patchdata in util.sortedpairs(build.patches) do
80 print("Applying patch "..patch.."...") 81 print("Applying patch "..patch.."...")
81 local ok, err = fs.patch(tostring(patch)) 82 local ok, err = fs.apply_patch(tostring(patch, patchdata))
82 if not ok then 83 if not ok then
83 return nil, "Failed applying patch "..patch 84 return nil, "Failed applying patch "..patch
84 end 85 end
@@ -91,28 +92,28 @@ end
91-- @param rockspec_file string: local or remote filename of a rockspec. 92-- @param rockspec_file string: local or remote filename of a rockspec.
92-- @param need_to_fetch boolean: true if sources need to be fetched, 93-- @param need_to_fetch boolean: true if sources need to be fetched,
93-- false if the rockspec was obtained from inside a source rock. 94-- false if the rockspec was obtained from inside a source rock.
94-- @return boolean or (nil, string): True if succeeded or 95-- @return boolean or (nil, string, [string]): True if succeeded or
95-- nil and an error message. 96-- nil and an error message followed by an error code.
96function build_rockspec(rockspec_file, need_to_fetch, minimal_mode) 97function build_rockspec(rockspec_file, need_to_fetch, minimal_mode)
97 assert(type(rockspec_file) == "string") 98 assert(type(rockspec_file) == "string")
98 assert(type(need_to_fetch) == "boolean") 99 assert(type(need_to_fetch) == "boolean")
99 100
100 local rockspec, err = fetch.load_rockspec(rockspec_file) 101 local rockspec, err, errcode = fetch.load_rockspec(rockspec_file)
101 if err then 102 if err then
102 return nil, err 103 return nil, err, errcode
103 elseif not rockspec.build then 104 elseif not rockspec.build then
104 return nil, "Rockspec error: build table not specified" 105 return nil, "Rockspec error: build table not specified"
105 elseif not rockspec.build.type then 106 elseif not rockspec.build.type then
106 return nil, "Rockspec error: build type not specified" 107 return nil, "Rockspec error: build type not specified"
107 end 108 end
108 109
109 local ok, err = deps.fulfill_dependencies(rockspec) 110 local ok, err, errcode = deps.fulfill_dependencies(rockspec)
110 if err then 111 if err then
111 return nil, err 112 return nil, err, errcode
112 end 113 end
113 ok, err = deps.check_external_deps(rockspec, "build") 114 ok, err, errcode = deps.check_external_deps(rockspec, "build")
114 if err then 115 if err then
115 return nil, err 116 return nil, err, errcode
116 end 117 end
117 118
118 local name, version = rockspec.name, rockspec.version 119 local name, version = rockspec.name, rockspec.version
@@ -121,13 +122,13 @@ function build_rockspec(rockspec_file, need_to_fetch, minimal_mode)
121 end 122 end
122 123
123 if not minimal_mode then 124 if not minimal_mode then
124 local _, dir 125 local _, source_dir
125 if need_to_fetch then 126 if need_to_fetch then
126 ok, dir = fetch.fetch_sources(rockspec, true) 127 ok, source_dir, errcode = fetch.fetch_sources(rockspec, true)
127 if not ok then 128 if not ok then
128 return nil, dir 129 return nil, source_dir, errcode
129 end 130 end
130 fs.change_dir(dir) 131 fs.change_dir(source_dir)
131 elseif rockspec.source.file then 132 elseif rockspec.source.file then
132 local ok, err = fs.unpack_archive(rockspec.source.file) 133 local ok, err = fs.unpack_archive(rockspec.source.file)
133 if not ok then 134 if not ok then
@@ -144,8 +145,8 @@ function build_rockspec(rockspec_file, need_to_fetch, minimal_mode)
144 bin = path.bin_dir(name, version), 145 bin = path.bin_dir(name, version),
145 } 146 }
146 147
147 for _, dir in pairs(dirs) do 148 for _, d in pairs(dirs) do
148 fs.make_dir(dir) 149 fs.make_dir(d)
149 end 150 end
150 local rollback = util.schedule_function(function() 151 local rollback = util.schedule_function(function()
151 fs.delete(path.install_dir(name, version)) 152 fs.delete(path.install_dir(name, version))
@@ -178,8 +179,8 @@ function build_rockspec(rockspec_file, need_to_fetch, minimal_mode)
178 end 179 end
179 180
180 if build.install then 181 if build.install then
181 for id, dir in pairs(dirs) do 182 for id, install_dir in pairs(dirs) do
182 ok, err = install_files(build.install[id], dir) 183 ok, err = install_files(build.install[id], install_dir)
183 if not ok then 184 if not ok then
184 return nil, err 185 return nil, err
185 end 186 end
@@ -188,16 +189,16 @@ function build_rockspec(rockspec_file, need_to_fetch, minimal_mode)
188 189
189 local copy_directories = build.copy_directories or {"doc"} 190 local copy_directories = build.copy_directories or {"doc"}
190 191
191 for _, dir in pairs(copy_directories) do 192 for _, copy_dir in pairs(copy_directories) do
192 if fs.is_dir(dir) then 193 if fs.is_dir(copy_dir) then
193 local dest = fs.make_path(path.install_dir(name, version), dir) 194 local dest = dir.path(path.install_dir(name, version), copy_dir)
194 fs.make_dir(dest) 195 fs.make_dir(dest)
195 fs.copy_contents(dir, dest) 196 fs.copy_contents(copy_dir, dest)
196 end 197 end
197 end 198 end
198 199
199 for _, dir in pairs(dirs) do 200 for _, d in pairs(dirs) do
200 fs.remove_dir_if_empty(dir) 201 fs.remove_dir_if_empty(d)
201 end 202 end
202 203
203 fs.pop_dir() 204 fs.pop_dir()
@@ -227,21 +228,21 @@ end
227-- @param rock_file string: local or remote filename of a rock. 228-- @param rock_file string: local or remote filename of a rock.
228-- @param need_to_fetch boolean: true if sources need to be fetched, 229-- @param need_to_fetch boolean: true if sources need to be fetched,
229-- false if the rockspec was obtained from inside a source rock. 230-- false if the rockspec was obtained from inside a source rock.
230-- @return boolean or (nil, string): True if build was successful, 231-- @return boolean or (nil, string, [string]): True if build was successful,
231-- or false and an error message. 232-- or false and an error message and an optional error code.
232local function build_rock(rock_file, need_to_fetch) 233function build_rock(rock_file, need_to_fetch)
233 assert(type(rock_file) == "string") 234 assert(type(rock_file) == "string")
234 assert(type(need_to_fetch) == "boolean") 235 assert(type(need_to_fetch) == "boolean")
235 236
236 local dir, err = fetch.fetch_and_unpack_rock(rock_file) 237 local unpack_dir, err, errcode = fetch.fetch_and_unpack_rock(rock_file)
237 if not dir then 238 if not unpack_dir then
238 return nil, err 239 return nil, err, errcode
239 end 240 end
240 local rockspec_file = path.rockspec_name_from_rock(rock_file) 241 local rockspec_file = path.rockspec_name_from_rock(rock_file)
241 fs.change_dir(dir) 242 fs.change_dir(unpack_dir)
242 local ok, err = build_rockspec(rockspec_file, need_to_fetch) 243 local ok, err, errcode = build_rockspec(rockspec_file, need_to_fetch)
243 fs.pop_dir() 244 fs.pop_dir()
244 return ok, err 245 return ok, err, errcode
245end 246end
246 247
247--- Driver function for "build" command. 248--- Driver function for "build" command.
@@ -268,7 +269,7 @@ function run(...)
268 return install.install_binary_rock(name) 269 return install.install_binary_rock(name)
269 elseif name:match("%.rock$") then 270 elseif name:match("%.rock$") then
270 return build_rock(name, true) 271 return build_rock(name, true)
271 elseif not name:match(fs.dir_separator) then 272 elseif not name:match(dir.separator) then
272 local search = require("luarocks.search") 273 local search = require("luarocks.search")
273 return search.act_on_src_or_rockspec(run, name, version) 274 return search.act_on_src_or_rockspec(run, name, version)
274 end 275 end
diff --git a/src/luarocks/build/builtin.lua b/src/luarocks/build/builtin.lua
index c93edaa2..cfd71858 100644
--- a/src/luarocks/build/builtin.lua
+++ b/src/luarocks/build/builtin.lua
@@ -6,6 +6,7 @@ local fs = require("luarocks.fs")
6local path = require("luarocks.path") 6local path = require("luarocks.path")
7local util = require("luarocks.util") 7local util = require("luarocks.util")
8local cfg = require("luarocks.cfg") 8local cfg = require("luarocks.cfg")
9local dir = require("luarocks.dir")
9 10
10--- Check if platform was detected 11--- Check if platform was detected
11-- @param query string: The platform name to check. 12-- @param query string: The platform name to check.
@@ -62,13 +63,13 @@ function run(rockspec)
62 local extras = { unpack(objects) } 63 local extras = { unpack(objects) }
63 add_flags(extras, "-libpath:%s", libdirs) 64 add_flags(extras, "-libpath:%s", libdirs)
64 add_flags(extras, "%s.lib", libraries) 65 add_flags(extras, "%s.lib", libraries)
65 local basename = fs.base_name(library):gsub(".[^.]*$", "") 66 local basename = dir.base_name(library):gsub(".[^.]*$", "")
66 local deffile = basename .. ".def" 67 local deffile = basename .. ".def"
67 local def = io.open(fs.make_path(fs.current_dir(), deffile), "w+") 68 local def = io.open(dir.path(fs.current_dir(), deffile), "w+")
68 def:write("EXPORTS\n") 69 def:write("EXPORTS\n")
69 def:write("luaopen_"..name:gsub("%.", "_").."\n") 70 def:write("luaopen_"..name:gsub("%.", "_").."\n")
70 def:close() 71 def:close()
71 local ok = execute(variables.LD, "-dll", "-def:"..deffile, "-out:"..library, fs.make_path(variables.LUA_LIBDIR, "lua5.1.lib"), unpack(extras)) 72 local ok = execute(variables.LD, "-dll", "-def:"..deffile, "-out:"..library, dir.path(variables.LUA_LIBDIR, "lua5.1.lib"), unpack(extras))
72 local manifestfile = basename..".dll.manifest" 73 local manifestfile = basename..".dll.manifest"
73 if ok and fs.exists(manifestfile) then 74 if ok and fs.exists(manifestfile) then
74 ok = execute(variables.MT, "-manifest", manifestfile, "-outputresource:"..basename..".dll;2") 75 ok = execute(variables.MT, "-manifest", manifestfile, "-outputresource:"..basename..".dll;2")
@@ -103,7 +104,7 @@ function run(rockspec)
103 if type(info) == "string" then 104 if type(info) == "string" then
104 local ext = info:match(".([^.]+)$") 105 local ext = info:match(".([^.]+)$")
105 if ext == "lua" then 106 if ext == "lua" then
106 local dest = fs.make_path(luadir, moddir) 107 local dest = dir.path(luadir, moddir)
107 built_modules[info] = dest 108 built_modules[info] = dest
108 else 109 else
109 info = {info} 110 info = {info}
@@ -124,11 +125,11 @@ function run(rockspec)
124 table.insert(objects, object) 125 table.insert(objects, object)
125 end 126 end
126 if not ok then break end 127 if not ok then break end
127 local module_name = fs.make_path(moddir, name:match("([^.]*)$").."."..cfg.lib_extension):gsub("//", "/") 128 local module_name = dir.path(moddir, name:match("([^.]*)$").."."..cfg.lib_extension):gsub("//", "/")
128 if moddir ~= "" then 129 if moddir ~= "" then
129 fs.make_dir(moddir) 130 fs.make_dir(moddir)
130 end 131 end
131 local dest = fs.make_path(libdir, moddir) 132 local dest = dir.path(libdir, moddir)
132 built_modules[module_name] = dest 133 built_modules[module_name] = dest
133 ok = compile_library(module_name, objects, info.libraries, info.libdirs, name) 134 ok = compile_library(module_name, objects, info.libraries, info.libdirs, name)
134 if not ok then break end 135 if not ok then break end
@@ -141,7 +142,7 @@ function run(rockspec)
141 end 142 end
142 if ok then 143 if ok then
143 if fs.is_dir("lua") then 144 if fs.is_dir("lua") then
144 fs.copy_contents("lua", luadir) 145 ok = fs.copy_contents("lua", luadir)
145 end 146 end
146 end 147 end
147 if ok then 148 if ok then
diff --git a/src/luarocks/command_line.lua b/src/luarocks/command_line.lua
index 9389d6ef..e220bb34 100644
--- a/src/luarocks/command_line.lua
+++ b/src/luarocks/command_line.lua
@@ -1,5 +1,5 @@
1 1
2program_version = "1.0.1" 2program_version = "1.1"
3 3
4--- Functions for command-line scripts. 4--- Functions for command-line scripts.
5module("luarocks.command_line", package.seeall) 5module("luarocks.command_line", package.seeall)
diff --git a/src/luarocks/deps.lua b/src/luarocks/deps.lua
index d5a64e52..6f986575 100644
--- a/src/luarocks/deps.lua
+++ b/src/luarocks/deps.lua
@@ -17,10 +17,11 @@ local rep = require("luarocks.rep")
17local search = require("luarocks.search") 17local search = require("luarocks.search")
18local install = require("luarocks.install") 18local install = require("luarocks.install")
19local cfg = require("luarocks.cfg") 19local cfg = require("luarocks.cfg")
20local manif = require("luarocks.manif") 20local manif_core = require("luarocks.manif_core")
21local fs = require("luarocks.fs") 21local fs = require("luarocks.fs")
22local fetch = require("luarocks.fetch") 22local fetch = require("luarocks.fetch")
23local path = require("luarocks.path") 23local path = require("luarocks.path")
24local dir = require("luarocks.dir")
24 25
25local operators = { 26local operators = {
26 ["=="] = "==", 27 ["=="] = "==",
@@ -320,7 +321,7 @@ local function match_dep(dep, blacklist)
320 if dep.name == "lua" then 321 if dep.name == "lua" then
321 versions = { "5.1" } 322 versions = { "5.1" }
322 else 323 else
323 versions = manif.get_versions(dep.name) 324 versions = manif_core.get_versions(dep.name)
324 end 325 end
325 if not versions then 326 if not versions then
326 return nil 327 return nil
@@ -399,8 +400,9 @@ end
399-- Packages are installed using the LuaRocks "install" command. 400-- Packages are installed using the LuaRocks "install" command.
400-- Aborts the program if a dependency could not be fulfilled. 401-- Aborts the program if a dependency could not be fulfilled.
401-- @param rockspec table: A rockspec in table format. 402-- @param rockspec table: A rockspec in table format.
402-- @return boolean or (nil, string): True if no errors occurred, or 403-- @return boolean or (nil, string, [string]): True if no errors occurred, or
403-- nil and an error message if any test failed. 404-- nil and an error message if any test failed, followed by an optional
405-- error code.
404function fulfill_dependencies(rockspec) 406function fulfill_dependencies(rockspec)
405 407
406 if rockspec.supported_platforms then 408 if rockspec.supported_platforms then
@@ -469,9 +471,9 @@ function fulfill_dependencies(rockspec)
469 if not rock then 471 if not rock then
470 return nil, "Could not find a rock to satisfy dependency: "..show_dep(dep) 472 return nil, "Could not find a rock to satisfy dependency: "..show_dep(dep)
471 end 473 end
472 local ok, err = install.run(rock) 474 local ok, err, errcode = install.run(rock)
473 if not ok then 475 if not ok then
474 return nil, "Failed installing dependency: "..rock.." - "..err 476 return nil, "Failed installing dependency: "..rock.." - "..err, errcode
475 end 477 end
476 end 478 end
477 end 479 end
@@ -521,7 +523,7 @@ function check_external_deps(rockspec, mode)
521 prefix = extdir 523 prefix = extdir
522 end 524 end
523 for dirname, dirdata in pairs(dirs) do 525 for dirname, dirdata in pairs(dirs) do
524 dirdata.dir = vars[name.."_"..dirname] or fs.make_path(prefix, dirdata.subdir) 526 dirdata.dir = vars[name.."_"..dirname] or dir.path(prefix, dirdata.subdir)
525 local file = files[dirdata.testfile] 527 local file = files[dirdata.testfile]
526 if file then 528 if file then
527 local files = {} 529 local files = {}
@@ -535,10 +537,11 @@ function check_external_deps(rockspec, mode)
535 local found = false 537 local found = false
536 failed_file = nil 538 failed_file = nil
537 for _, f in pairs(files) do 539 for _, f in pairs(files) do
540 -- small convenience hack
538 if f:match("%.so$") or f:match("%.dylib$") or f:match("%.dll$") then 541 if f:match("%.so$") or f:match("%.dylib$") or f:match("%.dll$") then
539 f = f:gsub("%.[^.]+$", "."..cfg.external_lib_extension) 542 f = f:gsub("%.[^.]+$", "."..cfg.external_lib_extension)
540 end 543 end
541 local testfile = fs.make_path(dirdata.dir, f) 544 local testfile = dir.path(dirdata.dir, f)
542 if fs.exists(testfile) then 545 if fs.exists(testfile) then
543 found = true 546 found = true
544 break 547 break
@@ -565,7 +568,7 @@ function check_external_deps(rockspec, mode)
565 end 568 end
566 end 569 end
567 if not ok then 570 if not ok then
568 return nil, "Could not find expected file "..failed_file.." for "..name.." -- you may have to install "..name.." in your system and/or set the "..name.."_DIR variable" 571 return nil, "Could not find expected file "..failed_file.." for "..name.." -- you may have to install "..name.." in your system and/or set the "..name.."_DIR variable", "dependency"
569 end 572 end
570 end 573 end
571 end 574 end
diff --git a/src/luarocks/dir.lua b/src/luarocks/dir.lua
new file mode 100644
index 00000000..5cb3b846
--- /dev/null
+++ b/src/luarocks/dir.lua
@@ -0,0 +1,47 @@
1
2module("luarocks.dir", package.seeall)
3
4separator = "/"
5
6--- Strip the path off a path+filename.
7-- @param pathname string: A path+name, such as "/a/b/c"
8-- or "\a\b\c".
9-- @return string: The filename without its path, such as "c".
10function base_name(pathname)
11 assert(type(pathname) == "string")
12
13 local base = pathname:match(".*[/\\]([^/\\]*)")
14 return base or pathname
15end
16
17--- Strip the name off a path+filename.
18-- @param pathname string: A path+name, such as "/a/b/c".
19-- @return string: The filename without its path, such as "/a/b/".
20-- For entries such as "/a/b/", "/a/" is returned. If there are
21-- no directory separators in input, "" is returned.
22function dir_name(pathname)
23 assert(type(pathname) == "string")
24
25 return (pathname:gsub("/*$", ""):match("(.*/)[^/]*")) or ""
26end
27
28--- Describe a path in a cross-platform way.
29-- Use this function to avoid platform-specific directory
30-- separators in other modules. If the first item contains a
31-- protocol descriptor (e.g. "http:"), paths are always constituted
32-- with forward slashes.
33-- @param ... strings representing directories
34-- @return string: a string with a platform-specific representation
35-- of the path.
36function path(...)
37 local items = {...}
38 local i = 1
39 while items[i] do
40 if items[i] == "" then
41 table.remove(items, i)
42 else
43 i = i + 1
44 end
45 end
46 return table.concat(items, "/")
47end
diff --git a/src/luarocks/fetch.lua b/src/luarocks/fetch.lua
index b61fab28..5b2d20cb 100644
--- a/src/luarocks/fetch.lua
+++ b/src/luarocks/fetch.lua
@@ -3,6 +3,7 @@
3module("luarocks.fetch", package.seeall) 3module("luarocks.fetch", package.seeall)
4 4
5local fs = require("luarocks.fs") 5local fs = require("luarocks.fs")
6local dir = require("luarocks.dir")
6local type_check = require("luarocks.type_check") 7local type_check = require("luarocks.type_check")
7local path = require("luarocks.path") 8local path = require("luarocks.path")
8local deps = require("luarocks.deps") 9local deps = require("luarocks.deps")
@@ -18,8 +19,9 @@ local util = require("luarocks.util")
18-- resulting local filename of the remote file as the basename of the URL; 19-- resulting local filename of the remote file as the basename of the URL;
19-- if that is not correct (due to a redirection, for example), the local 20-- if that is not correct (due to a redirection, for example), the local
20-- filename can be given explicitly as this second argument. 21-- filename can be given explicitly as this second argument.
21-- @return string or (nil, string): the absolute local pathname for the 22-- @return string or (nil, string, [string]): the absolute local pathname for the
22-- fetched file, or nil and a message in case of errors. 23-- fetched file, or nil and a message in case of errors, followed by
24-- an optional error code.
23function fetch_url(url, filename) 25function fetch_url(url, filename)
24 assert(type(url) == "string") 26 assert(type(url) == "string")
25 assert(type(filename) == "string" or not filename) 27 assert(type(filename) == "string" or not filename)
@@ -30,9 +32,9 @@ function fetch_url(url, filename)
30 elseif protocol == "http" or protocol == "ftp" or protocol == "https" then 32 elseif protocol == "http" or protocol == "ftp" or protocol == "https" then
31 local ok = fs.download(url) 33 local ok = fs.download(url)
32 if not ok then 34 if not ok then
33 return nil, "Failed downloading "..url 35 return nil, "Failed downloading "..url, "network"
34 end 36 end
35 return fs.make_path(fs.current_dir(), filename or fs.base_name(url)) 37 return dir.path(fs.current_dir(), filename or dir.base_name(url))
36 else 38 else
37 return nil, "Unsupported protocol "..protocol 39 return nil, "Unsupported protocol "..protocol
38 end 40 end
@@ -46,30 +48,31 @@ end
46-- when creating temporary directory. 48-- when creating temporary directory.
47-- @param filename string or nil: local filename of URL to be downloaded, 49-- @param filename string or nil: local filename of URL to be downloaded,
48-- in case it can't be inferred from the URL. 50-- in case it can't be inferred from the URL.
49-- @return (string, string) or (nil, string): absolute local pathname of 51-- @return (string, string) or (nil, string, [string]): absolute local pathname of
50-- the fetched file and temporary directory name; or nil and an error message. 52-- the fetched file and temporary directory name; or nil and an error message
53-- followed by an optional error code
51function fetch_url_at_temp_dir(url, tmpname, filename) 54function fetch_url_at_temp_dir(url, tmpname, filename)
52 assert(type(url) == "string") 55 assert(type(url) == "string")
53 assert(type(tmpname) == "string") 56 assert(type(tmpname) == "string")
54 assert(type(filename) == "string" or not filename) 57 assert(type(filename) == "string" or not filename)
55 filename = filename or fs.base_name(url) 58 filename = filename or dir.base_name(url)
56 59
57 local protocol, pathname = fs.split_url(url) 60 local protocol, pathname = fs.split_url(url)
58 if protocol == "file" then 61 if protocol == "file" then
59 return pathname, fs.dir_name(pathname) 62 return pathname, dir.dir_name(pathname)
60 else 63 else
61 local dir = fs.make_temp_dir(tmpname) 64 local temp_dir = fs.make_temp_dir(tmpname)
62 if not dir then 65 if not temp_dir then
63 return nil, "Failed creating temporary directory." 66 return nil, "Failed creating temporary directory."
64 end 67 end
65 util.schedule_function(fs.delete, dir) 68 util.schedule_function(fs.delete, temp_dir)
66 fs.change_dir(dir) 69 fs.change_dir(temp_dir)
67 local file, err = fetch_url(url, filename) 70 local file, err, errcode = fetch_url(url, filename)
71 fs.pop_dir()
68 if not file then 72 if not file then
69 return nil, "Error fetching file: "..err 73 return nil, "Error fetching file: "..err, errcode
70 end 74 end
71 fs.pop_dir() 75 return file, temp_dir
72 return file, dir
73 end 76 end
74end 77end
75 78
@@ -79,37 +82,37 @@ end
79-- @param rock_file string: URL or filename of the rock. 82-- @param rock_file string: URL or filename of the rock.
80-- @param dest string or nil: if given, directory will be used as 83-- @param dest string or nil: if given, directory will be used as
81-- a permanent destination. 84-- a permanent destination.
82-- @return string or (nil, string): the directory containing the contents 85-- @return string or (nil, string, [string]): the directory containing the contents
83-- of the unpacked rock. 86-- of the unpacked rock.
84function fetch_and_unpack_rock(rock_file, dest) 87function fetch_and_unpack_rock(rock_file, dest)
85 assert(type(rock_file) == "string") 88 assert(type(rock_file) == "string")
86 assert(type(dest) == "string" or not dest) 89 assert(type(dest) == "string" or not dest)
87 90
88 local name = fs.base_name(rock_file):match("(.*)%.[^.]*%.rock") 91 local name = dir.base_name(rock_file):match("(.*)%.[^.]*%.rock")
89 92
90 local rock_file, err = fetch_url_at_temp_dir(rock_file,"luarocks-rock-"..name) 93 local rock_file, err, errcode = fetch_url_at_temp_dir(rock_file,"luarocks-rock-"..name)
91 if not rock_file then 94 if not rock_file then
92 return nil, "Could not fetch rock file: " .. err 95 return nil, "Could not fetch rock file: " .. err, errcode
93 end 96 end
94 97
95 rock_file = fs.absolute_name(rock_file) 98 rock_file = fs.absolute_name(rock_file)
96 local dir 99 local unpack_dir
97 if dest then 100 if dest then
98 dir = dest 101 unpack_dir = dest
99 fs.make_dir(dir) 102 fs.make_dir(unpack_dir)
100 else 103 else
101 dir = fs.make_temp_dir(name) 104 unpack_dir = fs.make_temp_dir(name)
102 end 105 end
103 if not dest then 106 if not dest then
104 util.schedule_function(fs.delete, dir) 107 util.schedule_function(fs.delete, unpack_dir)
105 end 108 end
106 fs.change_dir(dir) 109 fs.change_dir(unpack_dir)
107 local ok = fs.unzip(rock_file) 110 local ok = fs.unzip(rock_file)
108 if not ok then 111 if not ok then
109 return nil, "Failed unpacking rock file: " .. rock_file 112 return nil, "Failed unpacking rock file: " .. rock_file
110 end 113 end
111 fs.pop_dir() 114 fs.pop_dir()
112 return dir 115 return unpack_dir
113end 116end
114 117
115--- Back-end function that actually loads the local rockspec. 118--- Back-end function that actually loads the local rockspec.
@@ -142,7 +145,7 @@ function load_local_rockspec(filename)
142 util.platform_overrides(rockspec.source) 145 util.platform_overrides(rockspec.source)
143 util.platform_overrides(rockspec.hooks) 146 util.platform_overrides(rockspec.hooks)
144 147
145 local basename = fs.base_name(filename) 148 local basename = dir.base_name(filename)
146 rockspec.name = basename:match("(.*)-[^-]*-[0-9]*") 149 rockspec.name = basename:match("(.*)-[^-]*-[0-9]*")
147 if not rockspec.name then 150 if not rockspec.name then
148 return nil, "Expected filename in format 'name-version-revision.rockspec'." 151 return nil, "Expected filename in format 'name-version-revision.rockspec'."
@@ -150,7 +153,7 @@ function load_local_rockspec(filename)
150 153
151 local protocol, pathname = fs.split_url(rockspec.source.url) 154 local protocol, pathname = fs.split_url(rockspec.source.url)
152 if protocol == "http" or protocol == "https" or protocol == "ftp" or protocol == "file" then 155 if protocol == "http" or protocol == "https" or protocol == "ftp" or protocol == "file" then
153 rockspec.source.file = rockspec.source.file or fs.base_name(rockspec.source.url) 156 rockspec.source.file = rockspec.source.file or dir.base_name(rockspec.source.url)
154 end 157 end
155 rockspec.source.protocol, rockspec.source.pathname = protocol, pathname 158 rockspec.source.protocol, rockspec.source.pathname = protocol, pathname
156 159
@@ -167,7 +170,7 @@ function load_local_rockspec(filename)
167 170
168 rockspec.local_filename = filename 171 rockspec.local_filename = filename
169 local filebase = rockspec.source.file or rockspec.source.url 172 local filebase = rockspec.source.file or rockspec.source.url
170 local base = fs.base_name(filebase) 173 local base = dir.base_name(filebase)
171 base = base:gsub("%.[^.]*$", ""):gsub("%.tar$", "") 174 base = base:gsub("%.[^.]*$", ""):gsub("%.tar$", "")
172 rockspec.source.dir = rockspec.source.dir 175 rockspec.source.dir = rockspec.source.dir
173 or rockspec.source.module 176 or rockspec.source.module
@@ -197,19 +200,19 @@ end
197-- Only the LuaRocks runtime loader should use 200-- Only the LuaRocks runtime loader should use
198-- load_local_rockspec directly. 201-- load_local_rockspec directly.
199-- @param filename string: Local or remote filename of a rockspec. 202-- @param filename string: Local or remote filename of a rockspec.
200-- @return table or (nil, string): A table representing the rockspec 203-- @return table or (nil, string, [string]): A table representing the rockspec
201-- or nil followed by an error message. 204-- or nil followed by an error message and optional error code.
202function load_rockspec(filename) 205function load_rockspec(filename)
203 assert(type(filename) == "string") 206 assert(type(filename) == "string")
204 207
205 local name = fs.base_name(filename):match("(.*)%.rockspec") 208 local name = dir.base_name(filename):match("(.*)%.rockspec")
206 if not name then 209 if not name then
207 return nil, "Filename '"..filename.."' does not look like a rockspec." 210 return nil, "Filename '"..filename.."' does not look like a rockspec."
208 end 211 end
209 212
210 local filename, err = fetch_url_at_temp_dir(filename,"luarocks-rockspec-"..name) 213 local filename, err, errcode = fetch_url_at_temp_dir(filename,"luarocks-rockspec-"..name)
211 if not filename then 214 if not filename then
212 return nil, err 215 return nil, err, errcode
213 end 216 end
214 217
215 return load_local_rockspec(filename) 218 return load_local_rockspec(filename)
@@ -220,9 +223,9 @@ end
220-- @param extract boolean: Whether to extract the sources from 223-- @param extract boolean: Whether to extract the sources from
221-- the fetched source tarball or not. 224-- the fetched source tarball or not.
222-- @param dest_dir string or nil: If set, will extract to the given directory. 225-- @param dest_dir string or nil: If set, will extract to the given directory.
223-- @return (string, string) or (nil, string): The absolute pathname of 226-- @return (string, string) or (nil, string, [string]): The absolute pathname of
224-- the fetched source tarball and the temporary directory created to 227-- the fetched source tarball and the temporary directory created to
225-- store it; or nil and an error message. 228-- store it; or nil and an error message and optional error code.
226function get_sources(rockspec, extract, dest_dir) 229function get_sources(rockspec, extract, dest_dir)
227 assert(type(rockspec) == "table") 230 assert(type(rockspec) == "table")
228 assert(type(extract) == "boolean") 231 assert(type(extract) == "boolean")
@@ -231,17 +234,17 @@ function get_sources(rockspec, extract, dest_dir)
231 local url = rockspec.source.url 234 local url = rockspec.source.url
232 local name = rockspec.name.."-"..rockspec.version 235 local name = rockspec.name.."-"..rockspec.version
233 local filename = rockspec.source.file 236 local filename = rockspec.source.file
234 local source_file, dir, err 237 local source_file, store_dir, err, errcode
235 if dest_dir then 238 if dest_dir then
236 fs.change_dir(dest_dir) 239 fs.change_dir(dest_dir)
237 source_file, err = fetch_url(url, filename) 240 source_file, err, errcode = fetch_url(url, filename)
238 fs.pop_dir() 241 fs.pop_dir()
239 dir = dest_dir 242 store_dir = dest_dir
240 else 243 else
241 source_file, dir = fetch_url_at_temp_dir(url, "luarocks-source-"..name, filename) 244 source_file, store_dir, errcode = fetch_url_at_temp_dir(url, "luarocks-source-"..name, filename)
242 end 245 end
243 if not source_file then 246 if not source_file then
244 return nil, err or dir 247 return nil, err or store_dir, errcode
245 end 248 end
246 if rockspec.source.md5 then 249 if rockspec.source.md5 then
247 if not fs.check_md5(source_file, rockspec.source.md5) then 250 if not fs.check_md5(source_file, rockspec.source.md5) then
@@ -249,11 +252,11 @@ function get_sources(rockspec, extract, dest_dir)
249 end 252 end
250 end 253 end
251 if extract then 254 if extract then
252 fs.change_dir(dir) 255 fs.change_dir(store_dir)
253 fs.unpack_archive(rockspec.source.file) 256 fs.unpack_archive(rockspec.source.file)
254 fs.pop_dir() 257 fs.pop_dir()
255 end 258 end
256 return source_file, dir 259 return source_file, store_dir
257end 260end
258 261
259--- Download sources for building a rock, calling the appropriate protocol method. 262--- Download sources for building a rock, calling the appropriate protocol method.
diff --git a/src/luarocks/fetch/cvs.lua b/src/luarocks/fetch/cvs.lua
index 291388cb..c2957c09 100644
--- a/src/luarocks/fetch/cvs.lua
+++ b/src/luarocks/fetch/cvs.lua
@@ -3,6 +3,7 @@
3module("luarocks.fetch.cvs", package.seeall) 3module("luarocks.fetch.cvs", package.seeall)
4 4
5local fs = require("luarocks.fs") 5local fs = require("luarocks.fs")
6local dir = require("luarocks.dir")
6local util = require("luarocks.util") 7local util = require("luarocks.util")
7 8
8--- Download sources for building a rock, using CVS. 9--- Download sources for building a rock, using CVS.
@@ -17,27 +18,27 @@ function get_sources(rockspec, extract, dest_dir)
17 assert(type(dest_dir) == "string" or not dest_dir) 18 assert(type(dest_dir) == "string" or not dest_dir)
18 19
19 local name_version = rockspec.name .. "-" .. rockspec.version 20 local name_version = rockspec.name .. "-" .. rockspec.version
20 local module = rockspec.source.module or fs.base_name(rockspec.source.url) 21 local module = rockspec.source.module or dir.base_name(rockspec.source.url)
21 local command = {"cvs", "-d"..rockspec.source.pathname, "export", module} 22 local command = {"cvs", "-d"..rockspec.source.pathname, "export", module}
22 if rockspec.source.tag then 23 if rockspec.source.tag then
23 table.insert(command, 4, "-r") 24 table.insert(command, 4, "-r")
24 table.insert(command, 5, rockspec.source.tag) 25 table.insert(command, 5, rockspec.source.tag)
25 end 26 end
26 local dir 27 local store_dir
27 if not dest_dir then 28 if not dest_dir then
28 dir = fs.make_temp_dir(name_version) 29 store_dir = fs.make_temp_dir(name_version)
29 if not dir then 30 if not store_dir then
30 return nil, "Failed creating temporary directory." 31 return nil, "Failed creating temporary directory."
31 end 32 end
32 util.schedule_function(fs.delete, dir) 33 util.schedule_function(fs.delete, store_dir)
33 else 34 else
34 dir = dest_dir 35 store_dir = dest_dir
35 end 36 end
36 fs.change_dir(dir) 37 fs.change_dir(store_dir)
37 if not fs.execute(unpack(command)) then 38 if not fs.execute(unpack(command)) then
38 return nil, "Failed fetching files from CVS." 39 return nil, "Failed fetching files from CVS."
39 end 40 end
40 fs.pop_dir() 41 fs.pop_dir()
41 return module, dir 42 return module, store_dir
42end 43end
43 44
diff --git a/src/luarocks/fetch/git.lua b/src/luarocks/fetch/git.lua
index d0d204a9..d2420ef8 100644
--- a/src/luarocks/fetch/git.lua
+++ b/src/luarocks/fetch/git.lua
@@ -3,6 +3,7 @@
3module("luarocks.fetch.git", package.seeall) 3module("luarocks.fetch.git", package.seeall)
4 4
5local fs = require("luarocks.fs") 5local fs = require("luarocks.fs")
6local dir = require("luarocks.dir")
6local util = require("luarocks.util") 7local util = require("luarocks.util")
7 8
8--- Download sources for building a rock, using git. 9--- Download sources for building a rock, using git.
@@ -17,7 +18,7 @@ function get_sources(rockspec, extract, dest_dir)
17 assert(type(dest_dir) == "string" or not dest_dir) 18 assert(type(dest_dir) == "string" or not dest_dir)
18 19
19 local name_version = rockspec.name .. "-" .. rockspec.version 20 local name_version = rockspec.name .. "-" .. rockspec.version
20 local module = fs.base_name(rockspec.source.url) 21 local module = dir.base_name(rockspec.source.url)
21 -- Strip off .git from base name if present 22 -- Strip off .git from base name if present
22 module = module:gsub("%.git$", "") 23 module = module:gsub("%.git$", "")
23 local command = {"git", "clone", rockspec.source.url, module} 24 local command = {"git", "clone", rockspec.source.url, module}
@@ -26,17 +27,17 @@ function get_sources(rockspec, extract, dest_dir)
26 if tag_or_branch then 27 if tag_or_branch then
27 checkout_command = {"git", "checkout", tag_or_branch} 28 checkout_command = {"git", "checkout", tag_or_branch}
28 end 29 end
29 local dir 30 local store_dir
30 if not dest_dir then 31 if not dest_dir then
31 dir = fs.make_temp_dir(name_version) 32 store_dir = fs.make_temp_dir(name_version)
32 if not dir then 33 if not store_dir then
33 return nil, "Failed creating temporary directory." 34 return nil, "Failed creating temporary directory."
34 end 35 end
35 util.schedule_function(fs.delete, dir) 36 util.schedule_function(fs.delete, store_dir)
36 else 37 else
37 dir = dest_dir 38 store_dir = dest_dir
38 end 39 end
39 fs.change_dir(dir) 40 fs.change_dir(store_dir)
40 if not fs.execute(unpack(command)) then 41 if not fs.execute(unpack(command)) then
41 return nil, "Failed fetching files from GIT while cloning." 42 return nil, "Failed fetching files from GIT while cloning."
42 end 43 end
@@ -48,6 +49,6 @@ function get_sources(rockspec, extract, dest_dir)
48 fs.pop_dir() 49 fs.pop_dir()
49 end 50 end
50 fs.pop_dir() 51 fs.pop_dir()
51 return module, dir 52 return module, store_dir
52end 53end
53 54
diff --git a/src/luarocks/fetch/sscm.lua b/src/luarocks/fetch/sscm.lua
index a2c70b96..539014c2 100644
--- a/src/luarocks/fetch/sscm.lua
+++ b/src/luarocks/fetch/sscm.lua
@@ -3,6 +3,7 @@
3module("luarocks.fetch.sscm", package.seeall) 3module("luarocks.fetch.sscm", package.seeall)
4 4
5local fs = require("luarocks.fs") 5local fs = require("luarocks.fs")
6local dir = require("luarocks.dir")
6 7
7--- Download sources via Surround SCM Server for building a rock. 8--- Download sources via Surround SCM Server for building a rock.
8-- @param rockspec table: The rockspec table 9-- @param rockspec table: The rockspec table
@@ -15,7 +16,7 @@ function get_sources(rockspec, extract, dest_dir)
15 assert(type(rockspec) == "table") 16 assert(type(rockspec) == "table")
16 assert(type(dest_dir) == "string" or not dest_dir) 17 assert(type(dest_dir) == "string" or not dest_dir)
17 18
18 local module = rockspec.source.module or fs.base_name(rockspec.source.url) 19 local module = rockspec.source.module or dir.base_name(rockspec.source.url)
19 local branch, repository = string.match(rockspec.source.pathname, "^([^/]*)/(.*)") 20 local branch, repository = string.match(rockspec.source.pathname, "^([^/]*)/(.*)")
20 if not branch or not repository then 21 if not branch or not repository then
21 return nil, "Error retrieving branch and repository from rockspec." 22 return nil, "Error retrieving branch and repository from rockspec."
diff --git a/src/luarocks/fs.lua b/src/luarocks/fs.lua
index 7027167a..a905755d 100644
--- a/src/luarocks/fs.lua
+++ b/src/luarocks/fs.lua
@@ -21,14 +21,12 @@ for _, platform in ipairs(cfg.platforms) do
21 end 21 end
22end 22end
23 23
24local fs_lua = require("luarocks.fs.lua")
24local fs_unix = require("luarocks.fs.unix") 25local fs_unix = require("luarocks.fs.unix")
25 26
26local fs_mt = { 27local fs_mt = {
27 __index = function(t, k) 28 __index = function(t, k)
28 local impl = fs_impl and fs_impl[k] 29 local impl = fs_lua[k] or fs_impl[k] or fs_unix[k]
29 if not impl then
30 impl = fs_unix[k]
31 end
32 rawset(t, k, impl) 30 rawset(t, k, impl)
33 return impl 31 return impl
34 end 32 end
@@ -36,6 +34,7 @@ local fs_mt = {
36 34
37setmetatable(luarocks.fs, fs_mt) 35setmetatable(luarocks.fs, fs_mt)
38 36
37fs_lua.init_fs_functions(luarocks.fs)
39fs_unix.init_fs_functions(luarocks.fs) 38fs_unix.init_fs_functions(luarocks.fs)
40if fs_impl then 39if fs_impl then
41 fs_impl.init_fs_functions(luarocks.fs) 40 fs_impl.init_fs_functions(luarocks.fs)
diff --git a/src/luarocks/fs/lua.lua b/src/luarocks/fs/lua.lua
new file mode 100644
index 00000000..00efd4f4
--- /dev/null
+++ b/src/luarocks/fs/lua.lua
@@ -0,0 +1,565 @@
1
2local assert, type, table, io, package, math, os, ipairs =
3 assert, type, table, io, package, math, os, ipairs
4
5--- Native Lua implementation of filesystem and platform abstractions,
6-- using LuaFileSystem, LZLib, MD5 and LuaCurl.
7module("luarocks.fs.lua", package.seeall)
8
9local cfg = require("luarocks.cfg")
10local dir = require("luarocks.dir")
11
12local zip_ok, zip = pcall(require, "luarocks.tools.zip")
13local lfs_ok, lfs = pcall(require, "lfs")
14local curl_ok, curl = pcall(require, "luacurl")
15local md5_ok, md5 = pcall(require, "md5")
16
17local tar = require("luarocks.tools.tar")
18local patch = require("luarocks.tools.patch")
19
20local dir_stack = {}
21
22math.randomseed(os.time())
23
24dir_separator = "/"
25
26local fs_absolute_name,
27 fs_copy,
28 fs_current_dir,
29 fs_dir_stack,
30 fs_execute,
31 fs_execute_string,
32 fs_exists,
33 fs_find,
34 fs_is_dir,
35 fs_is_file,
36 fs_make_dir,
37 fs_set_time,
38 fs_Q
39
40function init_fs_functions(impl)
41 fs_absolute_name = impl.absolute_name
42 fs_copy = impl.copy
43 fs_current_dir = impl.current_dir
44 fs_dir_stack = impl.dir_stack
45 fs_execute = impl.execute
46 fs_execute_string = impl.execute_string
47 fs_exists = impl.exists
48 fs_find = impl.find
49 fs_is_dir = impl.is_dir
50 fs_is_file = impl.is_file
51 fs_make_dir = impl.make_dir
52 fs_set_time = impl.set_time
53 fs_Q = impl.Q
54end
55
56--- Quote argument for shell processing.
57-- Adds single quotes and escapes.
58-- @param arg string: Unquoted argument.
59-- @return string: Quoted argument.
60function Q(arg)
61 assert(type(arg) == "string")
62
63 -- FIXME Unix-specific
64 return "'" .. arg:gsub("\\", "\\\\"):gsub("'", "'\\''") .. "'"
65end
66
67--- Run the given command.
68-- The command is executed in the current directory in the dir stack.
69-- @param cmd string: No quoting/escaping is applied to the command.
70-- @return boolean: true if command succeeds (status code 0), false
71-- otherwise.
72function execute_string(cmd)
73 if os.execute(cmd) == 0 then
74 return true
75 else
76 return false
77 end
78end
79
80--- Run the given command, quoting its arguments.
81-- The command is executed in the current directory in the dir stack.
82-- @param command string: The command to be executed. No quoting/escaping
83-- is applied.
84-- @param ... Strings containing additional arguments, which are quoted.
85-- @return boolean: true if command succeeds (status code 0), false
86-- otherwise.
87function execute(command, ...)
88 assert(type(command) == "string")
89
90 for _, arg in ipairs({...}) do
91 assert(type(arg) == "string")
92 command = command .. " " .. fs_Q(arg)
93 end
94 return fs_execute_string(command)
95end
96
97--- Test is file/dir is writable.
98-- Warning: testing if a file/dir is writable does not guarantee
99-- that it will remain writable and therefore it is no replacement
100-- for checking the result of subsequent operations.
101-- @param file string: filename to test
102-- @return boolean: true if file exists, false otherwise.
103function is_writable(file)
104 assert(file)
105 local result
106 if fs_is_dir(file) then
107 local file2 = file .. '/.tmpluarockstestwritable'
108 local fh = io.open(file2, 'w')
109 result = fh ~= nil
110 if fh then fh:close() end
111 os.remove(file2)
112 else
113 local fh = io.open(file, 'r+')
114 result = fh ~= nil
115 if fh then fh:close() end
116 end
117 return result
118end
119
120--- Create a temporary directory.
121-- @param name string: name pattern to use for avoiding conflicts
122-- when creating temporary directory.
123-- @return string or nil: name of temporary directory or nil on failure.
124function make_temp_dir(name)
125 assert(type(name) == "string")
126
127 local temp_dir = (os.getenv("TMP") or "/tmp") .. "/luarocks_" .. name:gsub(dir.separator, "_") .. "-" .. tostring(math.floor(math.random() * 10000))
128 if fs_make_dir(temp_dir) then
129 return temp_dir
130 else
131 return nil
132 end
133end
134
135--- Return an absolute pathname from a potentially relative one.
136-- @param pathname string: pathname to convert.
137-- @param relative_to string or nil: path to prepend when making
138-- pathname absolute, or the current dir in the dir stack if
139-- not given.
140-- @return string: The pathname converted to absolute.
141function absolute_name(pathname, relative_to)
142 assert(type(pathname) == "string")
143 assert(type(relative_to) == "string" or not relative_to)
144
145 relative_to = relative_to or fs_current_dir()
146 if pathname:sub(1,1) == "/" then
147 return pathname
148 else
149 return relative_to .. "/" .. pathname
150 end
151end
152
153--- Split protocol and path from an URL or local pathname.
154-- URLs should be in the "protocol://path" format.
155-- For local pathnames, "file" is returned as the protocol.
156-- @param url string: an URL or a local pathname.
157-- @return string, string: the protocol, and the absolute pathname without the protocol.
158function split_url(url)
159 assert(type(url) == "string")
160
161 local protocol, pathname = url:match("^([^:]*)://(.*)")
162 if not protocol then
163 protocol = "file"
164 pathname = url
165 end
166 if protocol == "file" then
167 pathname = fs_absolute_name(pathname)
168 end
169 return protocol, pathname
170end
171
172---------------------------------------------------------------------
173-- LuaFileSystem functions
174---------------------------------------------------------------------
175
176if lfs_ok then
177
178--- Obtain current directory.
179-- Uses the module's internal dir stack.
180-- @return string: the absolute pathname of the current directory.
181function current_dir()
182 return lfs.currentdir()
183end
184
185--- Change the current directory.
186-- Uses the module's internal dir stack. This does not have exact
187-- semantics of chdir, as it does not handle errors the same way,
188-- but works well for our purposes for now.
189-- @param d string: The directory to switch to.
190function change_dir(d)
191 table.insert(dir_stack, lfs.currentdir())
192 lfs.chdir(d)
193--local x="CHDIR: " for _,d in ipairs(dir_stack) do x=x.." "..d end print(x)
194end
195
196--- Change directory to root.
197-- Allows leaving a directory (e.g. for deleting it) in
198-- a crossplatform way.
199function change_dir_to_root()
200 table.insert(dir_stack, lfs.currentdir())
201 -- TODO Does this work on Windows?
202 lfs.chdir("/")
203--local x="CHDIR ROOT: " for _,d in ipairs(dir_stack) do x=x.." "..d end print(x)
204end
205
206--- Change working directory to the previous in the dir stack.
207-- @return true if a pop ocurred, false if the stack was empty.
208function pop_dir()
209 local d = table.remove(dir_stack)
210--local x="POP DIR: " for _,d in ipairs(dir_stack) do x=x.." "..d end print(x)
211 if d then
212 lfs.chdir(d)
213 return true
214 else
215 return false
216 end
217end
218
219--- Create a directory if it does not already exist.
220-- If any of the higher levels in the path name does not exist
221-- too, they are created as well.
222-- @param directory string: pathname of directory to create.
223-- @return boolean: true on success, false on failure.
224function make_dir(directory)
225 assert(type(directory) == "string")
226 local path = nil
227 for d in directory:gmatch("[^"..dir.separator.."]*"..dir.separator.."*") do
228 path = path and path..d or d
229 local mode = lfs.attributes(path, "mode")
230 if not mode then
231 if not lfs.mkdir(path) then
232 return false
233 end
234 elseif mode ~= "directory" then
235 return false
236 end
237 end
238 return true
239end
240
241--- Remove a directory if it is empty.
242-- Does not return errors (for example, if directory is not empty or
243-- if already does not exist)
244-- @param d string: pathname of directory to remove.
245function remove_dir_if_empty(d)
246 assert(d)
247 lfs.rmdir(d)
248end
249
250--- Copy a file.
251-- @param src string: Pathname of source
252-- @param dest string: Pathname of destination
253-- @return boolean or (boolean, string): true on success, false on failure,
254-- plus an error message.
255function copy(src, dest)
256 assert(src and dest)
257 local destmode = lfs.attributes(dest, "mode")
258 if destmode == "directory" then
259 dest = dir.path(dest, dir.base_name(src))
260 end
261 local src_h, err = io.open(src, "r")
262 if not src_h then return nil, err end
263 local dest_h, err = io.open(dest, "w+")
264 if not dest_h then src_h:close() return nil, err end
265 while true do
266 local block = src_h:read(8192)
267 if not block then break end
268 dest_h:write(block)
269 end
270 src_h:close()
271 dest_h:close()
272 return true
273end
274
275--- Implementation function for recursive copy of directory contents.
276-- @param src string: Pathname of source
277-- @param dest string: Pathname of destination
278-- @return boolean or (boolean, string): true on success, false on failure
279local function recursive_copy(src, dest)
280 local srcmode = lfs.attributes(src, "mode")
281
282 if srcmode == "file" then
283 local ok = fs_copy(src, dest)
284 if not ok then return false end
285 elseif srcmode == "directory" then
286 local subdir = dir.path(dest, dir.base_name(src))
287 fs_make_dir(subdir)
288 for file in lfs.dir(src) do
289 if file ~= "." and file ~= ".." then
290 local ok = recursive_copy(dir.path(src, file), subdir)
291 if not ok then return false end
292 end
293 end
294 end
295 return true
296end
297
298--- Recursively copy the contents of a directory.
299-- @param src string: Pathname of source
300-- @param dest string: Pathname of destination
301-- @return boolean or (boolean, string): true on success, false on failure,
302-- plus an error message.
303function copy_contents(src, dest)
304 assert(src and dest)
305 assert(lfs.attributes(src, "mode") == "directory")
306
307 for file in lfs.dir(src) do
308 local ok = recursive_copy(dir.path(src, file), dest)
309 if not ok then
310 return false, "Failed copying "..src.." to "..dest
311 end
312 end
313 return true
314end
315
316--- Implementation function for recursive removal of directories.
317-- @param src string: Pathname of source
318-- @param dest string: Pathname of destination
319-- @return boolean or (boolean, string): true on success,
320-- or nil and an error message on failure.
321local function recursive_delete(src)
322 local srcmode = lfs.attributes(src, "mode")
323
324 if srcmode == "file" then
325 return os.remove(src)
326 elseif srcmode == "directory" then
327 for file in lfs.dir(src) do
328 if file ~= "." and file ~= ".." then
329 local ok, err = recursive_delete(dir.path(src, file))
330 if not ok then return nil, err end
331 end
332 end
333 local ok, err = lfs.rmdir(src)
334 if not ok then return nil, err end
335 end
336 return true
337end
338
339--- Delete a file or a directory and all its contents.
340-- For safety, this only accepts absolute paths.
341-- @param arg string: Pathname of source
342-- @return boolean: true on success, false on failure.
343function delete(arg)
344 assert(arg)
345 assert(arg:sub(1,1) == "/")
346 return recursive_delete(arg) or false
347end
348
349--- List the contents of a directory.
350-- @param at string or nil: directory to list (will be the current
351-- directory if none is given).
352-- @return table: an array of strings with the filenames representing
353-- the contents of a directory.
354function list_dir(at)
355 assert(type(at) == "string" or not at)
356 if not at then
357 at = fs_current_dir()
358 end
359 if not fs_is_dir(at) then
360 return {}
361 end
362 local result = {}
363 for file in lfs.dir(at) do
364 if file ~= "." and file ~= ".." then
365 table.insert(result, file)
366 end
367 end
368 return result
369end
370
371--- Implementation function for recursive find.
372-- @param cwd string: Current working directory in recursion.
373-- @param prefix string: Auxiliary prefix string to form pathname.
374-- @param result table: Array of strings where results are collected.
375local function recursive_find(cwd, prefix, result)
376 for file in lfs.dir(cwd) do
377 if file ~= "." and file ~= ".." then
378 local item = prefix .. file
379 table.insert(result, item)
380 if lfs.attributes(item, "mode") == "directory" then
381 recursive_find(item, item..dir_separator, result)
382 end
383 end
384 end
385end
386
387--- Recursively scan the contents of a directory.
388-- @param at string or nil: directory to scan (will be the current
389-- directory if none is given).
390-- @return table: an array of strings with the filenames representing
391-- the contents of a directory.
392function find(at)
393 assert(type(at) == "string" or not at)
394 if not at then
395 at = fs_current_dir()
396 end
397 if not fs_is_dir(at) then
398 return {}
399 end
400 local result = {}
401 recursive_find(lfs.currentdir(), "", result)
402 return result
403end
404
405--- Test for existance of a file.
406-- @param file string: filename to test
407-- @return boolean: true if file exists, false otherwise.
408function exists(file)
409 assert(file)
410 return type(lfs.attributes(file)) == "table"
411end
412
413--- Test is pathname is a directory.
414-- @param file string: pathname to test
415-- @return boolean: true if it is a directory, false otherwise.
416function is_dir(file)
417 assert(file)
418 return lfs.attributes(file, "mode") == "directory"
419end
420
421--- Test is pathname is a regular file.
422-- @param file string: pathname to test
423-- @return boolean: true if it is a file, false otherwise.
424function is_file(file)
425 assert(file)
426 return lfs.attributes(file, "mode") == "file"
427end
428
429function set_time(file, time)
430 return lfs.touch(file, time)
431end
432
433end
434
435---------------------------------------------------------------------
436-- LuaZip functions
437---------------------------------------------------------------------
438
439if zip_ok then
440
441local function zip(zipfile, ...)
442 return zip.zip(zipfile, ...)
443end
444
445--- Uncompress files from a .zip archive.
446-- @param zipfile string: pathname of .zip archive to be extracted.
447-- @return boolean: true on success, false on failure.
448function unzip(zipfile)
449 assert(zipfile)
450 -- FIXME!!!!
451 return fs_execute("unzip", zipfile)
452end
453
454end
455
456---------------------------------------------------------------------
457-- LuaCurl functions
458---------------------------------------------------------------------
459
460if curl_ok then
461
462--- Download a remote file.
463-- @param url string: URL to be fetched.
464-- @param filename string or nil: this function attempts to detect the
465-- resulting local filename of the remote file as the basename of the URL;
466-- if that is not correct (due to a redirection, for example), the local
467-- filename can be given explicitly as this second argument.
468-- @return boolean: true on success, false on failure.
469function download(url, filename)
470 assert(type(url) == "string")
471 assert(type(filename) == "string" or not filename)
472
473 filename = filename or dir.base_name(url)
474
475 local c = curl.new()
476 if not c then return false end
477 local file = io.open(filename, "wb")
478 if not file then return false end
479 local ok = c:setopt(curl.OPT_WRITEFUNCTION, function (stream, buffer)
480 stream:write(buffer)
481 return string.len(buffer)
482 end)
483 ok = ok and c:setopt(curl.OPT_WRITEDATA, file)
484 ok = ok and c:setopt(curl.OPT_BUFFERSIZE, 5000)
485 ok = ok and c:setopt(curl.OPT_HTTPHEADER, "Connection: Keep-Alive")
486 ok = ok and c:setopt(curl.OPT_URL, url)
487 ok = ok and c:setopt(curl.OPT_CONNECTTIMEOUT, 15)
488 ok = ok and c:perform()
489 ok = ok and c:close()
490 file:close()
491 return ok
492end
493
494end
495
496---------------------------------------------------------------------
497-- MD5 functions
498---------------------------------------------------------------------
499
500if md5_ok then
501
502--- Check the MD5 checksum for a file.
503-- @param file string: The file to be checked.
504-- @param md5sum string: The string with the expected MD5 checksum.
505-- @return boolean: true if the MD5 checksum for 'file' equals 'md5sum', false if not
506-- or if it could not perform the check for any reason.
507function check_md5(file, md5sum)
508 file = fs_absolute_name(file)
509 local file = io.open(file, "r")
510 if not file then return false end
511 local computed = md5.sumhexa(file:read("*a"))
512 file:close()
513 if not computed then
514 return false
515 end
516 if computed:match("^"..md5sum) then
517 return true
518 else
519 return false
520 end
521end
522
523end
524
525--- Apply a patch.
526-- @param patchname string: The filename of the patch.
527function apply_patch(patchname, patchdata)
528 local p, all_ok = patch.read_patch(patchname, patchdata)
529 if not all_ok then
530 return nil, "Failed reading patch "..patchname
531 end
532 if p then
533 return patch.apply_patch(p, 1)
534 end
535end
536
537--- Unpack an archive.
538-- Extract the contents of an archive, detecting its format by
539-- filename extension.
540-- @param archive string: Filename of archive.
541-- @return boolean or (boolean, string): true on success, false and an error message on failure.
542function unpack_archive(archive)
543 assert(type(archive) == "string")
544
545 local ok
546 if archive:match("%.tar%.gz$") or archive:match("%.tgz$") then
547 -- ok = fs_execute("tar zxvpf ", archive)
548 ok = fs_execute_string("gunzip -c "..archive.."|tar -xf -")
549 elseif archive:match("%.tar%.bz2$") then
550 -- ok = fs_execute("tar jxvpf ", archive)
551 ok = fs_execute_string("bunzip2 -c "..archive.."|tar -xf -")
552 elseif archive:match("%.zip$") then
553 ok = fs_execute("unzip ", archive)
554 elseif archive:match("%.lua$") or archive:match("%.c$") then
555 -- Ignore .lua and .c files; they don't need to be extracted.
556 return true
557 else
558 local ext = archive:match(".*(%..*)")
559 return false, "Unrecognized filename extension "..(ext or "")
560 end
561 if not ok then
562 return false, "Failed extracting "..archive
563 end
564 return true
565end
diff --git a/src/luarocks/fs/unix.lua b/src/luarocks/fs/unix.lua
index bf72988f..11378854 100644
--- a/src/luarocks/fs/unix.lua
+++ b/src/luarocks/fs/unix.lua
@@ -6,43 +6,40 @@ local assert, type, table, io, package, math, os, ipairs =
6module("luarocks.fs.unix", package.seeall) 6module("luarocks.fs.unix", package.seeall)
7 7
8local cfg = require("luarocks.cfg") 8local cfg = require("luarocks.cfg")
9local dir = require("luarocks.dir")
9 10
10math.randomseed(os.time()) 11math.randomseed(os.time())
11 12
12dir_stack = {} 13dir_stack = {}
13 14
14local fs_absolute_name, 15local fs_absolute_name,
15 fs_base_name,
16 fs_copy, 16 fs_copy,
17 fs_current_dir, 17 fs_current_dir,
18 fs_dir_stack, 18 fs_dir_stack,
19 fs_execute, 19 fs_execute,
20 fs_execute_string, 20 fs_execute_string,
21 fs_is_dir, 21 fs_is_dir,
22 fs_is_file,
22 fs_make_dir, 23 fs_make_dir,
23 fs_make_path,
24 fs_exists, 24 fs_exists,
25 fs_find, 25 fs_find,
26 fs_Q 26 fs_Q
27 27
28function init_fs_functions(impl) 28function init_fs_functions(impl)
29 fs_absolute_name = impl.absolute_name 29 fs_absolute_name = impl.absolute_name
30 fs_base_name = impl.base_name
31 fs_copy = impl.copy 30 fs_copy = impl.copy
32 fs_current_dir = impl.current_dir 31 fs_current_dir = impl.current_dir
33 fs_dir_stack = impl.dir_stack 32 fs_dir_stack = impl.dir_stack
34 fs_execute = impl.execute 33 fs_execute = impl.execute
35 fs_execute_string = impl.execute_string 34 fs_execute_string = impl.execute_string
36 fs_is_dir = impl.is_dir 35 fs_is_dir = impl.is_dir
36 fs_is_file = impl.is_file
37 fs_make_dir = impl.make_dir 37 fs_make_dir = impl.make_dir
38 fs_make_path = impl.make_path
39 fs_exists = impl.exists 38 fs_exists = impl.exists
40 fs_find = impl.find 39 fs_find = impl.find
41 fs_Q = impl.Q 40 fs_Q = impl.Q
42end 41end
43 42
44dir_separator = "/"
45
46--- Quote argument for shell processing. 43--- Quote argument for shell processing.
47-- Adds single quotes and escapes. 44-- Adds single quotes and escapes.
48-- @param arg string: Unquoted argument. 45-- @param arg string: Unquoted argument.
@@ -63,8 +60,8 @@ function current_dir()
63 current = pipe:read("*l") 60 current = pipe:read("*l")
64 pipe:close() 61 pipe:close()
65 end 62 end
66 for _, dir in ipairs(fs_dir_stack) do 63 for _, d in ipairs(fs_dir_stack) do
67 current = fs_absolute_name(dir, current) 64 current = fs_absolute_name(d, current)
68 end 65 end
69 return current 66 return current
70end 67end
@@ -103,10 +100,10 @@ end
103-- Uses the module's internal dir stack. This does not have exact 100-- Uses the module's internal dir stack. This does not have exact
104-- semantics of chdir, as it does not handle errors the same way, 101-- semantics of chdir, as it does not handle errors the same way,
105-- but works well for our purposes for now. 102-- but works well for our purposes for now.
106-- @param dir string: The directory to switch to. 103-- @param d string: The directory to switch to.
107function change_dir(dir) 104function change_dir(d)
108 assert(type(dir) == "string") 105 assert(type(d) == "string")
109 table.insert(fs_dir_stack, dir) 106 table.insert(fs_dir_stack, d)
110end 107end
111 108
112--- Change directory to root. 109--- Change directory to root.
@@ -118,26 +115,27 @@ end
118 115
119--- Change working directory to the previous in the dir stack. 116--- Change working directory to the previous in the dir stack.
120function pop_dir() 117function pop_dir()
121 table.remove(fs_dir_stack) 118 local d = table.remove(fs_dir_stack)
119 return d ~= nil
122end 120end
123 121
124--- Create a directory if it does not already exist. 122--- Create a directory if it does not already exist.
125-- If any of the higher levels in the path name does not exist 123-- If any of the higher levels in the path name does not exist
126-- too, they are created as well. 124-- too, they are created as well.
127-- @param dir string: pathname of directory to create. 125-- @param d string: pathname of directory to create.
128-- @return boolean: true on success, false on failure. 126-- @return boolean: true on success, false on failure.
129function make_dir(dir) 127function make_dir(d)
130 assert(dir) 128 assert(d)
131 return fs_execute("mkdir -p", dir) 129 return fs_execute("mkdir -p", d)
132end 130end
133 131
134--- Remove a directory if it is empty. 132--- Remove a directory if it is empty.
135-- Does not return errors (for example, if directory is not empty or 133-- Does not return errors (for example, if directory is not empty or
136-- if already does not exist) 134-- if already does not exist)
137-- @param dir string: pathname of directory to remove. 135-- @param dir string: pathname of directory to remove.
138function remove_dir_if_empty(dir) 136function remove_dir_if_empty(d)
139 assert(dir) 137 assert(d)
140 fs_execute_string("rmdir "..fs_Q(dir).." 1> /dev/null 2> /dev/null") 138 fs_execute_string("rmdir "..fs_Q(d).." 1> /dev/null 2> /dev/null")
141end 139end
142 140
143--- Copy a file. 141--- Copy a file.
@@ -183,7 +181,7 @@ end
183-- directory if none is given). 181-- directory if none is given).
184-- @return table: an array of strings with the filenames representing 182-- @return table: an array of strings with the filenames representing
185-- the contents of a directory. 183-- the contents of a directory.
186function dir(at) 184function list_dir(at)
187 assert(type(at) == "string" or not at) 185 assert(type(at) == "string" or not at)
188 if not at then 186 if not at then
189 at = fs_current_dir() 187 at = fs_current_dir()
@@ -263,6 +261,14 @@ function is_dir(file)
263 return fs_execute("test -d", file) 261 return fs_execute("test -d", file)
264end 262end
265 263
264--- Test is pathname is a regular file.
265-- @param file string: pathname to test
266-- @return boolean: true if it is a regular file, false otherwise.
267function is_file(file)
268 assert(file)
269 return fs_execute("test -f", file)
270end
271
266--- Download a remote file. 272--- Download a remote file.
267-- @param url string: URL to be fetched. 273-- @param url string: URL to be fetched.
268-- @param filename string or nil: this function attempts to detect the 274-- @param filename string or nil: this function attempts to detect the
@@ -275,39 +281,17 @@ function download(url, filename)
275 assert(type(filename) == "string" or not filename) 281 assert(type(filename) == "string" or not filename)
276 282
277 if cfg.downloader == "wget" then 283 if cfg.downloader == "wget" then
278 if filename then 284 if filename then
279 return fs_execute("wget --quiet --continue --output-document ", filename, url) 285 return fs_execute("wget --quiet --continue --output-document ", filename, url)
280 else 286 else
281 return fs_execute("wget --quiet --continue ", url) 287 return fs_execute("wget --quiet --continue ", url)
282 end 288 end
283 elseif cfg.downloader == "curl" then 289 elseif cfg.downloader == "curl" then
284 filename = filename or fs_base_name(url) 290 filename = filename or dir.base_name(url)
285 return fs_execute_string("curl "..fs_Q(url).." 2> /dev/null 1> "..fs_Q(filename)) 291 return fs_execute_string("curl "..fs_Q(url).." 2> /dev/null 1> "..fs_Q(filename))
286 end 292 end
287end 293end
288 294
289--- Strip the path off a path+filename.
290-- @param pathname string: A path+name, such as "/a/b/c".
291-- @return string: The filename without its path, such as "c".
292function base_name(pathname)
293 assert(type(pathname) == "string")
294
295 local base = pathname:match(".*/([^/]*)")
296 return base or pathname
297end
298
299--- Strip the name off a path+filename.
300-- @param pathname string: A path+name, such as "/a/b/c".
301-- @return string: The filename without its path, such as "/a/b/".
302-- For entries such as "/a/b/", "/a/" is returned. If there are
303-- no directory separators in input, "" is returned.
304function dir_name(pathname)
305 assert(type(pathname) == "string")
306
307 local dir = pathname:gsub("/*$", ""):match("(.*/)[^/]*")
308 return dir or ""
309end
310
311--- Create a temporary directory. 295--- Create a temporary directory.
312-- @param name string: name pattern to use for avoiding conflicts 296-- @param name string: name pattern to use for avoiding conflicts
313-- when creating temporary directory. 297-- when creating temporary directory.
@@ -315,7 +299,7 @@ end
315function make_temp_dir(name) 299function make_temp_dir(name)
316 assert(type(name) == "string") 300 assert(type(name) == "string")
317 301
318 local temp_dir = (os.getenv("TMP") or "/tmp") .. "/" .. name .. "-" .. tostring(math.floor(math.random() * 10000)) 302 local temp_dir = (os.getenv("TMP") or "/tmp") .. "/luarocks_" .. name:gsub(dir.separator, "_") .. "-" .. tostring(math.floor(math.random() * 10000))
319 if fs_make_dir(temp_dir) then 303 if fs_make_dir(temp_dir) then
320 return temp_dir 304 return temp_dir
321 else 305 else
@@ -323,9 +307,13 @@ function make_temp_dir(name)
323 end 307 end
324end 308end
325 309
310function chmod(pathname, mode)
311 return fs_execute("chmod "..mode, pathname)
312end
313
326--- Apply a patch. 314--- Apply a patch.
327-- @param patchname string: The filename of the patch. 315-- @param patchname string: The filename of the patch.
328function patch(patchname) 316function apply_patch(patchname)
329 return fs_execute("patch -p1 -f -i ", patchname) 317 return fs_execute("patch -p1 -f -i ", patchname)
330end 318end
331 319
@@ -417,27 +405,6 @@ function absolute_name(pathname, relative_to)
417 end 405 end
418end 406end
419 407
420--- Describe a path in a cross-platform way.
421-- Use this function to avoid platform-specific directory
422-- separators in other modules. If the first item contains a
423-- protocol descriptor (e.g. "http:"), paths are always constituted
424-- with forward slashes.
425-- @param ... strings representing directories
426-- @return string: a string with a platform-specific representation
427-- of the path.
428function make_path(...)
429 local items = {...}
430 local i = 1
431 while items[i] do
432 if items[i] == "" then
433 table.remove(items, i)
434 else
435 i = i + 1
436 end
437 end
438 return table.concat(items, "/")
439end
440
441--- Split protocol and path from an URL or local pathname. 408--- Split protocol and path from an URL or local pathname.
442-- URLs should be in the "protocol://path" format. 409-- URLs should be in the "protocol://path" format.
443-- For local pathnames, "file" is returned as the protocol. 410-- For local pathnames, "file" is returned as the protocol.
@@ -466,7 +433,7 @@ function wrap_script(file, dest)
466 assert(type(file) == "string") 433 assert(type(file) == "string")
467 assert(type(dest) == "string") 434 assert(type(dest) == "string")
468 435
469 local base = fs_base_name(file) 436 local base = dir.base_name(file)
470 local wrapname = dest.."/"..base 437 local wrapname = dest.."/"..base
471 local wrapper = io.open(wrapname, "w") 438 local wrapper = io.open(wrapname, "w")
472 if not wrapper then 439 if not wrapper then
@@ -476,7 +443,7 @@ function wrap_script(file, dest)
476 wrapper:write('LUA_PATH="'..package.path..';$LUA_PATH"\n') 443 wrapper:write('LUA_PATH="'..package.path..';$LUA_PATH"\n')
477 wrapper:write('LUA_CPATH="'..package.cpath..';$LUA_CPATH"\n') 444 wrapper:write('LUA_CPATH="'..package.cpath..';$LUA_CPATH"\n')
478 wrapper:write('export LUA_PATH LUA_CPATH\n') 445 wrapper:write('export LUA_PATH LUA_CPATH\n')
479 wrapper:write('exec "'..fs_make_path(cfg.variables["LUA_BINDIR"], cfg.lua_interpreter)..'" -lluarocks.require "'..file..'" "$@"\n') 446 wrapper:write('exec "'..dir.path(cfg.variables["LUA_BINDIR"], cfg.lua_interpreter)..'" -lluarocks.require "'..file..'" "$@"\n')
480 wrapper:close() 447 wrapper:close()
481 if fs_execute("chmod +x",wrapname) then 448 if fs_execute("chmod +x",wrapname) then
482 return true 449 return true
diff --git a/src/luarocks/fs/win32.lua b/src/luarocks/fs/win32.lua
index 5702a3aa..cafdca7d 100644
--- a/src/luarocks/fs/win32.lua
+++ b/src/luarocks/fs/win32.lua
@@ -4,24 +4,21 @@
4module("luarocks.fs.win32", package.seeall) 4module("luarocks.fs.win32", package.seeall)
5 5
6local cfg = require("luarocks.cfg") 6local cfg = require("luarocks.cfg")
7local dir = require("luarocks.dir")
7 8
8local fs_base_name, 9local fs_copy,
9 fs_copy,
10 fs_current_dir, 10 fs_current_dir,
11 fs_execute, 11 fs_execute,
12 fs_execute_string, 12 fs_execute_string,
13 fs_is_dir, 13 fs_is_dir,
14 fs_make_path,
15 fs_Q 14 fs_Q
16 15
17function init_fs_functions(impl) 16function init_fs_functions(impl)
18 fs_base_name = impl.base_name
19 fs_copy = impl.copy 17 fs_copy = impl.copy
20 fs_current_dir = impl.current_dir 18 fs_current_dir = impl.current_dir
21 fs_execute = impl.execute 19 fs_execute = impl.execute
22 fs_execute_string = impl.execute_string 20 fs_execute_string = impl.execute_string
23 fs_is_dir = impl.is_dir 21 fs_is_dir = impl.is_dir
24 fs_make_path = impl.make_path
25 fs_Q = impl.Q 22 fs_Q = impl.Q
26end 23end
27 24
@@ -39,9 +36,9 @@ function Q(arg)
39 return '"' .. arg:gsub('"', '\\"') .. '"' 36 return '"' .. arg:gsub('"', '\\"') .. '"'
40end 37end
41 38
42local function command_at(dir, cmd) 39local function command_at(directory, cmd)
43 local drive = dir:match("^([A-Za-z]:)") 40 local drive = directory:match("^([A-Za-z]:)")
44 cmd = "cd " .. fs_Q(dir) .. " & " .. cmd 41 cmd = "cd " .. fs_Q(directory) .. " & " .. cmd
45 if drive then 42 if drive then
46 cmd = drive .. " & " .. cmd 43 cmd = drive .. " & " .. cmd
47 end 44 end
@@ -78,6 +75,14 @@ function is_dir(file)
78 return fs_execute("chdir /D " .. fs_Q(file) .. " 2>NUL 1>NUL") 75 return fs_execute("chdir /D " .. fs_Q(file) .. " 2>NUL 1>NUL")
79end 76end
80 77
78--- Test is pathname is a regular file.
79-- @param file string: pathname to test
80-- @return boolean: true if it is a regular file, false otherwise.
81function is_dir(file)
82 assert(file)
83 return fs_execute("test -d" .. fs_Q(file) .. " 2>NUL 1>NUL")
84end
85
81--- Test is file/dir is writable. 86--- Test is file/dir is writable.
82-- @param file string: filename to test 87-- @param file string: filename to test
83-- @return boolean: true if file exists, false otherwise. 88-- @return boolean: true if file exists, false otherwise.
@@ -102,21 +107,21 @@ end
102--- Create a directory if it does not already exist. 107--- Create a directory if it does not already exist.
103-- If any of the higher levels in the path name does not exist 108-- If any of the higher levels in the path name does not exist
104-- too, they are created as well. 109-- too, they are created as well.
105-- @param dir string: pathname of directory to create. 110-- @param d string: pathname of directory to create.
106-- @return boolean: true on success, false on failure. 111-- @return boolean: true on success, false on failure.
107function make_dir(dir) 112function make_dir(d)
108 assert(dir) 113 assert(d)
109 fs_execute("mkdir "..fs_Q(dir).." 1> NUL 2> NUL") 114 fs_execute("mkdir "..fs_Q(d).." 1> NUL 2> NUL")
110 return 1 115 return 1
111end 116end
112 117
113--- Remove a directory if it is empty. 118--- Remove a directory if it is empty.
114-- Does not return errors (for example, if directory is not empty or 119-- Does not return errors (for example, if directory is not empty or
115-- if already does not exist) 120-- if already does not exist)
116-- @param dir string: pathname of directory to remove. 121-- @param d string: pathname of directory to remove.
117function remove_dir_if_empty(dir) 122function remove_dir_if_empty(d)
118 assert(dir) 123 assert(d)
119 fs_execute_string("rmdir "..fs_Q(dir).." 1> NUL 2> NUL") 124 fs_execute_string("rmdir "..fs_Q(d).." 1> NUL 2> NUL")
120end 125end
121 126
122--- Copy a file. 127--- Copy a file.
@@ -164,7 +169,7 @@ end
164-- directory if none is given). 169-- directory if none is given).
165-- @return table: an array of strings with the filenames representing 170-- @return table: an array of strings with the filenames representing
166-- the contents of a directory. 171-- the contents of a directory.
167function dir(at) 172function list_dir(at)
168 assert(type(at) == "string" or not at) 173 assert(type(at) == "string" or not at)
169 if not at then 174 if not at then
170 at = fs_current_dir() 175 at = fs_current_dir()
@@ -226,16 +231,6 @@ function download(url, filename)
226 end 231 end
227end 232end
228 233
229--- Strip the path off a path+filename.
230-- @param pathname string: A path+name, such as "/a/b/c".
231-- @return string: The filename without its path, such as "c".
232function base_name(pathname)
233 assert(type(pathname) == "string")
234
235 local base = pathname:match(".*[/\\]([^/\\]*)")
236 return base or pathname
237end
238
239--- Strip the last extension of a filename. 234--- Strip the last extension of a filename.
240-- Example: "foo.tar.gz" becomes "foo.tar". 235-- Example: "foo.tar.gz" becomes "foo.tar".
241-- If filename has no dots, returns it unchanged. 236-- If filename has no dots, returns it unchanged.
@@ -323,7 +318,7 @@ function wrap_script(file, dest)
323 assert(type(file) == "string") 318 assert(type(file) == "string")
324 assert(type(dest) == "string") 319 assert(type(dest) == "string")
325 320
326 local base = fs_base_name(file) 321 local base = dir.base_name(file)
327 local wrapname = dest.."/"..base..".bat" 322 local wrapname = dest.."/"..base..".bat"
328 local wrapper = io.open(wrapname, "w") 323 local wrapper = io.open(wrapname, "w")
329 if not wrapper then 324 if not wrapper then
@@ -333,7 +328,7 @@ function wrap_script(file, dest)
333 wrapper:write("setlocal\n") 328 wrapper:write("setlocal\n")
334 wrapper:write('set LUA_PATH='..package.path..";%LUA_PATH%\n") 329 wrapper:write('set LUA_PATH='..package.path..";%LUA_PATH%\n")
335 wrapper:write('set LUA_CPATH='..package.cpath..";%LUA_CPATH%\n") 330 wrapper:write('set LUA_CPATH='..package.cpath..";%LUA_CPATH%\n")
336 wrapper:write('"'..fs_make_path(cfg.variables["LUA_BINDIR"], cfg.lua_interpreter)..'" -lluarocks.require "'..file..'" %*\n') 331 wrapper:write('"'..dir.path(cfg.variables["LUA_BINDIR"], cfg.lua_interpreter)..'" -lluarocks.require "'..file..'" %*\n')
337 wrapper:write("endlocal\n") 332 wrapper:write("endlocal\n")
338 wrapper:close() 333 wrapper:close()
339 return true 334 return true
@@ -353,7 +348,7 @@ function copy_binary(filename, dest)
353 return nil, err 348 return nil, err
354 end 349 end
355 local exe_pattern = "%.[Ee][Xx][Ee]$" 350 local exe_pattern = "%.[Ee][Xx][Ee]$"
356 local base = fs_base_name(filename) 351 local base = dir.base_name(filename)
357 if base:match(exe_pattern) then 352 if base:match(exe_pattern) then
358 base = base:gsub(exe_pattern, ".lua") 353 base = base:gsub(exe_pattern, ".lua")
359 local helpname = dest.."/"..base 354 local helpname = dest.."/"..base
diff --git a/src/luarocks/install.lua b/src/luarocks/install.lua
index b115a8a8..3ef32c99 100644
--- a/src/luarocks/install.lua
+++ b/src/luarocks/install.lua
@@ -23,15 +23,17 @@ or a filename of a locally available rock.
23 23
24--- Install a binary rock. 24--- Install a binary rock.
25-- @param rock_file string: local or remote filename of a rock. 25-- @param rock_file string: local or remote filename of a rock.
26-- @return boolean or (nil, string): True if succeeded or 26-- @return boolean or (nil, string, [string]): True if succeeded or
27-- nil and an error message. 27-- nil and an error message and an optional error code.
28function install_binary_rock(rock_file) 28function install_binary_rock(rock_file)
29 assert(type(rock_file) == "string")
30
29 local name, version, arch = path.parse_rock_name(rock_file) 31 local name, version, arch = path.parse_rock_name(rock_file)
30 if not name then 32 if not name then
31 return nil, "Filename "..rock_file.." does not match format 'name-version-revision.arch.rock'." 33 return nil, "Filename "..rock_file.." does not match format 'name-version-revision.arch.rock'."
32 end 34 end
33 if arch ~= "all" and arch ~= cfg.arch then 35 if arch ~= "all" and arch ~= cfg.arch then
34 return nil, "Incompatible architecture "..arch 36 return nil, "Incompatible architecture "..arch, "arch"
35 end 37 end
36 if rep.is_installed(name, version) then 38 if rep.is_installed(name, version) then
37 rep.delete_version(name, version) 39 rep.delete_version(name, version)
@@ -40,23 +42,23 @@ function install_binary_rock(rock_file)
40 fs.delete(path.install_dir(name, version)) 42 fs.delete(path.install_dir(name, version))
41 fs.remove_dir_if_empty(path.versions_dir(name)) 43 fs.remove_dir_if_empty(path.versions_dir(name))
42 end) 44 end)
43 local ok, err = fetch.fetch_and_unpack_rock(rock_file, path.install_dir(name, version)) 45 local ok, err, errcode = fetch.fetch_and_unpack_rock(rock_file, path.install_dir(name, version))
44 if not ok then return nil, err end 46 if not ok then return nil, err, errcode end
45 ok, err = rep.install_bins(name, version) 47 ok, err = rep.install_bins(name, version)
46 48
47 local rockspec, err = fetch.load_rockspec(path.rockspec_file(name, version)) 49 local rockspec, err, errcode = fetch.load_rockspec(path.rockspec_file(name, version))
48 if err then 50 if err then
49 return nil, "Failed loading rockspec for installed package: "..err 51 return nil, "Failed loading rockspec for installed package: "..err, errcode
50 end 52 end
51 53
52 ok, err = deps.check_external_deps(rockspec, "install") 54 ok, err, errcode = deps.check_external_deps(rockspec, "install")
53 if err then 55 if err then
54 return nil, err 56 return nil, err, errcode
55 end 57 end
56 58
57 ok, err = deps.fulfill_dependencies(rockspec) 59 ok, err, errcode = deps.fulfill_dependencies(rockspec)
58 if err then 60 if err then
59 return nil, err 61 return nil, err, errcode
60 end 62 end
61 63
62 ok, err = rep.run_hook(rockspec, "post_install") 64 ok, err = rep.run_hook(rockspec, "post_install")
diff --git a/src/luarocks/list.lua b/src/luarocks/list.lua
index 1251653c..f918640c 100644
--- a/src/luarocks/list.lua
+++ b/src/luarocks/list.lua
@@ -6,7 +6,7 @@ module("luarocks.list", package.seeall)
6local search = require("luarocks.search") 6local search = require("luarocks.search")
7local cfg = require("luarocks.cfg") 7local cfg = require("luarocks.cfg")
8local util = require("luarocks.util") 8local util = require("luarocks.util")
9local fs = require("luarocks.fs") 9local dir = require("luarocks.dir")
10 10
11help_summary = "Lists currently installed rocks." 11help_summary = "Lists currently installed rocks."
12 12
@@ -24,7 +24,7 @@ function run(...)
24 local query = search.make_query(filter or "", version) 24 local query = search.make_query(filter or "", version)
25 query.exact_name = false 25 query.exact_name = false
26 for _, tree in ipairs(cfg.rocks_trees) do 26 for _, tree in ipairs(cfg.rocks_trees) do
27 search.manifest_search(results, fs.make_path(tree, "rocks"), query) 27 search.manifest_search(results, dir.path(tree, "rocks"), query)
28 end 28 end
29 print() 29 print()
30 print("Installed rocks:") 30 print("Installed rocks:")
diff --git a/src/luarocks/make.lua b/src/luarocks/make.lua
index 1c116cdb..1365cf13 100644
--- a/src/luarocks/make.lua
+++ b/src/luarocks/make.lua
@@ -32,7 +32,7 @@ function run(...)
32 assert(type(rockspec) == "string" or not rockspec) 32 assert(type(rockspec) == "string" or not rockspec)
33 33
34 if not rockspec then 34 if not rockspec then
35 local files = fs.dir(fs.current_dir()) 35 local files = fs.list_dir(fs.current_dir())
36 for _, file in pairs(files) do 36 for _, file in pairs(files) do
37 if file:match(".rockspec$") then 37 if file:match(".rockspec$") then
38 if rockspec then 38 if rockspec then
diff --git a/src/luarocks/manif.lua b/src/luarocks/manif.lua
index 98772319..3e5790ae 100644
--- a/src/luarocks/manif.lua
+++ b/src/luarocks/manif.lua
@@ -10,98 +10,35 @@ local deps = require("luarocks.deps")
10local cfg = require("luarocks.cfg") 10local cfg = require("luarocks.cfg")
11local persist = require("luarocks.persist") 11local persist = require("luarocks.persist")
12local fetch = require("luarocks.fetch") 12local fetch = require("luarocks.fetch")
13local type_check = require("luarocks.type_check") 13local dir = require("luarocks.dir")
14 14local manif_core = require("luarocks.manif_core")
15manifest_cache = {}
16
17--- Get all versions of a package listed in a manifest file.
18-- @param name string: a package name.
19-- @param manifest table or nil: a manifest table; if not given, the
20-- default local manifest table is used.
21-- @return table: An array of strings listing installed
22-- versions of a package.
23function get_versions(name, manifest)
24 assert(type(name) == "string")
25 assert(type(manifest) == "table" or not manifest)
26
27 if not manifest then
28 manifest = load_local_manifest(cfg.rocks_dir)
29 if not manifest then
30 return {}
31 end
32 end
33
34 local item = manifest.repository[name]
35 if item then
36 return util.keys(item)
37 end
38 return {}
39end
40
41--- Back-end function that actually loads the manifest
42-- and stores it in the manifest cache.
43-- @param file string: The local filename of the manifest file.
44-- @param repo_url string: The repository identifier.
45local function manifest_loader(file, repo_url, quick)
46 local manifest = persist.load_into_table(file)
47 if not manifest then
48 return nil, "Failed loading manifest for "..repo_url
49 end
50 if not quick then
51 local ok, err = type_check.type_check_manifest(manifest)
52 if not ok then
53 return nil, "Error checking manifest: "..err
54 end
55 end
56
57 manifest_cache[repo_url] = manifest
58 return manifest
59end
60 15
61--- Load a local or remote manifest describing a repository. 16--- Load a local or remote manifest describing a repository.
62-- All functions that use manifest tables assume they were obtained 17-- All functions that use manifest tables assume they were obtained
63-- through either this function or load_local_manifest. 18-- through either this function or load_local_manifest.
64-- @param repo_url string: URL or pathname for the repository. 19-- @param repo_url string: URL or pathname for the repository.
65-- @return table or (nil, string): A table representing the manifest, 20-- @return table or (nil, string, [string]): A table representing the manifest,
66-- or nil followed by an error message. 21-- or nil followed by an error message and an optional error code.
67function load_manifest(repo_url) 22function load_manifest(repo_url)
68 assert(type(repo_url) == "string") 23 assert(type(repo_url) == "string")
69 24
70 if manifest_cache[repo_url] then 25 if manif_core.manifest_cache[repo_url] then
71 return manifest_cache[repo_url] 26 return manif_core.manifest_cache[repo_url]
72 end 27 end
73 28
74 local protocol, pathname = fs.split_url(repo_url) 29 local protocol, pathname = fs.split_url(repo_url)
75 if protocol == "file" then 30 if protocol == "file" then
76 pathname = fs.make_path(pathname, "manifest") 31 pathname = dir.path(pathname, "manifest")
77 else 32 else
78 local url = fs.make_path(repo_url, "manifest") 33 local url = dir.path(repo_url, "manifest")
79 local name = repo_url:gsub("[/:]","_") 34 local name = repo_url:gsub("[/:]","_")
80 local file, dir = fetch.fetch_url_at_temp_dir(url, "luarocks-manifest-"..name) 35 local file, err, errcode = fetch.fetch_url_at_temp_dir(url, "luarocks-manifest-"..name)
81 if not file then 36 if not file then
82 return nil, "Failed fetching manifest for "..repo_url 37 return nil, "Failed fetching manifest for "..repo_url, errcode
83 end 38 end
84 pathname = file 39 pathname = file
85 end 40 end
86 return manifest_loader(pathname, repo_url) 41 return manif_core.manifest_loader(pathname, repo_url)
87end
88
89--- Load a local manifest describing a repository.
90-- All functions that use manifest tables assume they were obtained
91-- through either this function or load_manifest.
92-- @param repo_url string: URL or pathname for the repository.
93-- @return table or (nil, string): A table representing the manifest,
94-- or nil followed by an error message.
95function load_local_manifest(repo_url)
96 assert(type(repo_url) == "string")
97
98 if manifest_cache[repo_url] then
99 return manifest_cache[repo_url]
100 end
101
102 local pathname = fs.make_path(repo_url, "manifest")
103
104 return manifest_loader(pathname, repo_url, true)
105end 42end
106 43
107--- Sort function for ordering rock identifiers in a manifest's 44--- Sort function for ordering rock identifiers in a manifest's
@@ -183,7 +120,7 @@ local function save_manifest(repo, manifest)
183 assert(type(repo) == "string") 120 assert(type(repo) == "string")
184 assert(type(manifest) == "table") 121 assert(type(manifest) == "table")
185 122
186 local filename = fs.make_path(repo, "manifest") 123 local filename = dir.path(repo, "manifest")
187 return persist.save_from_table(filename, manifest) 124 return persist.save_from_table(filename, manifest)
188end 125end
189 126
@@ -302,7 +239,7 @@ function make_manifest(repo)
302 local results = search.disk_search(repo, query) 239 local results = search.disk_search(repo, query)
303 240
304 local manifest = { repository = {}, modules = {}, commands = {} } 241 local manifest = { repository = {}, modules = {}, commands = {} }
305 manifest_cache[repo] = manifest 242 manif_core.manifest_cache[repo] = manifest
306 store_results(results, manifest) 243 store_results(results, manifest)
307 return save_manifest(repo, manifest) 244 return save_manifest(repo, manifest)
308end 245end
@@ -388,7 +325,7 @@ function make_index(repo)
388 end 325 end
389 local manifest = load_manifest(repo) 326 local manifest = load_manifest(repo)
390 files = fs.find(repo) 327 files = fs.find(repo)
391 local out = io.open(fs.make_path(repo, "index.html"), "w") 328 local out = io.open(dir.path(repo, "index.html"), "w")
392 out:write(index_header) 329 out:write(index_header)
393 for package, version_list in util.sortedpairs(manifest.repository) do 330 for package, version_list in util.sortedpairs(manifest.repository) do
394 local latest_rockspec = nil 331 local latest_rockspec = nil
@@ -414,7 +351,7 @@ function make_index(repo)
414 end 351 end
415 output = output .. index_package_end 352 output = output .. index_package_end
416 if latest_rockspec then 353 if latest_rockspec then
417 local rockspec = persist.load_into_table(fs.make_path(repo, latest_rockspec)) 354 local rockspec = persist.load_into_table(dir.path(repo, latest_rockspec))
418 local vars = { 355 local vars = {
419 anchor = package, 356 anchor = package,
420 package = rockspec.package, 357 package = rockspec.package,
diff --git a/src/luarocks/manif_core.lua b/src/luarocks/manif_core.lua
new file mode 100644
index 00000000..941c569d
--- /dev/null
+++ b/src/luarocks/manif_core.lua
@@ -0,0 +1,73 @@
1
2--- Core functions for querying manifest files.
3module("luarocks.manif_core", package.seeall)
4
5local persist = require("luarocks.persist")
6local type_check = require("luarocks.type_check")
7local dir = require("luarocks.dir")
8local util = require("luarocks.util")
9local cfg = require("luarocks.cfg")
10
11manifest_cache = {}
12
13--- Back-end function that actually loads the manifest
14-- and stores it in the manifest cache.
15-- @param file string: The local filename of the manifest file.
16-- @param repo_url string: The repository identifier.
17function manifest_loader(file, repo_url, quick)
18 local manifest = persist.load_into_table(file)
19 if not manifest then
20 return nil, "Failed loading manifest for "..repo_url
21 end
22 if not quick then
23 local ok, err = type_check.type_check_manifest(manifest)
24 if not ok then
25 return nil, "Error checking manifest: "..err
26 end
27 end
28
29 manifest_cache[repo_url] = manifest
30 return manifest
31end
32
33--- Load a local manifest describing a repository.
34-- All functions that use manifest tables assume they were obtained
35-- through either this function or load_manifest.
36-- @param repo_url string: URL or pathname for the repository.
37-- @return table or (nil, string): A table representing the manifest,
38-- or nil followed by an error message.
39function load_local_manifest(repo_url)
40 assert(type(repo_url) == "string")
41
42 if manifest_cache[repo_url] then
43 return manifest_cache[repo_url]
44 end
45
46 local pathname = dir.path(repo_url, "manifest")
47
48 return manifest_loader(pathname, repo_url, true)
49end
50
51--- Get all versions of a package listed in a manifest file.
52-- @param name string: a package name.
53-- @param manifest table or nil: a manifest table; if not given, the
54-- default local manifest table is used.
55-- @return table: An array of strings listing installed
56-- versions of a package.
57function get_versions(name, manifest)
58 assert(type(name) == "string")
59 assert(type(manifest) == "table" or not manifest)
60
61 if not manifest then
62 manifest = load_local_manifest(cfg.rocks_dir)
63 if not manifest then
64 return {}
65 end
66 end
67
68 local item = manifest.repository[name]
69 if item then
70 return util.keys(item)
71 end
72 return {}
73end
diff --git a/src/luarocks/pack.lua b/src/luarocks/pack.lua
index 3f64b1c0..7b1c68dd 100644
--- a/src/luarocks/pack.lua
+++ b/src/luarocks/pack.lua
@@ -9,6 +9,7 @@ local fetch = require("luarocks.fetch")
9local fs = require("luarocks.fs") 9local fs = require("luarocks.fs")
10local cfg = require("luarocks.cfg") 10local cfg = require("luarocks.cfg")
11local util = require("luarocks.util") 11local util = require("luarocks.util")
12local dir = require("luarocks.dir")
12 13
13help_summary = "Create a rock, packing sources or binaries." 14help_summary = "Create a rock, packing sources or binaries."
14help_arguments = "{<rockspec>|<name> [<version>]}" 15help_arguments = "{<rockspec>|<name> [<version>]}"
@@ -38,15 +39,15 @@ local function pack_source_rock(rockspec_file)
38 local name_version = rockspec.name .. "-" .. rockspec.version 39 local name_version = rockspec.name .. "-" .. rockspec.version
39 local rock_file = fs.absolute_name(name_version .. ".src.rock") 40 local rock_file = fs.absolute_name(name_version .. ".src.rock")
40 41
41 local source_file, dir = fetch.fetch_sources(rockspec, false) 42 local source_file, source_dir = fetch.fetch_sources(rockspec, false)
42 if not source_file then 43 if not source_file then
43 return nil, dir 44 return nil, source_dir
44 end 45 end
45 fs.change_dir(dir) 46 fs.change_dir(source_dir)
46 47
47 fs.delete(rock_file) 48 fs.delete(rock_file)
48 fs.copy(rockspec_file, dir) 49 fs.copy(rockspec_file, source_dir)
49 if not fs.zip(rock_file, fs.base_name(rockspec_file), fs.base_name(source_file)) then 50 if not fs.zip(rock_file, dir.base_name(rockspec_file), dir.base_name(source_file)) then
50 return nil, "Failed packing "..rock_file 51 return nil, "Failed packing "..rock_file
51 end 52 end
52 fs.pop_dir() 53 fs.pop_dir()
@@ -87,7 +88,7 @@ local function pack_binary_rock(name, version)
87 rock_file = rock_file:gsub("%."..cfg.arch:gsub("%-","%%-").."%.", ".all.") 88 rock_file = rock_file:gsub("%."..cfg.arch:gsub("%-","%%-").."%.", ".all.")
88 end 89 end
89 fs.delete(rock_file) 90 fs.delete(rock_file)
90 if not fs.zip(rock_file, unpack(fs.dir())) then 91 if not fs.zip(rock_file, unpack(fs.list_dir())) then
91 return nil, "Failed packing "..rock_file 92 return nil, "Failed packing "..rock_file
92 end 93 end
93 fs.pop_dir() 94 fs.pop_dir()
diff --git a/src/luarocks/path.lua b/src/luarocks/path.lua
index e236db01..ae9c7fae 100644
--- a/src/luarocks/path.lua
+++ b/src/luarocks/path.lua
@@ -4,7 +4,7 @@
4-- point where the layout of the local installation is defined in LuaRocks. 4-- point where the layout of the local installation is defined in LuaRocks.
5module("luarocks.path", package.seeall) 5module("luarocks.path", package.seeall)
6 6
7local fs = require("luarocks.fs") 7local dir = require("luarocks.dir")
8local cfg = require("luarocks.cfg") 8local cfg = require("luarocks.cfg")
9 9
10--- Infer rockspec filename from a rock filename. 10--- Infer rockspec filename from a rock filename.
@@ -12,7 +12,7 @@ local cfg = require("luarocks.cfg")
12-- @return string: Filename of the rockspec, without path. 12-- @return string: Filename of the rockspec, without path.
13function rockspec_name_from_rock(rock_name) 13function rockspec_name_from_rock(rock_name)
14 assert(type(rock_name) == "string") 14 assert(type(rock_name) == "string")
15 local base_name = fs.base_name(rock_name) 15 local base_name = dir.base_name(rock_name)
16 return base_name:match("(.*)%.[^.]*.rock") .. ".rockspec" 16 return base_name:match("(.*)%.[^.]*.rock") .. ".rockspec"
17end 17end
18 18
@@ -25,7 +25,7 @@ function versions_dir(name, repo)
25 assert(type(name) == "string") 25 assert(type(name) == "string")
26 assert(not repo or type(repo) == "string") 26 assert(not repo or type(repo) == "string")
27 27
28 return fs.make_path(repo or cfg.rocks_dir, name) 28 return dir.path(repo or cfg.rocks_dir, name)
29end 29end
30 30
31--- Get the local installation directory (prefix) for a package. 31--- Get the local installation directory (prefix) for a package.
@@ -39,7 +39,7 @@ function install_dir(name, version, repo)
39 assert(type(version) == "string") 39 assert(type(version) == "string")
40 assert(not repo or type(repo) == "string") 40 assert(not repo or type(repo) == "string")
41 41
42 return fs.make_path(repo or cfg.rocks_dir, name, version) 42 return dir.path(repo or cfg.rocks_dir, name, version)
43end 43end
44 44
45--- Get the local filename of the rockspec of an installed rock. 45--- Get the local filename of the rockspec of an installed rock.
@@ -53,7 +53,7 @@ function rockspec_file(name, version, repo)
53 assert(type(version) == "string") 53 assert(type(version) == "string")
54 assert(not repo or type(repo) == "string") 54 assert(not repo or type(repo) == "string")
55 55
56 return fs.make_path(repo or cfg.rocks_dir, name, version, name.."-"..version..".rockspec") 56 return dir.path(repo or cfg.rocks_dir, name, version, name.."-"..version..".rockspec")
57end 57end
58 58
59--- Get the local installation directory for C libraries of a package. 59--- Get the local installation directory for C libraries of a package.
@@ -67,7 +67,7 @@ function lib_dir(name, version, repo)
67 assert(type(version) == "string") 67 assert(type(version) == "string")
68 assert(not repo or type(repo) == "string") 68 assert(not repo or type(repo) == "string")
69 69
70 return fs.make_path(repo or cfg.rocks_dir, name, version, "lib") 70 return dir.path(repo or cfg.rocks_dir, name, version, "lib")
71end 71end
72 72
73--- Get the local installation directory for Lua modules of a package. 73--- Get the local installation directory for Lua modules of a package.
@@ -81,7 +81,7 @@ function lua_dir(name, version, repo)
81 assert(type(version) == "string") 81 assert(type(version) == "string")
82 assert(not repo or type(repo) == "string") 82 assert(not repo or type(repo) == "string")
83 83
84 return fs.make_path(repo or cfg.rocks_dir, name, version, "lua") 84 return dir.path(repo or cfg.rocks_dir, name, version, "lua")
85end 85end
86 86
87--- Get the local installation directory for documentation of a package. 87--- Get the local installation directory for documentation of a package.
@@ -95,7 +95,7 @@ function doc_dir(name, version, repo)
95 assert(type(version) == "string") 95 assert(type(version) == "string")
96 assert(not repo or type(repo) == "string") 96 assert(not repo or type(repo) == "string")
97 97
98 return fs.make_path(repo or cfg.rocks_dir, name, version, "doc") 98 return dir.path(repo or cfg.rocks_dir, name, version, "doc")
99end 99end
100 100
101--- Get the local installation directory for configuration files of a package. 101--- Get the local installation directory for configuration files of a package.
@@ -109,7 +109,7 @@ function conf_dir(name, version, repo)
109 assert(type(version) == "string") 109 assert(type(version) == "string")
110 assert(not repo or type(repo) == "string") 110 assert(not repo or type(repo) == "string")
111 111
112 return fs.make_path(repo or cfg.rocks_dir, name, version, "conf") 112 return dir.path(repo or cfg.rocks_dir, name, version, "conf")
113end 113end
114 114
115--- Get the local installation directory for command-line scripts 115--- Get the local installation directory for command-line scripts
@@ -124,7 +124,7 @@ function bin_dir(name, version, repo)
124 assert(type(version) == "string") 124 assert(type(version) == "string")
125 assert(not repo or type(repo) == "string") 125 assert(not repo or type(repo) == "string")
126 126
127 return fs.make_path(repo or cfg.rocks_dir, name, version, "bin") 127 return dir.path(repo or cfg.rocks_dir, name, version, "bin")
128end 128end
129 129
130--- Extract name, version and arch of a rock filename. 130--- Extract name, version and arch of a rock filename.
@@ -133,7 +133,7 @@ end
133-- of rock, or nil if name could not be parsed 133-- of rock, or nil if name could not be parsed
134function parse_rock_name(rock_file) 134function parse_rock_name(rock_file)
135 assert(type(rock_file) == "string") 135 assert(type(rock_file) == "string")
136 return fs.base_name(rock_file):match("(.*)-([^-]+-%d+)%.([^.]+)%.rock$") 136 return dir.base_name(rock_file):match("(.*)-([^-]+-%d+)%.([^.]+)%.rock$")
137end 137end
138 138
139--- Extract name and version of a rockspec filename. 139--- Extract name and version of a rockspec filename.
@@ -142,7 +142,7 @@ end
142-- of rockspec, or nil if name could not be parsed 142-- of rockspec, or nil if name could not be parsed
143function parse_rockspec_name(rockspec_file) 143function parse_rockspec_name(rockspec_file)
144 assert(type(rockspec_file) == "string") 144 assert(type(rockspec_file) == "string")
145 return fs.base_name(rockspec_file):match("(.*)-([^-]+-%d+)%.(rockspec)") 145 return dir.base_name(rockspec_file):match("(.*)-([^-]+-%d+)%.(rockspec)")
146end 146end
147 147
148--- Make a rockspec or rock URL. 148--- Make a rockspec or rock URL.
@@ -159,13 +159,13 @@ function make_url(pathname, name, version, arch)
159 159
160 local filename = name.."-"..version 160 local filename = name.."-"..version
161 if arch == "installed" then 161 if arch == "installed" then
162 filename = fs.make_path(name, version, filename..".rockspec") 162 filename = dir.path(name, version, filename..".rockspec")
163 elseif arch == "rockspec" then 163 elseif arch == "rockspec" then
164 filename = filename..".rockspec" 164 filename = filename..".rockspec"
165 else 165 else
166 filename = filename.."."..arch..".rock" 166 filename = filename.."."..arch..".rock"
167 end 167 end
168 return fs.make_path(pathname, filename) 168 return dir.path(pathname, filename)
169end 169end
170 170
171--- Convert a pathname to a module identifier. 171--- Convert a pathname to a module identifier.
@@ -180,7 +180,7 @@ function path_to_module(file)
180 180
181 local name = file:match("(.*)%."..cfg.lua_extension.."$") 181 local name = file:match("(.*)%."..cfg.lua_extension.."$")
182 if name then 182 if name then
183 name = name:gsub(fs.dir_separator, ".") 183 name = name:gsub(dir.separator, ".")
184 local init = name:match("(.*)%.init$") 184 local init = name:match("(.*)%.init$")
185 if init then 185 if init then
186 name = init 186 name = init
@@ -188,7 +188,7 @@ function path_to_module(file)
188 else 188 else
189 name = file:match("(.*)%."..cfg.lib_extension.."$") 189 name = file:match("(.*)%."..cfg.lib_extension.."$")
190 if name then 190 if name then
191 name = name:gsub(fs.dir_separator, ".") 191 name = name:gsub(dir.separator, ".")
192 end 192 end
193 end 193 end
194 return name 194 return name
@@ -200,7 +200,7 @@ end
200-- @return string: A directory name using the platform's separator. 200-- @return string: A directory name using the platform's separator.
201function module_to_path(mod) 201function module_to_path(mod)
202 assert(type(mod) == "string") 202 assert(type(mod) == "string")
203 return (mod:gsub("[^.]*$", ""):gsub("%.", fs.dir_separator)) 203 return (mod:gsub("[^.]*$", ""):gsub("%.", dir.separator))
204end 204end
205 205
206--- Set up path-related variables for a given rock. 206--- Set up path-related variables for a given rock.
diff --git a/src/luarocks/rep.lua b/src/luarocks/rep.lua
index b5797803..304832f1 100644
--- a/src/luarocks/rep.lua
+++ b/src/luarocks/rep.lua
@@ -6,6 +6,7 @@ local fs = require("luarocks.fs")
6local path = require("luarocks.path") 6local path = require("luarocks.path")
7local cfg = require("luarocks.cfg") 7local cfg = require("luarocks.cfg")
8local util = require("luarocks.util") 8local util = require("luarocks.util")
9local dir = require("luarocks.dir")
9 10
10--- Get all installed versions of a package. 11--- Get all installed versions of a package.
11-- @param name string: a package name. 12-- @param name string: a package name.
@@ -14,7 +15,7 @@ local util = require("luarocks.util")
14function get_versions(name) 15function get_versions(name)
15 assert(type(name) == "string") 16 assert(type(name) == "string")
16 17
17 local dirs = fs.dir(path.versions_dir(name)) 18 local dirs = fs.list_dir(path.versions_dir(name))
18 return (dirs and #dirs > 0) and dirs or nil 19 return (dirs and #dirs > 0) and dirs or nil
19end 20end
20 21
@@ -41,7 +42,7 @@ function delete_version(name, version)
41 42
42 fs.delete(path.install_dir(name, version)) 43 fs.delete(path.install_dir(name, version))
43 if not get_versions(name) then 44 if not get_versions(name) then
44 fs.delete(fs.make_path(cfg.rocks_dir, name)) 45 fs.delete(dir.path(cfg.rocks_dir, name))
45 end 46 end
46end 47end
47 48
@@ -50,7 +51,7 @@ end
50function delete_bin(command) 51function delete_bin(command)
51 assert(type(command) == "string") 52 assert(type(command) == "string")
52 53
53 fs.delete(fs.make_path(cfg.scripts_dir, command)) 54 fs.delete(dir.path(cfg.scripts_dir, command))
54end 55end
55 56
56--- Install bin entries in the repository bin dir. 57--- Install bin entries in the repository bin dir.
@@ -71,9 +72,9 @@ function install_bins(name, version, single_file)
71 if not ok then 72 if not ok then
72 return nil, "Could not create "..cfg.scripts_dir 73 return nil, "Could not create "..cfg.scripts_dir
73 end 74 end
74 local files = single_file and {single_file} or fs.dir(bindir) 75 local files = single_file and {single_file} or fs.list_dir(bindir)
75 for _, file in pairs(files) do 76 for _, file in pairs(files) do
76 local fullname = fs.make_path(bindir, file) 77 local fullname = dir.path(bindir, file)
77 local match = file:match("%.lua$") 78 local match = file:match("%.lua$")
78 local file 79 local file
79 if not match then 80 if not match then
@@ -140,7 +141,7 @@ function package_commands(package, version)
140 local bins = fs.find(bindir) 141 local bins = fs.find(bindir)
141 for _, file in ipairs(bins) do 142 for _, file in ipairs(bins) do
142 if file then 143 if file then
143 result[file] = fs.make_path(bindir, file) 144 result[file] = dir.path(bindir, file)
144 end 145 end
145 end 146 end
146 return result 147 return result
@@ -159,7 +160,7 @@ function is_binary_rock(name, version)
159 end 160 end
160 if fs.exists(bin_dir) then 161 if fs.exists(bin_dir) then
161 for _, name in pairs(fs.find(bin_dir)) do 162 for _, name in pairs(fs.find(bin_dir)) do
162 if fs.is_actual_binary(fs.make_path(bin_dir, name)) then 163 if fs.is_actual_binary(dir.path(bin_dir, name)) then
163 return true 164 return true
164 end 165 end
165 end 166 end
diff --git a/src/luarocks/search.lua b/src/luarocks/search.lua
index a3ac68f3..eae3809f 100644
--- a/src/luarocks/search.lua
+++ b/src/luarocks/search.lua
@@ -4,6 +4,7 @@
4module("luarocks.search", package.seeall) 4module("luarocks.search", package.seeall)
5 5
6local fs = require("luarocks.fs") 6local fs = require("luarocks.fs")
7local dir = require("luarocks.dir")
7local path = require("luarocks.path") 8local path = require("luarocks.path")
8local manif = require("luarocks.manif") 9local manif = require("luarocks.manif")
9local deps = require("luarocks.deps") 10local deps = require("luarocks.deps")
@@ -133,14 +134,14 @@ function disk_search(repo, query, results)
133 end 134 end
134 query_arch_as_table(query) 135 query_arch_as_table(query)
135 136
136 for _, name in pairs(fs.dir(repo)) do 137 for _, name in pairs(fs.list_dir(repo)) do
137 local pathname = fs.make_path(repo, name) 138 local pathname = dir.path(repo, name)
138 local rname, rversion, rarch = path.parse_rock_name(name) 139 local rname, rversion, rarch = path.parse_rock_name(name)
139 if not rname then 140 if not rname then
140 rname, rversion, rarch = path.parse_rockspec_name(name) 141 rname, rversion, rarch = path.parse_rockspec_name(name)
141 end 142 end
142 if fs.is_dir(pathname) then 143 if fs.is_dir(pathname) then
143 for _, version in pairs(fs.dir(pathname)) do 144 for _, version in pairs(fs.list_dir(pathname)) do
144 if version:match("-%d+$") then 145 if version:match("-%d+$") then
145 store_if_match(results, repo, name, version, "installed", query) 146 store_if_match(results, repo, name, version, "installed", query)
146 end 147 end
diff --git a/src/luarocks/tools/patch.lua b/src/luarocks/tools/patch.lua
new file mode 100644
index 00000000..4fc5c3b5
--- /dev/null
+++ b/src/luarocks/tools/patch.lua
@@ -0,0 +1,708 @@
1-- patch.lua - Patch utility to apply unified diffs
2--
3-- http://lua-users.org/wiki/LuaPatch
4--
5-- (c) 2008 David Manura, Licensed under the same terms as Lua (MIT license).
6-- Code is heavilly based on the Python-based patch.py version 8.06-1
7-- Copyright (c) 2008 rainforce.org, MIT License
8-- Project home: http://code.google.com/p/python-patch/ .
9
10module("luarocks.tools.patch", package.seeall)
11
12local version = '0.1'
13
14local io = io
15local os = os
16local string = string
17local table = table
18local format = string.format
19
20-- logging
21local debugmode = false
22local function debug(s) end
23local function info(s) end
24local function warning(s) io.stderr:write(s .. '\n') end
25
26-- Returns boolean whether string s2 starts with string s.
27local function startswith(s, s2)
28 return s:sub(1, #s2) == s2
29end
30
31-- Returns boolean whether string s2 ends with string s.
32local function endswith(s, s2)
33 return #s >= #s2 and s:sub(#s-#s2+1) == s2
34end
35
36-- Returns string s after filtering out any new-line characters from end.
37local function endlstrip(s)
38 return s:gsub('[\r\n]+$', '')
39end
40
41-- Returns shallow copy of table t.
42local function table_copy(t)
43 local t2 = {}
44 for k,v in pairs(t) do t2[k] = v end
45 return t2
46end
47
48-- Returns boolean whether array t contains value v.
49local function array_contains(t, v)
50 for _,v2 in ipairs(t) do if v == v2 then return true end end
51 return false
52end
53
54local function exists(filename)
55 local fh = io.open(filename)
56 local result = fh ~= nil
57 if fh then fh:close() end
58 return result
59end
60local function isfile() return true end --FIX?
61
62local function read_file(filename)
63 local fh, err, oserr = io.open(filename, 'rb')
64 if not fh then return fh, err, oserr end
65 local data, err, oserr = fh:read'*a'
66 fh:close()
67 if not data then return nil, err, oserr end
68 return data
69end
70
71local function write_file(filename, data)
72 local fh, err, oserr = io.open(filename 'wb')
73 if not fh then return fh, err, oserr end
74 local status, err, oserr = fh:write(data)
75 fh:close()
76 if not status then return nil, err, oserr end
77 return true
78end
79
80local function file_copy(src, dest)
81 local data, err, oserr = read_file(src)
82 if not data then return data, err, oserr end
83 local status, err, oserr = write_file(dest)
84 if not status then return status, err, oserr end
85 return true
86end
87
88local function string_as_file(s)
89 return {
90 at = 0,
91 str = s,
92 len = #s,
93 eof = false,
94 read = function(self, n)
95 if self.eof then return nil end
96 local chunk = self.str:sub(at, at+n)
97 self.at = self.at + n
98 if at > self.len then
99 self.eof = true
100 end
101 end,
102 close = function(self)
103 self.eof = true
104 end,
105 }
106end
107
108--
109-- file_lines(f) is similar to f:lines() for file f.
110-- The main difference is that read_lines includes
111-- new-line character sequences ("\n", "\r\n", "\r"),
112-- if any, at the end of each line. Embedded "\0" are also handled.
113-- Caution: The newline behavior can depend on whether f is opened
114-- in binary or ASCII mode.
115-- (file_lines - version 20080913)
116--
117local function file_lines(f)
118 local CHUNK_SIZE = 1024
119 local buffer = ""
120 local pos_beg = 1
121 return function()
122 local pos, chars
123 while 1 do
124 pos, chars = buffer:match('()([\r\n].)', pos_beg)
125 if pos or not f then
126 break
127 elseif f then
128 local chunk = f:read(CHUNK_SIZE)
129 if chunk then
130 buffer = buffer:sub(pos_beg) .. chunk
131 pos_beg = 1
132 else
133 f = nil
134 end
135 end
136 end
137 if not pos then
138 pos = #buffer
139 elseif chars == '\r\n' then
140 pos = pos + 1
141 end
142 local line = buffer:sub(pos_beg, pos)
143 pos_beg = pos + 1
144 if #line > 0 then
145 return line
146 end
147 end
148end
149
150local function match_linerange(line)
151 local m1, m2, m3, m4 = line:match("^@@ %-(%d+),(%d+) %+(%d+),(%d+)")
152 if not m1 then m1, m3, m4 = line:match("^@@ %-(%d+) %+(%d+),(%d+)") end
153 if not m1 then m1, m2, m3 = line:match("^@@ %-(%d+),(%d+) %+(%d+)") end
154 if not m1 then m1, m3 = line:match("^@@ %-(%d+) %+(%d+)") end
155 return m1, m2, m3, m4
156end
157
158function read_patch(filename, data)
159 -- define possible file regions that will direct the parser flow
160 local state = 'header'
161 -- 'header' - comments before the patch body
162 -- 'filenames' - lines starting with --- and +++
163 -- 'hunkhead' - @@ -R +R @@ sequence
164 -- 'hunkbody'
165 -- 'hunkskip' - skipping invalid hunk mode
166
167 local all_ok = true
168 local lineends = {lf=0, crlf=0, cr=0}
169 local files = {source={}, target={}, hunks={}, fileends={}, hunkends={}}
170 local nextfileno = 0
171 local nexthunkno = 0 --: even if index starts with 0 user messages
172 -- number hunks from 1
173
174 -- hunkinfo holds parsed values, hunkactual - calculated
175 local hunkinfo = {
176 startsrc=nil, linessrc=nil, starttgt=nil, linestgt=nil,
177 invalid=false, text={}
178 }
179 local hunkactual = {linessrc=nil, linestgt=nil}
180
181 info(format("reading patch %s", filename))
182
183 local fp
184 if data then
185 fp = string_as_file(data)
186 else
187 fp = filename == '-' and io.stdin or assert(io.open(filename, "rb"))
188 end
189 local lineno = 0
190 for line in file_lines(fp) do
191 lineno = lineno + 1
192 if state == 'header' then
193 if startswith(line, "--- ") then
194 state = 'filenames'
195 end
196 -- state is 'header' or 'filenames'
197 end
198 if state == 'hunkbody' then
199 -- skip hunkskip and hunkbody code until definition of hunkhead read
200
201 -- process line first
202 if line:match"^[- +\\]" or line:match"^[\r\n]*$" then
203 -- gather stats about line endings
204 local he = files.hunkends[nextfileno]
205 if endswith(line, "\r\n") then
206 he.crlf = he.crlf + 1
207 elseif endswith(line, "\n") then
208 he.lf = he.lf + 1
209 elseif endswith(line, "\r") then
210 he.cr = he.cr + 1
211 end
212 if startswith(line, "-") then
213 hunkactual.linessrc = hunkactual.linessrc + 1
214 elseif startswith(line, "+") then
215 hunkactual.linestgt = hunkactual.linestgt + 1
216 elseif startswith(line, "\\") then
217 -- nothing
218 else
219 hunkactual.linessrc = hunkactual.linessrc + 1
220 hunkactual.linestgt = hunkactual.linestgt + 1
221 end
222 table.insert(hunkinfo.text, line)
223 -- todo: handle \ No newline cases
224 else
225 warning(format("invalid hunk no.%d at %d for target file %s",
226 nexthunkno, lineno, files.target[nextfileno]))
227 -- add hunk status node
228 table.insert(files.hunks[nextfileno], table_copy(hunkinfo))
229 files.hunks[nextfileno][nexthunkno].invalid = true
230 all_ok = false
231 state = 'hunkskip'
232 end
233
234 -- check exit conditions
235 if hunkactual.linessrc > hunkinfo.linessrc or
236 hunkactual.linestgt > hunkinfo.linestgt
237 then
238 warning(format("extra hunk no.%d lines at %d for target %s",
239 nexthunkno, lineno, files.target[nextfileno]))
240 -- add hunk status node
241 table.insert(files.hunks[nextfileno], table_copy(hunkinfo))
242 files.hunks[nextfileno][nexthunkno].invalid = true
243 state = 'hunkskip'
244 elseif hunkinfo.linessrc == hunkactual.linessrc and
245 hunkinfo.linestgt == hunkactual.linestgt
246 then
247 table.insert(files.hunks[nextfileno], table_copy(hunkinfo))
248 state = 'hunkskip'
249
250 -- detect mixed window/unix line ends
251 local ends = files.hunkends[nextfileno]
252 if (ends.cr~=0 and 1 or 0) + (ends.crlf~=0 and 1 or 0) +
253 (ends.lf~=0 and 1 or 0) > 1
254 then
255 warning(format("inconsistent line ends in patch hunks for %s",
256 files.source[nextfileno]))
257 end
258 if debugmode then
259 local debuglines = {crlf=ends.crlf, lf=ends.lf, cr=ends.cr,
260 file=files.target[nextfileno], hunk=nexthunkno}
261 debug(format("crlf: %(crlf)d lf: %(lf)d cr: %(cr)d\t " ..
262 "- file: %(file)s hunk: %(hunk)d", debuglines))
263 end
264 end
265 -- state is 'hunkbody' or 'hunkskip'
266 end
267
268 if state == 'hunkskip' then
269 if match_linerange(line) then
270 state = 'hunkhead'
271 elseif startswith(line, "--- ") then
272 state = 'filenames'
273 if debugmode and #files.source > 0 then
274 debug(format("- %2d hunks for %s", #files.hunks[nextfileno],
275 files.source[nextfileno]))
276 end
277 end
278 -- state is 'hunkskip', 'hunkhead', or 'filenames'
279 end
280 local advance
281 if state == 'filenames' then
282 if startswith(line, "--- ") then
283 if array_contains(files.source, nextfileno) then
284 all_ok = false
285 warning(format("skipping invalid patch for %s",
286 files.source[nextfileno+1]))
287 table.remove(files.source, nextfileno+1)
288 -- double source filename line is encountered
289 -- attempt to restart from this second line
290 end
291 -- Accept a space as a terminator, like GNU patch does.
292 -- Breaks patches containing filenames with spaces...
293 -- FIXME Figure out what does GNU patch do in those cases.
294 local match = line:match("^--- ([^\t ]+)")
295 if not match then
296 all_ok = false
297 warning(format("skipping invalid filename at line %d", lineno+1))
298 state = 'header'
299 else
300 table.insert(files.source, match)
301 end
302 elseif not startswith(line, "+++ ") then
303 if array_contains(files.source, nextfileno) then
304 all_ok = false
305 warning(format("skipping invalid patch with no target for %s",
306 files.source[nextfileno+1]))
307 table.remove(files.source, nextfileno+1)
308 else
309 -- this should be unreachable
310 warning("skipping invalid target patch")
311 end
312 state = 'header'
313 else
314 if array_contains(files.target, nextfileno) then
315 all_ok = false
316 warning(format("skipping invalid patch - double target at line %d",
317 lineno+1))
318 table.remove(files.source, nextfileno+1)
319 table.remove(files.target, nextfileno+1)
320 nextfileno = nextfileno - 1
321 -- double target filename line is encountered
322 -- switch back to header state
323 state = 'header'
324 else
325 -- Accept a space as a terminator, like GNU patch does.
326 -- Breaks patches containing filenames with spaces...
327 -- FIXME Figure out what does GNU patch do in those cases.
328 local re_filename = "^%+%+%+ ([^ \t]+)"
329 local match = line:match(re_filename)
330 if not match then
331 all_ok = false
332 warning(format(
333 "skipping invalid patch - no target filename at line %d",
334 lineno+1))
335 state = 'header'
336 else
337 table.insert(files.target, match)
338 nextfileno = nextfileno + 1
339 nexthunkno = 0
340 table.insert(files.hunks, {})
341 table.insert(files.hunkends, table_copy(lineends))
342 table.insert(files.fileends, table_copy(lineends))
343 state = 'hunkhead'
344 advance = true
345 end
346 end
347 end
348 -- state is 'filenames', 'header', or ('hunkhead' with advance)
349 end
350 if not advance and state == 'hunkhead' then
351 local m1, m2, m3, m4 = match_linerange(line)
352 if not m1 then
353 if not array_contains(files.hunks, nextfileno-1) then
354 all_ok = false
355 warning(format("skipping invalid patch with no hunks for file %s",
356 files.target[nextfileno]))
357 end
358 state = 'header'
359 else
360 hunkinfo.startsrc = tonumber(m1)
361 hunkinfo.linessrc = tonumber(m2 or 1)
362 hunkinfo.starttgt = tonumber(m3)
363 hunkinfo.linestgt = tonumber(m4 or 1)
364 hunkinfo.invalid = false
365 hunkinfo.text = {}
366
367 hunkactual.linessrc = 0
368 hunkactual.linestgt = 0
369
370 state = 'hunkbody'
371 nexthunkno = nexthunkno + 1
372 end
373 -- state is 'header' or 'hunkbody'
374 end
375 end
376 if state ~= 'hunkskip' then
377 warning(format("patch file incomplete - %s", filename))
378 all_ok = false
379 -- os.exit(?)
380 else
381 -- duplicated message when an eof is reached
382 if debugmode and #files.source > 0 then
383 debug(format("- %2d hunks for %s", #files.hunks[nextfileno],
384 files.source[nextfileno]))
385 end
386 end
387
388 local sum = 0; for _,hset in ipairs(files.hunks) do sum = sum + #hset end
389 info(format("total files: %d total hunks: %d", #files.source, sum))
390 fp:close()
391 return files, all_ok
392end
393
394local function find_hunk(file, h, hno)
395 for fuzz=0,2 do
396 local lineno = h.startsrc
397 for i=0,#file do
398 local found = true
399 local location = lineno
400 local total = #h.text - fuzz
401 for l, hline in ipairs(h.text) do
402 if l > fuzz then
403 -- todo: \ No newline at the end of file
404 if startswith(hline, " ") or startswith(hline, "-") then
405 local line = file[lineno]
406 lineno = lineno + 1
407 if not line or #line == 0 then
408 found = false
409 break
410 end
411 if endlstrip(line) ~= endlstrip(hline:sub(2)) then
412 found = false
413 break
414 end
415 end
416 end
417 end
418 if found then
419 local offset = location - h.startsrc - fuzz
420 if offset ~= 0 then
421 warning(format("Hunk %d found at offset %d%s...", hno, offset, fuzz == 0 and "" or format(" (fuzz %d)", fuzz)))
422 end
423 h.startsrc = location
424 h.starttgt = h.starttgt + offset
425 for i=1,fuzz do
426 table.remove(h.text, 1)
427 table.remove(h.text, #h.text)
428 end
429 return true
430 end
431 lineno = i
432 end
433 end
434 return false
435end
436
437local function load_file(filename)
438 local fp = assert(io.open(filename))
439 local file = {}
440 local readline = file_lines(fp)
441 while true do
442 local line = readline()
443 if not line then break end
444 table.insert(file, line)
445 end
446 fp:close()
447 return file
448end
449
450local function find_hunks(file, hunks)
451 local matched = true
452 local lineno = 1
453 local hno = nil
454 for hno, h in ipairs(hunks) do
455 find_hunk(file, h, hno)
456 end
457end
458
459local function check_patched(file, hunks)
460 local matched = true
461 local lineno = 1
462 local hno = nil
463 local ok, err = pcall(function()
464 if #file == 0 then
465 error 'nomatch'
466 end
467 for hno, h in ipairs(hunks) do
468 -- skip to line just before hunk starts
469 if #file < h.starttgt then
470 error 'nomatch'
471 end
472 lineno = h.starttgt
473print() for k,v in pairs(h) do print(k,v) end print()
474 for _, hline in ipairs(h.text) do
475 -- todo: \ No newline at the end of file
476 if not startswith(hline, "-") and not startswith(hline, "\\") then
477 local line = file[lineno]
478 lineno = lineno + 1
479 if #line == 0 then
480 error 'nomatch'
481 end
482 if endlstrip(line) ~= endlstrip(hline:sub(2)) then
483 warning(format("file is not patched - failed hunk: %d", hno))
484 error 'nomatch'
485 end
486 end
487 end
488 end
489 end)
490 if err == 'nomatch' then
491 matched = false
492 end
493 -- todo: display failed hunk, i.e. expected/found
494
495 return matched
496end
497
498local function patch_hunks(srcname, tgtname, hunks)
499 local src = assert(io.open(srcname, "rb"))
500 local tgt = assert(io.open(tgtname, "wb"))
501
502 local src_readline = file_lines(src)
503
504 -- todo: detect linefeeds early - in apply_files routine
505 -- to handle cases when patch starts right from the first
506 -- line and no lines are processed. At the moment substituted
507 -- lineends may not be the same at the start and at the end
508 -- of patching. Also issue a warning about mixed lineends
509
510 local srclineno = 1
511 local lineends = {['\n']=0, ['\r\n']=0, ['\r']=0}
512 for hno, h in ipairs(hunks) do
513 debug(format("processing hunk %d for file %s", hno, tgtname))
514 -- skip to line just before hunk starts
515 while srclineno < h.startsrc do
516 local line = src_readline()
517 -- Python 'U' mode works only with text files
518 if endswith(line, "\r\n") then
519 lineends["\r\n"] = lineends["\r\n"] + 1
520 elseif endswith(line, "\n") then
521 lineends["\n"] = lineends["\n"] + 1
522 elseif endswith(line, "\r") then
523 lineends["\r"] = lineends["\r"] + 1
524 end
525 tgt:write(line)
526 srclineno = srclineno + 1
527 end
528
529 for _,hline in ipairs(h.text) do
530 -- todo: check \ No newline at the end of file
531 if startswith(hline, "-") or startswith(hline, "\\") then
532 src_readline()
533 srclineno = srclineno + 1
534 else
535 if not startswith(hline, "+") then
536 src_readline()
537 srclineno = srclineno + 1
538 end
539 local line2write = hline:sub(2)
540 -- detect if line ends are consistent in source file
541 local sum = 0
542 for k,v in pairs(lineends) do if v > 0 then sum=sum+1 end end
543 if sum == 1 then
544 local newline
545 for k,v in pairs(lineends) do if v ~= 0 then newline = k end end
546 tgt:write(endlstrip(line2write) .. newline)
547 else -- newlines are mixed or unknown
548 tgt:write(line2write)
549 end
550 end
551 end
552 end
553 for line in src_readline do
554 tgt:write(line)
555 end
556 tgt:close()
557 src:close()
558 return true
559end
560
561local function strip_dirs(filename, strip)
562 if strip == nil then return filename end
563 for i=1,strip do
564 filename=filename:gsub("^[^/]*/", "")
565 end
566 return filename
567end
568
569function apply_patch(patch, strip)
570 local all_ok = true
571 local total = #patch.source
572 for fileno, filename in ipairs(patch.source) do
573 filename = strip_dirs(filename, strip)
574 local continue
575 local f2patch = filename
576 if not exists(f2patch) then
577 f2patch = strip_dirs(patch.target[fileno], strip)
578 if not exists(f2patch) then --FIX:if f2patch nil
579 warning(format("source/target file does not exist\n--- %s\n+++ %s",
580 filename, f2patch))
581 all_ok = false
582 continue = true
583 end
584 end
585 if not continue and not isfile(f2patch) then
586 warning(format("not a file - %s", f2patch))
587 all_ok = false
588 continue = true
589 end
590 if not continue then
591
592 filename = f2patch
593
594 info(format("processing %d/%d:\t %s", fileno, total, filename))
595
596 -- validate before patching
597 local hunks = patch.hunks[fileno]
598 local file = load_file(filename)
599 local hunkno = 1
600 local hunk = hunks[hunkno]
601 local hunkfind = {}
602 local hunkreplace = {}
603 local validhunks = 0
604 local canpatch = false
605 local hunklineno
606 local isbreak
607 local lineno = 0
608
609 find_hunks(file, hunks)
610
611 for _, line in ipairs(file) do
612 lineno = lineno + 1
613 local continue
614 if not hunk or lineno < hunk.startsrc then
615 continue = true
616 elseif lineno == hunk.startsrc then
617 hunkfind = {}
618 for _,x in ipairs(hunk.text) do
619 if x:sub(1,1) == ' ' or x:sub(1,1) == '-' then
620 hunkfind[#hunkfind+1] = endlstrip(x:sub(2))
621 end end
622 hunkreplace = {}
623 for _,x in ipairs(hunk.text) do
624 if x:sub(1,1) == ' ' or x:sub(1,1) == '+' then
625 hunkreplace[#hunkreplace+1] = endlstrip(x:sub(2))
626 end end
627 --pprint(hunkreplace)
628 hunklineno = 1
629
630 -- todo \ No newline at end of file
631 end
632 -- check hunks in source file
633 if not continue and lineno < hunk.startsrc + #hunkfind - 1 then
634 if endlstrip(line) == hunkfind[hunklineno] then
635 hunklineno = hunklineno + 1
636 else
637 debug(format("hunk no.%d doesn't match source file %s",
638 hunkno, filename))
639 -- file may be already patched, but check other hunks anyway
640 hunkno = hunkno + 1
641 if hunkno <= #hunks then
642 hunk = hunks[hunkno]
643 continue = true
644 else
645 isbreak = true; break
646 end
647 end
648 end
649 -- check if processed line is the last line
650 if not continue and lineno == hunk.startsrc + #hunkfind - 1 then
651 debug(format("file %s hunk no.%d -- is ready to be patched",
652 filename, hunkno))
653 hunkno = hunkno + 1
654 validhunks = validhunks + 1
655 if hunkno <= #hunks then
656 hunk = hunks[hunkno]
657 else
658 if validhunks == #hunks then
659 -- patch file
660 canpatch = true
661 isbreak = true; break
662 end
663 end
664 end
665 end
666 if not isbreak then
667 if hunkno <= #hunks then
668 warning(format("premature end of source file %s at hunk %d",
669 filename, hunkno))
670 all_ok = false
671 end
672 end
673 if validhunks < #hunks then
674 if check_patched(file, hunks) then
675 warning(format("already patched %s", filename))
676 else
677 warning(format("source file is different - %s", filename))
678 all_ok = false
679 end
680 end
681 if canpatch then
682 local backupname = filename .. ".orig"
683 if exists(backupname) then
684 warning(format("can't backup original file to %s - aborting",
685 backupname))
686 all_ok = false
687 else
688 assert(os.rename(filename, backupname))
689 if patch_hunks(backupname, filename, hunks) then
690 warning(format("successfully patched %s", filename))
691 assert(os.remove(backupname))
692 else
693 warning(format("error patching file %s", filename))
694 assert(file_copy(filename, filename .. ".invalid"))
695 warning(format("invalid version is saved to %s",
696 filename .. ".invalid"))
697 -- todo: proper rejects
698 assert(os.rename(backupname, filename))
699 all_ok = false
700 end
701 end
702 end
703
704 end -- if not continue
705 end -- for
706 -- todo: check for premature eof
707 return all_ok
708end
diff --git a/src/luarocks/tools/tar.lua b/src/luarocks/tools/tar.lua
new file mode 100644
index 00000000..0a3e07a4
--- /dev/null
+++ b/src/luarocks/tools/tar.lua
@@ -0,0 +1,143 @@
1
2module("luarocks.tools.tar", package.seeall)
3
4local fs = require("luarocks.fs")
5local dir = require("luarocks.dir")
6
7local blocksize = 512
8
9local function get_typeflag(flag)
10 if flag == "0" or flag == "\0" then return "file"
11 elseif flag == "1" then return "link"
12 elseif flag == "2" then return "symlink" -- "reserved" in POSIX, "symlink" in GNU
13 elseif flag == "3" then return "character"
14 elseif flag == "4" then return "block"
15 elseif flag == "5" then return "directory"
16 elseif flag == "6" then return "fifo"
17 elseif flag == "7" then return "contiguous" -- "reserved" in POSIX, "contiguous" in GNU
18 elseif flag == "x" then return "next file"
19 elseif flag == "g" then return "global extended header"
20 elseif flag == "L" then return "long name"
21 elseif flag == "K" then return "long link name"
22 end
23 return "unknown"
24end
25
26local function octal_to_number(octal)
27 local exp = 0
28 local number = 0
29 for i = #octal,1,-1 do
30 local digit = tonumber(octal:sub(i,i))
31 if not digit then break end
32 number = number + (digit * 8^exp)
33 exp = exp + 1
34 end
35 return number
36end
37
38local function checksum_header(block)
39 local sum = 256
40 for i = 1,148 do
41 sum = sum + block:byte(i)
42 end
43 for i = 157,500 do
44 sum = sum + block:byte(i)
45 end
46 return sum
47end
48
49local function nullterm(s)
50 return s:match("^[^%z]*")
51end
52
53local function read_header_block(block)
54 local header = {}
55 header.name = nullterm(block:sub(1,100))
56 header.mode = nullterm(block:sub(101,108))
57 header.uid = octal_to_number(nullterm(block:sub(109,116)))
58 header.gid = octal_to_number(nullterm(block:sub(117,124)))
59 header.size = octal_to_number(nullterm(block:sub(125,136)))
60 header.mtime = octal_to_number(nullterm(block:sub(137,148)))
61 header.chksum = octal_to_number(nullterm(block:sub(149,156)))
62 header.typeflag = get_typeflag(block:sub(157,157))
63 header.linkname = nullterm(block:sub(158,257))
64 header.magic = block:sub(258,263)
65 header.version = block:sub(264,265)
66 header.uname = nullterm(block:sub(266,297))
67 header.gname = nullterm(block:sub(298,329))
68 header.devmajor = octal_to_number(nullterm(block:sub(330,337)))
69 header.devminor = octal_to_number(nullterm(block:sub(338,345)))
70 header.prefix = block:sub(346,500)
71 if header.magic ~= "ustar " and header.magic ~= "ustar\0" then
72 return false, "Invalid header magic "..header.magic
73 end
74 if header.version ~= "00" and header.version ~= " \0" then
75 return false, "Unknown version "..header.version
76 end
77 if not checksum_header(block) == header.chksum then
78 return false, "Failed header checksum"
79 end
80 return header
81end
82
83function untar(filename, destdir)
84 assert(type(filename) == "string")
85 assert(type(destdir) == "string")
86
87 local tar_handle = io.open(filename, "r")
88 if not tar_handle then return nil, "Error opening file "..filename end
89
90 local long_name, long_link_name
91 while true do
92 local block
93 repeat
94 block = tar_handle:read(blocksize)
95 until (not block) or checksum_header(block) > 256
96 if not block then break end
97 local header, err = read_header_block(block)
98 if not header then
99 print(err)
100 end
101
102 local file_data = tar_handle:read(math.ceil(header.size / blocksize) * blocksize):sub(1,header.size)
103
104 if header.typeflag == "long name" then
105 long_name = nullterm(file_data)
106 elseif header.typeflag == "long link name" then
107 long_link_name = nullterm(file_data)
108 else
109 if long_name then
110 header.name = long_name
111 long_name = nil
112 end
113 if long_link_name then
114 header.name = long_link_name
115 long_link_name = nil
116 end
117 end
118 local pathname = dir.path(destdir, header.name)
119 if header.typeflag == "directory" then
120 fs.make_dir(pathname)
121 elseif header.typeflag == "file" then
122 local dirname = dir.dir_name(pathname)
123 if dirname ~= "" then
124 fs.make_dir(dirname)
125 end
126 local file_handle = io.open(pathname, "wb")
127 file_handle:write(file_data)
128 file_handle:close()
129 fs.set_time(pathname, header.mtime)
130 if fs.chmod then
131 fs.chmod(pathname, header.mode)
132 end
133 end
134 print(pathname)
135 --[[
136 for k,v in pairs(header) do
137 print("[\""..tostring(k).."\"] = "..(type(v)=="number" and v or "\""..v:gsub("%z", "\\0").."\""))
138 end
139 print()
140 --]]
141 end
142 return true
143end
diff --git a/src/luarocks/tools/zip.lua b/src/luarocks/tools/zip.lua
new file mode 100644
index 00000000..621beae1
--- /dev/null
+++ b/src/luarocks/tools/zip.lua
@@ -0,0 +1,230 @@
1
2module("luarocks.tools.zip", package.seeall)
3
4local zlib = require "zlib"
5
6local function number_to_bytestring(number, nbytes)
7 local out = {}
8 for i = 1, nbytes do
9 local byte = number % 256
10 table.insert(out, string.char(byte))
11 number = (number - byte) / 256
12 end
13 return table.concat(out)
14end
15
16--- Return a zip handle open for writing.
17-- @param name filename of the zipfile to be created.
18-- @return a zip handle, or nil in case of error.
19function write_open(name)
20
21 local zf = {}
22
23 zf.ziphandle = io.open(name, "w")
24 if not zf.ziphandle then
25 return nil
26 end
27 zf.files = {}
28 zf.in_open_file = false
29
30 return zf
31end
32
33--- Begin a new file to be stored inside the zipfile.
34-- @param zf handle of the zipfile being written.
35-- @param filename filenome of the file to be added to the zipfile.
36-- @return true if succeeded, nil in case of failure.
37function write_open_new_file_in_zip(zf, filename)
38 if zf.in_open_file then
39 close_file_in_zip(zf)
40 return nil
41 end
42 local lfh = {}
43 zf.local_file_header = lfh
44 lfh.last_mod_file_time = 0 -- TODO
45 lfh.last_mod_file_date = 0 -- TODO
46 lfh.crc32 = 0 -- initial value
47 lfh.compressed_size = 0 -- unknown yet
48 lfh.uncompressed_size = 0 -- unknown yet
49 lfh.file_name_length = #filename
50 lfh.extra_field_length = 0
51 lfh.file_name = filename:gsub("\\", "/")
52 zf.in_open_file = true
53 zf.data = {}
54 return true
55end
56
57--- Write data to the file currently being stored in the zipfile.
58-- @param zf handle of the zipfile being written.
59-- @param buf string containing data to be written.
60-- @return true if succeeded, nil in case of failure.
61function write_in_file_in_zip(zf, buf)
62 if not zf.in_open_file then
63 return nil
64 end
65 local lfh = zf.local_file_header
66 local cbuf = zlib.compress(buf):sub(3, -5)
67 lfh.crc32 = zlib.crc32(lfh.crc32, buf)
68 lfh.compressed_size = lfh.compressed_size + #cbuf
69 lfh.uncompressed_size = lfh.uncompressed_size + #buf
70 table.insert(zf.data, cbuf)
71 return true
72end
73
74--- Complete the writing of a file stored in the zipfile.
75-- @param zf handle of the zipfile being written.
76-- @return true if succeeded, nil in case of failure.
77function write_close_file_in_zip(zf)
78 local zh = zf.ziphandle
79
80 if not zf.in_open_file then
81 return nil
82 end
83
84 -- Local file header
85 local lfh = zf.local_file_header
86 lfh.offset = zh:seek()
87 zh:write(number_to_bytestring(0x04034b50, 4)) -- signature
88 zh:write(number_to_bytestring(20, 2)) -- version needed to extract: 2.0
89 zh:write(number_to_bytestring(0, 2)) -- general purpose bit flag
90 zh:write(number_to_bytestring(8, 2)) -- compression method: deflate
91 zh:write(number_to_bytestring(lfh.last_mod_file_time, 2))
92 zh:write(number_to_bytestring(lfh.last_mod_file_date, 2))
93 zh:write(number_to_bytestring(lfh.crc32, 4))
94 zh:write(number_to_bytestring(lfh.compressed_size, 4))
95 zh:write(number_to_bytestring(lfh.uncompressed_size, 4))
96 zh:write(number_to_bytestring(lfh.file_name_length, 2))
97 zh:write(number_to_bytestring(lfh.extra_field_length, 2))
98 zh:write(lfh.file_name)
99
100 -- File data
101 for _, cbuf in ipairs(zf.data) do
102 zh:write(cbuf)
103 end
104
105 -- Data descriptor
106 zh:write(number_to_bytestring(lfh.crc32, 4))
107 zh:write(number_to_bytestring(lfh.compressed_size, 4))
108 zh:write(number_to_bytestring(lfh.uncompressed_size, 4))
109
110 table.insert(zf.files, lfh)
111 zf.in_open_file = false
112
113 return true
114end
115
116--- Complete the writing of the zipfile.
117-- @param zf handle of the zipfile being written.
118-- @return true if succeeded, nil in case of failure.
119function write_close(zf)
120 local zh = zf.ziphandle
121
122 local central_directory_offset = zh:seek()
123
124 local size_of_central_directory = 0
125 -- Central directory structure
126 for _, lfh in ipairs(zf.files) do
127 zh:write(number_to_bytestring(0x02014b50, 4)) -- signature
128 zh:write(number_to_bytestring(3, 2)) -- version made by: UNIX
129 zh:write(number_to_bytestring(20, 2)) -- version needed to extract: 2.0
130 zh:write(number_to_bytestring(0, 2)) -- general purpose bit flag
131 zh:write(number_to_bytestring(8, 2)) -- compression method: deflate
132 zh:write(number_to_bytestring(lfh.last_mod_file_time, 2))
133 zh:write(number_to_bytestring(lfh.last_mod_file_date, 2))
134 zh:write(number_to_bytestring(lfh.crc32, 4))
135 zh:write(number_to_bytestring(lfh.compressed_size, 4))
136 zh:write(number_to_bytestring(lfh.uncompressed_size, 4))
137 zh:write(number_to_bytestring(lfh.file_name_length, 2))
138 zh:write(number_to_bytestring(lfh.extra_field_length, 2))
139 zh:write(number_to_bytestring(0, 2)) -- file comment length
140 zh:write(number_to_bytestring(0, 2)) -- disk number start
141 zh:write(number_to_bytestring(0, 2)) -- internal file attributes
142 zh:write(number_to_bytestring(0, 4)) -- external file attributes
143 zh:write(number_to_bytestring(lfh.offset, 4)) -- relative offset of local header
144 zh:write(lfh.file_name)
145 size_of_central_directory = size_of_central_directory + 46 + lfh.file_name_length
146 end
147
148 -- End of central directory record
149 zh:write(number_to_bytestring(0x06054b50, 4)) -- signature
150 zh:write(number_to_bytestring(0, 2)) -- number of this disk
151 zh:write(number_to_bytestring(0, 2)) -- number of disk with start of central directory
152 zh:write(number_to_bytestring(#zf.files, 2)) -- total number of entries in the central dir on this disk
153 zh:write(number_to_bytestring(#zf.files, 2)) -- total number of entries in the central dir
154 zh:write(number_to_bytestring(size_of_central_directory, 4))
155 zh:write(number_to_bytestring(central_directory_offset, 4))
156 zh:write(number_to_bytestring(0, 2)) -- zip file comment length
157 zh:close()
158
159 return true
160end
161
162-- @return boolean or (boolean, string): true on success,
163-- false and an error message on failure.
164local function add_to_zip(zf, file)
165 local fin
166 local ok, err = write_open_new_file_in_zip(zf, file)
167 if not ok then
168 err = "error in opening "..file.." in zipfile"
169 else
170 fin = io.open(file, "rb")
171 if not fin then
172 ok = false
173 err = "error opening "..file.." for reading"
174 end
175 end
176 while ok do
177 local buf = fin:read(size_buf)
178 if not buf then
179 break
180 end
181 ok = write_in_file_in_zip(zf, buf)
182 if not ok then
183 err = "error in writing "..file.." in the zipfile"
184 end
185 end
186 if fin then
187 fin:close()
188 end
189 if ok then
190 ok = write_close_file_in_zip(zf)
191 if not ok then
192 err = "error in writing "..file.." in the zipfile"
193 end
194 end
195 return ok == true, err
196end
197
198--- Compress files in a .zip archive.
199-- @param zipfile string: pathname of .zip archive to be created.
200-- @param ... Filenames to be stored in the archive are given as
201-- additional arguments.
202-- @return boolean or (boolean, string): true on success,
203-- false and an error message on failure.
204function zip(zipfile, ...)
205 local zf = write_open(filename)
206 if not zf then
207 return nil, "error opening "..filename
208 end
209
210 local ok, err
211 for _, file in pairs({...}) do
212 if fs_is_dir(file) then
213 for _, file in pairs(fs_find(file)) do
214 if fs_is_file(file) then
215 ok, err = add_to_zip(file)
216 if not ok then break end
217 end
218 end
219 else
220 ok, err = add_to_zip(file)
221 if not ok then break end
222 end
223 end
224
225 local ok = write_close(zf)
226 if not ok then
227 return false, "error closing "..filename
228 end
229 return ok, err
230end
diff --git a/src/luarocks/type_check.lua b/src/luarocks/type_check.lua
index ec9d6c5c..0e4a73af 100644
--- a/src/luarocks/type_check.lua
+++ b/src/luarocks/type_check.lua
@@ -40,6 +40,7 @@ rockspec_types = {
40 dir = "string", 40 dir = "string",
41 tag = "string", 41 tag = "string",
42 branch = "string", 42 branch = "string",
43 module = "string",
43 cvs_tag = "string", 44 cvs_tag = "string",
44 cvs_module = "string" 45 cvs_module = "string"
45 }, 46 },
@@ -187,7 +188,6 @@ end
187type_check_table = function(tbl, types, context) 188type_check_table = function(tbl, types, context)
188 assert(type(tbl) == "table") 189 assert(type(tbl) == "table")
189 assert(type(types) == "table") 190 assert(type(types) == "table")
190
191 for k, v in pairs(tbl) do 191 for k, v in pairs(tbl) do
192 local t = types[k] or (type(k) == "string" and types["MUST_"..k]) or types.ANY 192 local t = types[k] or (type(k) == "string" and types["MUST_"..k]) or types.ANY
193 if t then 193 if t then
diff --git a/src/luarocks/unpack.lua b/src/luarocks/unpack.lua
index 60bc1295..c73264d0 100644
--- a/src/luarocks/unpack.lua
+++ b/src/luarocks/unpack.lua
@@ -7,6 +7,7 @@ local fetch = require("luarocks.fetch")
7local fs = require("luarocks.fs") 7local fs = require("luarocks.fs")
8local util = require("luarocks.util") 8local util = require("luarocks.util")
9local build = require("luarocks.build") 9local build = require("luarocks.build")
10local dir = require("luarocks.dir")
10 11
11help_summary = "Unpack the contents of a rock." 12help_summary = "Unpack the contents of a rock."
12help_arguments = "{<rock>|<name> [<version>]}" 13help_arguments = "{<rock>|<name> [<version>]}"
@@ -50,9 +51,9 @@ local function unpack_rock(rock_file, dir_name, kind)
50 assert(type(rock_file) == "string") 51 assert(type(rock_file) == "string")
51 assert(type(dir_name) == "string") 52 assert(type(dir_name) == "string")
52 53
53 local ok, err = fetch.fetch_and_unpack_rock(rock_file, dir_name) 54 local ok, err, errcode = fetch.fetch_and_unpack_rock(rock_file, dir_name)
54 if not ok then 55 if not ok then
55 return nil, "Failed unzipping rock "..rock_file 56 return nil, "Failed unzipping rock "..rock_file, errcode
56 end 57 end
57 fs.change_dir(dir_name) 58 fs.change_dir(dir_name)
58 local rockspec_file = dir_name..".rockspec" 59 local rockspec_file = dir_name..".rockspec"
@@ -83,7 +84,7 @@ end
83local function run_unpacker(file) 84local function run_unpacker(file)
84 assert(type(file) == "string") 85 assert(type(file) == "string")
85 86
86 local base_name = fs.base_name(file) 87 local base_name = dir.base_name(file)
87 local dir_name, kind, extension = base_name:match("(.*)%.([^.]+)%.(rock)$") 88 local dir_name, kind, extension = base_name:match("(.*)%.([^.]+)%.(rock)$")
88 if not extension then 89 if not extension then
89 dir_name, extension = base_name:match("(.*)%.(rockspec)$") 90 dir_name, extension = base_name:match("(.*)%.(rockspec)$")
@@ -117,7 +118,7 @@ local function run_unpacker(file)
117 end 118 end
118 print() 119 print()
119 print("Done. You may now enter directory ") 120 print("Done. You may now enter directory ")
120 print(fs.make_path(dir_name, rockspec.source.dir)) 121 print(dir.path(dir_name, rockspec.source.dir))
121 print("and type 'luarocks make' to build.") 122 print("and type 'luarocks make' to build.")
122 end 123 end
123 util.remove_scheduled_function(rollback) 124 util.remove_scheduled_function(rollback)
diff --git a/src/luarocks/validate.lua b/src/luarocks/validate.lua
new file mode 100644
index 00000000..1bf001c7
--- /dev/null
+++ b/src/luarocks/validate.lua
@@ -0,0 +1,159 @@
1
2module("luarocks.validate", package.seeall)
3
4local fs = require("luarocks.fs")
5local dir = require("luarocks.dir")
6local cfg = require("luarocks.cfg")
7local build = require("luarocks.build")
8local install = require("luarocks.install")
9local util = require("luarocks.util")
10
11help_summary = "Sandboxed test of build/install of all packages in a repository."
12
13help = [[
14<argument>, if given, is a local repository pathname.
15]]
16
17local function save_settings(repo)
18 local protocol, path = fs.split_url(repo)
19 table.insert(cfg.rocks_servers, 1, protocol.."://"..path)
20 return {
21 root_dir = cfg.root_dir,
22 rocks_dir = cfg.rocks_dir,
23 scripts_dir = cfg.scripts_dir,
24 }
25end
26
27local function restore_settings(settings)
28 cfg.root_dir = settings.root_dir
29 cfg.rocks_dir = settings.rocks_dir
30 cfg.scripts_dir = settings.scripts_dir
31 cfg.variables.ROCKS_TREE = settings.root_dir
32 cfg.variables.SCRIPTS_DIR = settings.scripts_dir
33 table.remove(cfg.rocks_servers, 1)
34end
35
36local function prepare_sandbox(file)
37 local root_dir = fs.make_temp_dir(file):gsub("/+$", "")
38 cfg.root_dir = root_dir
39 cfg.rocks_dir = root_dir.."/rocks"
40 cfg.scripts_dir = root_dir.."/bin"
41 cfg.variables.ROCKS_TREE = cfg.root_dir
42 cfg.variables.SCRIPTS_DIR = cfg.scripts_dir
43 return root_dir
44end
45
46local function validate_rockspec(file)
47 local ok, err, errcode = build.build_rockspec(file, true)
48 if not ok then
49 print(err)
50 end
51 return ok, err, errcode
52end
53
54local function validate_src_rock(file)
55 local ok, err, errcode = build.build_rock(file, false)
56 if not ok then
57 print(err)
58 end
59 return ok, err, errcode
60end
61
62local function validate_rock(file)
63 local ok, err, errcode = install.install_binary_rock(file)
64 if not ok then
65 print(err)
66 end
67 return ok, err, errcode
68end
69
70local function validate(repo, flags)
71 local results = {
72 ok = {}
73 }
74 local settings = save_settings(repo)
75 local sandbox
76 if flags["quick"] then
77 sandbox = prepare_sandbox("luarocks_validate")
78 end
79 if not fs.exists(repo) then
80 return nil, repo.." is not a local repository."
81 end
82 for _, file in pairs(fs.list_dir(repo)) do for _=1,1 do
83 if file == "manifest" or file == "index.html" then
84 break -- continue for
85 end
86 local pathname = fs.absolute_name(dir.path(repo, file))
87 if not flags["quick"] then
88 sandbox = prepare_sandbox(file)
89 end
90 local ok, err, errcode
91 print()
92 print("Verifying "..pathname)
93 if file:match("%.rockspec$") then
94 ok, err, errcode = validate_rockspec(pathname)
95 elseif file:match("%.src%.rock$") then
96 ok, err, errcode = validate_src_rock(pathname)
97 elseif file:match("%.rock$") then
98 ok, err, errcode = validate_rock(pathname)
99 end
100 if ok then
101 table.insert(results.ok, {file=file} )
102 else
103 if not errcode then
104 errcode = "misc"
105 end
106 if not results[errcode] then
107 results[errcode] = {}
108 end
109 table.insert(results[errcode], {file=file, err=err} )
110 end
111 util.run_scheduled_functions()
112 if not flags["quick"] then
113 fs.delete(sandbox)
114 end
115 repeat until not fs.pop_dir()
116 end end
117 if flags["quick"] then
118 fs.delete(sandbox)
119 end
120 restore_settings(settings)
121 print()
122 print("Results:")
123 print("--------")
124 print("OK: "..tostring(#results.ok))
125 for _, entry in ipairs(results.ok) do
126 print(entry.file)
127 end
128 for errcode, errors in pairs(results) do
129 if errcode ~= "ok" then
130 print()
131 print(errcode.." errors: "..tostring(#errors))
132 for _, entry in ipairs(errors) do
133 print(entry.file, entry.err)
134 end
135 end
136 end
137
138 print()
139 print("Summary:")
140 print("--------")
141 local total = 0
142 for errcode, errors in pairs(results) do
143 print(errcode..": "..tostring(#errors))
144 total = total + #errors
145 end
146 print("Total: "..total)
147 return true
148end
149
150function run(...)
151 local flags, repo = util.parse_flags(...)
152 assert(type(repo) == "string" or not repo)
153 repo = repo or cfg.rocks_dir
154
155 print("Verifying contents of "..repo)
156
157 return validate(repo, flags)
158end
159