aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/luarocks/admin/cache.lua89
-rw-r--r--src/luarocks/admin/cmd/add.lua135
-rw-r--r--src/luarocks/admin/cmd/make_manifest.lua55
-rw-r--r--src/luarocks/admin/cmd/refresh_cache.lua36
-rw-r--r--src/luarocks/admin/cmd/remove.lua96
-rw-r--r--src/luarocks/admin/index.lua190
-rw-r--r--src/luarocks/build.lua487
-rw-r--r--src/luarocks/build/builtin.lua403
-rw-r--r--src/luarocks/build/cmake.lua91
-rw-r--r--src/luarocks/build/command.lua51
-rw-r--r--src/luarocks/build/make.lua107
-rw-r--r--src/luarocks/cmd.lua807
-rw-r--r--src/luarocks/cmd/build.lua211
-rw-r--r--src/luarocks/cmd/config.lua402
-rw-r--r--src/luarocks/cmd/doc.lua158
-rw-r--r--src/luarocks/cmd/download.lua61
-rw-r--r--src/luarocks/cmd/init.lua230
-rw-r--r--src/luarocks/cmd/install.lua259
-rw-r--r--src/luarocks/cmd/lint.lua59
-rw-r--r--src/luarocks/cmd/list.lua113
-rw-r--r--src/luarocks/cmd/make.lua179
-rw-r--r--src/luarocks/cmd/new_version.lua237
-rw-r--r--src/luarocks/cmd/pack.lua41
-rw-r--r--src/luarocks/cmd/path.lua88
-rw-r--r--src/luarocks/cmd/purge.lua79
-rw-r--r--src/luarocks/cmd/remove.lua77
-rw-r--r--src/luarocks/cmd/search.lua91
-rw-r--r--src/luarocks/cmd/show.lua341
-rw-r--r--src/luarocks/cmd/test.lua53
-rw-r--r--src/luarocks/cmd/unpack.lua172
-rw-r--r--src/luarocks/cmd/upload.lua144
-rw-r--r--src/luarocks/cmd/which.lua44
-rw-r--r--src/luarocks/cmd/write_rockspec.lua423
-rw-r--r--src/luarocks/config.lua38
-rw-r--r--src/luarocks/core/dir.lua95
-rw-r--r--src/luarocks/core/manif.lua124
-rw-r--r--src/luarocks/core/path.lua151
-rw-r--r--src/luarocks/core/persist.lua70
-rw-r--r--src/luarocks/core/sysdetect.lua508
-rw-r--r--src/luarocks/core/types/query.lua13
-rw-r--r--src/luarocks/core/types/result.lua14
-rw-r--r--src/luarocks/core/types/rockspec.lua78
-rw-r--r--src/luarocks/core/util.lua334
-rw-r--r--src/luarocks/core/vers.lua211
-rw-r--r--src/luarocks/deplocks.lua111
-rw-r--r--src/luarocks/deps.lua877
-rw-r--r--src/luarocks/dir.lua65
-rw-r--r--src/luarocks/download.lua76
-rw-r--r--src/luarocks/fetch.lua615
-rw-r--r--src/luarocks/fetch/cvs.lua53
-rw-r--r--src/luarocks/fetch/git.lua166
-rw-r--r--src/luarocks/fetch/git_file.lua22
-rw-r--r--src/luarocks/fetch/git_http.lua29
-rw-r--r--src/luarocks/fetch/git_https.lua7
-rw-r--r--src/luarocks/fetch/git_ssh.lua35
-rw-r--r--src/luarocks/fetch/hg.lua64
-rw-r--r--src/luarocks/fetch/hg_http.lua27
-rw-r--r--src/luarocks/fetch/hg_https.lua8
-rw-r--r--src/luarocks/fetch/hg_ssh.lua8
-rw-r--r--src/luarocks/fetch/sscm.lua45
-rw-r--r--src/luarocks/fetch/svn.lua63
-rw-r--r--src/luarocks/fun.lua138
-rw-r--r--src/luarocks/loader.lua333
-rw-r--r--src/luarocks/manif.lua229
-rw-r--r--src/luarocks/manif/writer.lua459
-rw-r--r--src/luarocks/pack.lua188
-rw-r--r--src/luarocks/path.lua254
-rw-r--r--src/luarocks/persist.lua275
-rw-r--r--src/luarocks/queries.lua211
-rw-r--r--src/luarocks/remove.lua140
-rw-r--r--src/luarocks/repo_writer.lua52
-rw-r--r--src/luarocks/repos.lua690
-rw-r--r--src/luarocks/require.lua2
-rw-r--r--src/luarocks/results.lua60
-rw-r--r--src/luarocks/rockspecs.lua184
-rw-r--r--src/luarocks/search.lua385
-rw-r--r--src/luarocks/signing.lua48
-rw-r--r--src/luarocks/test.lua110
-rw-r--r--src/luarocks/test/busted.lua55
-rw-r--r--src/luarocks/test/command.lua55
-rw-r--r--src/luarocks/tools/patch.lua746
-rw-r--r--src/luarocks/tools/tar.lua210
-rw-r--r--src/luarocks/tools/zip.lua575
-rw-r--r--src/luarocks/type/manifest.lua92
-rw-r--r--src/luarocks/type/rockspec.lua261
-rw-r--r--src/luarocks/type_check.lua237
-rw-r--r--src/luarocks/upload/api.lua300
-rw-r--r--src/luarocks/upload/multipart.lua117
-rw-r--r--src/luarocks/util.lua611
89 files changed, 16933 insertions, 0 deletions
diff --git a/src/luarocks/admin/cache.lua b/src/luarocks/admin/cache.lua
new file mode 100644
index 00000000..11bcc818
--- /dev/null
+++ b/src/luarocks/admin/cache.lua
@@ -0,0 +1,89 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local os = _tl_compat and _tl_compat.os or os; local string = _tl_compat and _tl_compat.string or string
2
3
4local cache = {}
5
6
7local fs = require("luarocks.fs")
8local cfg = require("luarocks.core.cfg")
9local dir = require("luarocks.dir")
10local util = require("luarocks.util")
11
12function cache.get_upload_server(server)
13 if not server then server = cfg.upload_server end
14 if not server then
15 return nil, nil, "No server specified and no default configured with upload_server."
16 end
17 return server, cfg.upload_servers and cfg.upload_servers[server]
18end
19
20function cache.get_server_urls(server, upload_server)
21 local download_url = server
22 local login_url = nil
23 if upload_server then
24 if upload_server.rsync then download_url = "rsync://" .. upload_server.rsync
25 elseif upload_server.http then download_url = "http://" .. upload_server.http
26 elseif upload_server.ftp then download_url = "ftp://" .. upload_server.ftp
27 end
28
29 if upload_server.ftp then login_url = "ftp://" .. upload_server.ftp
30 elseif upload_server.sftp then login_url = "sftp://" .. upload_server.sftp
31 end
32 end
33 return download_url, login_url
34end
35
36function cache.split_server_url(url, user, password)
37 local protocol, server_path = dir.split_url(url)
38 if protocol == "file" then
39 server_path = fs.absolute_name(server_path)
40 elseif server_path:match("@") then
41 local credentials
42 credentials, server_path = server_path:match("([^@]*)@(.*)")
43 if credentials:match(":") then
44 user, password = credentials:match("([^:]*):(.*)")
45 else
46 user = credentials
47 end
48 end
49 local local_cache = dir.path(cfg.local_cache, (server_path:gsub("[\\/]", "_")))
50 return local_cache, protocol, server_path, user, password
51end
52
53local function download_cache(protocol, server_path, user, password)
54 os.remove("index.html")
55
56 if protocol == "rsync" then
57 local srv, path = server_path:match("([^/]+)(/.+)")
58 return fs.execute(cfg.variables.RSYNC .. " " .. cfg.variables.RSYNCFLAGS .. " -e ssh " .. user .. "@" .. srv .. ":" .. path .. "/ ./")
59 elseif protocol == "file" then
60 return fs.copy_contents(server_path, ".")
61 else
62 local login_info = ""
63 if user then login_info = " --user=" .. user end
64 if password then login_info = login_info .. " --password=" .. password end
65 return fs.execute(cfg.variables.WGET .. " --no-cache -q -m -np -nd " .. protocol .. "://" .. server_path .. login_info)
66 end
67end
68
69function cache.refresh_local_cache(url, given_user, given_password)
70 local local_cache, protocol, server_path, user, password = cache.split_server_url(url, given_user, given_password)
71
72 local ok, err = fs.make_dir(local_cache)
73 if not ok then
74 return nil, "Failed creating local cache dir: " .. err
75 end
76
77 fs.change_dir(local_cache)
78
79 util.printout("Refreshing cache " .. local_cache .. "...")
80
81 ok = download_cache(protocol, server_path, user, password)
82 if not ok then
83 return nil, "Failed downloading cache."
84 end
85
86 return local_cache, protocol, server_path, user, password
87end
88
89return cache
diff --git a/src/luarocks/admin/cmd/add.lua b/src/luarocks/admin/cmd/add.lua
new file mode 100644
index 00000000..862a08a8
--- /dev/null
+++ b/src/luarocks/admin/cmd/add.lua
@@ -0,0 +1,135 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2
3
4local add = {}
5
6
7local cfg = require("luarocks.core.cfg")
8local util = require("luarocks.util")
9local dir = require("luarocks.dir")
10local writer = require("luarocks.manif.writer")
11local fs = require("luarocks.fs")
12local cache = require("luarocks.admin.cache")
13local index = require("luarocks.admin.index")
14
15
16
17
18
19function add.add_to_parser(parser)
20 local cmd = parser:command("add", "Add a rock or rockspec to a rocks server.", util.see_also())
21
22 cmd:argument("rocks", "A local rockspec or rock file."):
23 args("+")
24
25 cmd:option("--server", "The server to use. If not given, the default server " ..
26 "set in the upload_server variable from the configuration file is used instead."):
27 target("add_server")
28 cmd:flag("--no-refresh", "Do not refresh the local cache prior to " ..
29 "generation of the updated manifest.")
30 cmd:flag("--index", "Produce an index.html file for the manifest. This " ..
31 "flag is automatically set if an index.html file already exists.")
32end
33
34local function zip_manifests()
35 for ver in util.lua_versions() do
36 local file = "manifest-" .. ver
37 local zip = file .. ".zip"
38 fs.delete(dir.path(fs.current_dir(), zip))
39 fs.zip(zip, file)
40 end
41end
42
43local function add_files_to_server(refresh, rockfiles, server, upload_server, do_index)
44
45 local download_url, login_url = cache.get_server_urls(server, upload_server)
46 local at = fs.current_dir()
47 local refresh_fn = refresh and cache.refresh_local_cache or cache.split_server_url
48
49 local local_cache, protocol, server_path, user, password = refresh_fn(download_url, cfg.upload_user, cfg.upload_password)
50 if not local_cache then
51 return nil, protocol
52 end
53
54 if not login_url then
55 login_url = protocol .. "://" .. server_path
56 end
57
58 local ok, err = fs.change_dir(at)
59 if not ok then return nil, err end
60
61 local files = {}
62 for _, rockfile in ipairs(rockfiles) do
63 if fs.exists(rockfile) then
64 util.printout("Copying file " .. rockfile .. " to " .. local_cache .. "...")
65 local absolute = fs.absolute_name(rockfile)
66 fs.copy(absolute, local_cache, "read")
67 table.insert(files, dir.base_name(absolute))
68 else
69 util.printerr("File " .. rockfile .. " not found")
70 end
71 end
72 if #files == 0 then
73 return nil, "No files found"
74 end
75
76 local ok, err = fs.change_dir(local_cache)
77 if not ok then return nil, err end
78
79 util.printout("Updating manifest...")
80 writer.make_manifest(local_cache, "one", true)
81
82 zip_manifests()
83
84 if fs.exists("index.html") then
85 do_index = true
86 end
87
88 if do_index then
89 util.printout("Updating index.html...")
90 index.make_index(local_cache)
91 end
92
93 local login_info = ""
94 if user then login_info = " -u " .. user end
95 if password then login_info = login_info .. ":" .. password end
96 if not login_url:match("/$") then
97 login_url = login_url .. "/"
98 end
99
100 if do_index then
101 table.insert(files, "index.html")
102 end
103 table.insert(files, "manifest")
104 for ver in util.lua_versions() do
105 table.insert(files, "manifest-" .. ver)
106 table.insert(files, "manifest-" .. ver .. ".zip")
107 end
108
109
110
111 local cmd
112 if protocol == "rsync" then
113 local srv, path = server_path:match("([^/]+)(/.+)")
114 cmd = cfg.variables.RSYNC .. " " .. cfg.variables.RSYNCFLAGS .. " -e ssh " .. local_cache .. "/ " .. user .. "@" .. srv .. ":" .. path .. "/"
115 elseif protocol == "file" then
116 return fs.copy_contents(local_cache, server_path)
117 elseif upload_server and upload_server.sftp then
118 local part1, part2 = upload_server.sftp:match("^([^/]*)/(.*)$")
119 cmd = cfg.variables.SCP .. " " .. table.concat(files, " ") .. " " .. user .. "@" .. part1 .. ":/" .. part2
120 else
121 cmd = cfg.variables.CURL .. " " .. login_info .. " -T '{" .. table.concat(files, ",") .. "}' " .. login_url
122 end
123
124 util.printout(cmd)
125 return fs.execute(cmd)
126end
127
128function add.command(args)
129 local server, server_table, err = cache.get_upload_server(args.add_server or args.server)
130 if not server then return nil, err end
131 return add_files_to_server(not args.no_refresh, args.rocks, server, server_table, args.index)
132end
133
134
135return add
diff --git a/src/luarocks/admin/cmd/make_manifest.lua b/src/luarocks/admin/cmd/make_manifest.lua
new file mode 100644
index 00000000..b028f900
--- /dev/null
+++ b/src/luarocks/admin/cmd/make_manifest.lua
@@ -0,0 +1,55 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string
2
3
4local make_manifest = {}
5
6
7local writer = require("luarocks.manif.writer")
8local index = require("luarocks.admin.index")
9local cfg = require("luarocks.core.cfg")
10local util = require("luarocks.util")
11local deps = require("luarocks.deps")
12local fs = require("luarocks.fs")
13local dir = require("luarocks.dir")
14
15
16
17
18
19function make_manifest.add_to_parser(parser)
20 local cmd = parser:command("make_manifest", "Compile a manifest file for a repository.", util.see_also())
21
22 cmd:argument("repository", "Local repository pathname."):
23 args("?")
24
25 cmd:flag("--local-tree", "If given, do not write versioned versions of the manifest file.\n" ..
26 "Use this when rebuilding the manifest of a local rocks tree.")
27 util.deps_mode_option(cmd)
28end
29
30
31
32
33function make_manifest.command(args)
34 local repo = args.repository or cfg.rocks_dir
35
36 util.printout("Making manifest for " .. repo)
37
38 if repo:match("/lib/luarocks") and not args.local_tree then
39 util.warning("This looks like a local rocks tree, but you did not pass --local-tree.")
40 end
41
42 local ok, err = writer.make_manifest(repo, deps.get_deps_mode(args), not args.local_tree)
43 if ok and not args.local_tree then
44 util.printout("Generating index.html for " .. repo)
45 index.make_index(repo)
46 end
47 if args.local_tree then
48 for luaver in util.lua_versions() do
49 fs.delete(dir.path(repo, "manifest-" .. luaver))
50 end
51 end
52 return ok, err
53end
54
55return make_manifest
diff --git a/src/luarocks/admin/cmd/refresh_cache.lua b/src/luarocks/admin/cmd/refresh_cache.lua
new file mode 100644
index 00000000..08e90bbc
--- /dev/null
+++ b/src/luarocks/admin/cmd/refresh_cache.lua
@@ -0,0 +1,36 @@
1
2
3local refresh_cache = {}
4
5
6local cfg = require("luarocks.core.cfg")
7local util = require("luarocks.util")
8local cache = require("luarocks.admin.cache")
9
10
11
12
13
14function refresh_cache.add_to_parser(parser)
15 local cmd = parser:command("refresh_cache", "Refresh local cache of a remote rocks server.", util.see_also())
16
17 cmd:option("--from", "The server to use. If not given, the default server " ..
18 "set in the upload_server variable from the configuration file is used instead."):
19 argname("<server>")
20end
21
22function refresh_cache.command(args)
23 local server, upload_server, err = cache.get_upload_server(args.server)
24 if not server then return nil, err end
25 local download_url = cache.get_server_urls(server, upload_server)
26
27 local ok, err = cache.refresh_local_cache(download_url, cfg.upload_user, cfg.upload_password)
28 if not ok then
29 return nil, err
30 else
31 return true
32 end
33end
34
35
36return refresh_cache
diff --git a/src/luarocks/admin/cmd/remove.lua b/src/luarocks/admin/cmd/remove.lua
new file mode 100644
index 00000000..eee761fa
--- /dev/null
+++ b/src/luarocks/admin/cmd/remove.lua
@@ -0,0 +1,96 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local string = _tl_compat and _tl_compat.string or string
2
3
4local admin_remove = {}
5
6
7local cfg = require("luarocks.core.cfg")
8local util = require("luarocks.util")
9local dir = require("luarocks.dir")
10local writer = require("luarocks.manif.writer")
11local fs = require("luarocks.fs")
12local cache = require("luarocks.admin.cache")
13local index = require("luarocks.admin.index")
14
15
16
17
18
19function admin_remove.add_to_parser(parser)
20 local cmd = parser:command("remove", "Remove a rock or rockspec from a rocks server.", util.see_also())
21
22 cmd:argument("rocks", "A local rockspec or rock file."):
23 args("+")
24
25 cmd:option("--server", "The server to use. If not given, the default server " ..
26 "set in the upload_server variable from the configuration file is used instead.")
27 cmd:flag("--no-refresh", "Do not refresh the local cache prior to " ..
28 "generation of the updated manifest.")
29end
30
31local function remove_files_from_server(refresh, rockfiles, server, upload_server)
32
33 local download_url, login_url = cache.get_server_urls(server, upload_server)
34 local at = fs.current_dir()
35 local refresh_fn = refresh and cache.refresh_local_cache or cache.split_server_url
36
37 local local_cache, protocol, server_path, user, password = refresh_fn(download_url, cfg.upload_user, cfg.upload_password)
38 if not local_cache then
39 return nil, protocol
40 end
41
42 local ok, err = fs.change_dir(at)
43 if not ok then return nil, err end
44
45 local nr_files = 0
46 for _, rockfile in ipairs(rockfiles) do
47 local basename = dir.base_name(rockfile)
48 local file = dir.path(local_cache, basename)
49 util.printout("Removing file " .. file .. "...")
50 fs.delete(file)
51 if not fs.exists(file) then
52 nr_files = nr_files + 1
53 else
54 util.printerr("Failed removing " .. file)
55 end
56 end
57 if nr_files == 0 then
58 return nil, "No files removed."
59 end
60
61 local ok, err = fs.change_dir(local_cache)
62 if not ok then return nil, err end
63
64 util.printout("Updating manifest...")
65 writer.make_manifest(local_cache, "one", true)
66 util.printout("Updating index.html...")
67 index.make_index(local_cache)
68
69 if protocol == "file" then
70 local cmd = cfg.variables.RSYNC .. " " .. cfg.variables.RSYNCFLAGS .. " --delete " .. local_cache .. "/ " .. server_path .. "/"
71 util.printout(cmd)
72 fs.execute(cmd)
73 return true
74 end
75
76 if protocol ~= "rsync" then
77 return nil, "This command requires 'rsync', check your configuration."
78 end
79
80 local srv, path = server_path:match("([^/]+)(/.+)")
81 local cmd = cfg.variables.RSYNC .. " " .. cfg.variables.RSYNCFLAGS .. " --delete -e ssh " .. local_cache .. "/ " .. user .. "@" .. srv .. ":" .. path .. "/"
82
83 util.printout(cmd)
84 fs.execute(cmd)
85
86 return true
87end
88
89function admin_remove.command(args)
90 local server, server_table, err = cache.get_upload_server(args.server)
91 if not server then return nil, err end
92 return remove_files_from_server(not args.no_refresh, args.rocks, server, server_table)
93end
94
95
96return admin_remove
diff --git a/src/luarocks/admin/index.lua b/src/luarocks/admin/index.lua
new file mode 100644
index 00000000..e2e2deff
--- /dev/null
+++ b/src/luarocks/admin/index.lua
@@ -0,0 +1,190 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local package = _tl_compat and _tl_compat.package or package; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2
3local index = {}
4
5
6local util = require("luarocks.util")
7local fs = require("luarocks.fs")
8local vers = require("luarocks.core.vers")
9local persist = require("luarocks.persist")
10local dir = require("luarocks.dir")
11local manif = require("luarocks.manif")
12
13
14
15
16
17local ext_url_target = ' target="_blank"'
18
19local index_header = [[
20<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
21<html>
22<head>
23<title>Available rocks</title>
24<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
25<style>
26body {
27 background-color: white;
28 font-family: "bitstream vera sans", "verdana", "sans";
29 font-size: 14px;
30}
31a {
32 color: #0000c0;
33 text-decoration: none;
34}
35a.pkg {
36 color: black;
37}
38a:hover {
39 text-decoration: underline;
40}
41td.main {
42 border-style: none;
43}
44blockquote {
45 font-size: 12px;
46}
47td.package {
48 background-color: #f0f0f0;
49 vertical-align: top;
50}
51td.spacer {
52 height: 5px;
53}
54td.version {
55 background-color: #d0d0d0;
56 vertical-align: top;
57 text-align: left;
58 padding: 5px;
59 width: 100px;
60}
61p.manifest {
62 font-size: 8px;
63}
64</style>
65</head>
66<body>
67<h1>Available rocks</h1>
68<p>
69Lua modules available from this location for use with <a href="http://www.luarocks.org">LuaRocks</a>:
70</p>
71<table class="main">
72]]
73
74local index_package_begin = [[
75<td class="package">
76<p><a name="$anchor"></a><a href="#$anchor" class="pkg"><b>$package</b></a> - $summary<br/>
77</p><blockquote><p>$detailed<br/>
78$externaldependencies
79<font size="-1"><a href="$original">latest sources</a> $homepage | License: $license</font></p>
80</blockquote></a></td>
81<td class="version">
82]]
83
84local index_package_end = [[
85</td></tr>
86<tr><td colspan="2" class="spacer"></td></tr>
87]]
88
89local index_footer_begin = [[
90</table>
91<p class="manifest">
92<a href="manifest">manifest file</a>
93]]
94local index_manifest_ver = [[
95&bull; <a href="manifest-$VER">Lua $VER manifest file</a> (<a href="manifest-$VER.zip">zip</a>)
96]]
97local index_footer_end = [[
98</p>
99</body>
100</html>
101]]
102
103function index.format_external_dependencies(rockspec)
104 if rockspec.external_dependencies then
105 local deplist = {}
106 local listed_set = {}
107 local plats = nil
108 for name, desc in util.sortedpairs(rockspec.external_dependencies) do
109 if name ~= "platforms" then
110 table.insert(deplist, name:lower())
111 listed_set[name] = true
112 else
113 plats = desc
114 end
115 end
116 if plats then
117 for plat, entries in util.sortedpairs(plats) do
118 for name, desc in util.sortedpairs(entries) do
119 if not listed_set[name] then
120 table.insert(deplist, name:lower() .. " (on " .. plat .. ")")
121 end
122 end
123 end
124 end
125 return '<p><b>External dependencies:</b> ' .. table.concat(deplist, ',&nbsp;') .. '</p>'
126 else
127 return ""
128 end
129end
130
131function index.make_index(repo)
132 if not fs.is_dir(repo) then
133 return nil, "Cannot access repository at " .. repo
134 end
135 local manifest = manif.load_manifest(repo)
136 local out = io.open(dir.path(repo, "index.html"), "w")
137
138 out:write(index_header)
139 for package, version_list in util.sortedpairs(manifest.repository) do
140 local latest_rockspec = nil
141 local output = index_package_begin
142 for version, data in util.sortedpairs(version_list, vers.compare_versions) do
143 local versions = {}
144 output = output .. version .. ':&nbsp;'
145 table.sort(data, function(a, b) return a.arch < b.arch end)
146 for _, item in ipairs(data) do
147 local file
148 if item.arch == 'rockspec' then
149 file = ("%s-%s.rockspec"):format(package, version)
150 if not latest_rockspec then latest_rockspec = file end
151 else
152 file = ("%s-%s.%s.rock"):format(package, version, item.arch)
153 end
154 table.insert(versions, '<a href="' .. file .. '">' .. item.arch .. '</a>')
155 end
156 output = output .. table.concat(versions, ',&nbsp;') .. '<br/>'
157 end
158 output = output .. index_package_end
159 if latest_rockspec then
160 local rockspec = persist.load_into_table(dir.path(repo, latest_rockspec))
161 local descript = rockspec.description or {}
162 local vars = {
163 anchor = package,
164 package = rockspec.package,
165 original = rockspec.source.url,
166 summary = descript.summary or "",
167 detailed = descript.detailed or "",
168 license = descript.license or "N/A",
169 homepage = descript.homepage and ('| <a href="' .. descript.homepage .. '"' .. ext_url_target .. '>project homepage</a>') or "",
170 externaldependencies = index.format_external_dependencies(rockspec),
171 }
172 vars.detailed = vars.detailed:gsub("\n\n", "</p><p>"):gsub("%s+", " ")
173 vars.detailed = vars.detailed:gsub("(https?://[a-zA-Z0-9%.%%-_%+%[%]=%?&/$@;:]+)", '<a href="%1"' .. ext_url_target .. '>%1</a>')
174 output = output:gsub("$(%w+)", vars)
175 else
176 output = output:gsub("$anchor", package)
177 output = output:gsub("$package", package)
178 output = output:gsub("$(%w+)", "")
179 end
180 out:write(output)
181 end
182 out:write(index_footer_begin)
183 for ver in util.lua_versions() do
184 out:write((index_manifest_ver:gsub("$VER", ver)))
185 end
186 out:write(index_footer_end)
187 out:close()
188end
189
190return index
diff --git a/src/luarocks/build.lua b/src/luarocks/build.lua
new file mode 100644
index 00000000..63082077
--- /dev/null
+++ b/src/luarocks/build.lua
@@ -0,0 +1,487 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string
2local build = {Builder = {}, }
3
4
5
6
7
8
9local path = require("luarocks.path")
10local util = require("luarocks.util")
11local fun = require("luarocks.fun")
12local fetch = require("luarocks.fetch")
13local fs = require("luarocks.fs")
14local dir = require("luarocks.dir")
15local deps = require("luarocks.deps")
16local cfg = require("luarocks.core.cfg")
17local vers = require("luarocks.core.vers")
18local repos = require("luarocks.repos")
19local repo_writer = require("luarocks.repo_writer")
20local deplocks = require("luarocks.deplocks")
21
22
23
24
25
26
27
28
29
30
31
32
33
34do
35
36
37
38 local function extract_from_rockspec(files)
39 for name, content in pairs(files) do
40 local fd = io.open(dir.path(fs.current_dir(), name), "w+")
41 fd:write(content)
42 fd:close()
43 end
44 end
45
46
47
48
49
50
51
52 function build.apply_patches(rockspec)
53
54 if not (rockspec.build.extra_files or rockspec.build.patches) then
55 return true
56 end
57
58 local fd = io.open(fs.absolute_name(".luarocks.patches.applied"), "r")
59 if fd then
60 fd:close()
61 return true
62 end
63
64 if rockspec.build.extra_files then
65 extract_from_rockspec(rockspec.build.extra_files)
66 end
67 if rockspec.build.patches then
68 extract_from_rockspec(rockspec.build.patches)
69 for patch, patchdata in util.sortedpairs(rockspec.build.patches) do
70 util.printout("Applying patch " .. patch .. "...")
71 local create_delete = rockspec:format_is_at_least("3.0")
72 local ok, err = fs.apply_patch(tostring(patch), patchdata, create_delete)
73 if not ok then
74 return nil, "Failed applying patch " .. patch
75 end
76 end
77 end
78
79 fd = io.open(fs.absolute_name(".luarocks.patches.applied"), "w")
80 if fd then
81 fd:close()
82 end
83 return true
84 end
85end
86
87local function check_macosx_deployment_target(rockspec)
88 local target = rockspec.build.macosx_deployment_target
89 local function patch_variable(var)
90 local rockspec_variables = rockspec.variables
91 if rockspec_variables[var]:match("MACOSX_DEPLOYMENT_TARGET") then
92 rockspec_variables[var] = (rockspec_variables[var]):gsub("MACOSX_DEPLOYMENT_TARGET=[^ ]*", "MACOSX_DEPLOYMENT_TARGET=" .. target)
93 else
94 rockspec_variables[var] = "env MACOSX_DEPLOYMENT_TARGET=" .. target .. " " .. rockspec_variables[var]
95 end
96 end
97 if cfg.is_platform("macosx") and rockspec:format_is_at_least("3.0") and target then
98 local version = util.popen_read("sw_vers -productVersion")
99 if version:match("^%d+%.%d+%.%d+$") or version:match("^%d+%.%d+$") then
100 if vers.compare_versions(target, version) then
101 return nil, ("This rock requires Mac OSX %s, and you are running %s."):format(target, version)
102 end
103 end
104 patch_variable("CC")
105 patch_variable("LD")
106 end
107 return true
108end
109
110local function process_dependencies(rockspec, opts, cwd)
111 if not opts.build_only_deps then
112 local ok, err, errcode = deps.check_external_deps(rockspec, "build")
113 if err then
114 return nil, err, errcode
115 end
116 end
117
118 if opts.deps_mode == "none" then
119 return true
120 end
121
122 local deplock_dir = fs.exists(dir.path(cwd, "luarocks.lock")) and cwd or nil
123
124 if not opts.build_only_deps then
125 if next(rockspec.build_dependencies) then
126
127 local user_lua_version = cfg.lua_version
128 local running_lua_version = _VERSION:sub(5)
129
130 if running_lua_version ~= user_lua_version then
131
132
133
134
135
136
137 cfg.lua_version = running_lua_version
138 cfg.lua_modules_path = cfg.lua_modules_path:gsub(user_lua_version:gsub("%.", "%%."), running_lua_version)
139 cfg.lib_modules_path = cfg.lib_modules_path:gsub(user_lua_version:gsub("%.", "%%."), running_lua_version)
140 cfg.rocks_subdir = cfg.rocks_subdir:gsub(user_lua_version:gsub("%.", "%%."), running_lua_version)
141 path.use_tree(cfg.root_dir)
142 end
143
144 local ok, err, errcode = deps.fulfill_dependencies(rockspec, "build_dependencies", "all", opts.verify, deplock_dir)
145
146 path.add_to_package_paths(cfg.root_dir)
147
148 if running_lua_version ~= user_lua_version then
149
150 cfg.lua_version = user_lua_version
151 cfg.lua_modules_path = cfg.lua_modules_path:gsub(running_lua_version:gsub("%.", "%%."), user_lua_version)
152 cfg.lib_modules_path = cfg.lib_modules_path:gsub(running_lua_version:gsub("%.", "%%."), user_lua_version)
153 cfg.rocks_subdir = cfg.rocks_subdir:gsub(running_lua_version:gsub("%.", "%%."), user_lua_version)
154 path.use_tree(cfg.root_dir)
155 end
156
157 if err then
158 return nil, err, errcode
159 end
160 end
161 end
162
163 return deps.fulfill_dependencies(rockspec, "dependencies", opts.deps_mode, opts.verify, deplock_dir)
164end
165
166local function fetch_and_change_to_source_dir(rockspec, opts)
167 if opts.minimal_mode or opts.build_only_deps then
168 return true
169 end
170 if opts.need_to_fetch then
171 if opts.branch then
172 rockspec.source.branch = opts.branch
173 end
174 local oks, source_dir, errcode = fetch.fetch_sources(rockspec, true)
175 if not oks then
176 return nil, source_dir, errcode
177 end
178 local ok, err
179 ok, err = fs.change_dir(source_dir)
180 if not ok then
181 return nil, err
182 end
183 else
184 if rockspec.source.file then
185 local ok, err = fs.unpack_archive(rockspec.source.file)
186 if not ok then
187 return nil, err
188 end
189 end
190 local ok, err = fetch.find_rockspec_source_dir(rockspec, ".")
191 if not ok then
192 return nil, err
193 end
194 end
195 fs.change_dir(rockspec.source.dir)
196 return true
197end
198
199local function prepare_install_dirs(name, version)
200 local dirs = {
201 lua = { name = path.lua_dir(name, version), is_module_path = true, perms = "read" },
202 lib = { name = path.lib_dir(name, version), is_module_path = true, perms = "exec" },
203 bin = { name = path.bin_dir(name, version), is_module_path = false, perms = "exec" },
204 conf = { name = path.conf_dir(name, version), is_module_path = false, perms = "read" },
205 }
206
207 for _, d in pairs(dirs) do
208 local ok, err = fs.make_dir(d.name)
209 if not ok then
210 return nil, err
211 end
212 end
213
214 return dirs
215end
216
217local function run_build_driver(rockspec, no_install)
218 local btype = rockspec.build.type
219 if btype == "none" then
220 return true
221 end
222
223 if btype == "module" then
224 util.printout("Do not use 'module' as a build type. Use 'builtin' instead.")
225 btype = "builtin"
226 rockspec.build.type = btype
227 end
228 local driver
229 if cfg.accepted_build_types and not fun.contains(cfg.accepted_build_types, btype) then
230 return nil, "This rockspec uses the '" .. btype .. "' build type, which is blocked by the 'accepted_build_types' setting in your LuaRocks configuration."
231 end
232 local pok, driver_str = pcall(require, "luarocks.build." .. btype)
233 if not (type(driver_str) == "table") then
234 return nil, "Failed initializing build back-end for build type '" .. btype .. "': " .. driver_str
235 else
236 driver = driver_str
237 end
238
239 if not driver.skip_lua_inc_lib_check then
240 local ok, err, errcode = deps.check_lua_incdir(rockspec.variables)
241 if not ok then
242 return nil, err, errcode
243 end
244
245 if cfg.link_lua_explicitly then
246 ok, err, errcode = deps.check_lua_libdir(rockspec.variables)
247 if not ok then
248 return nil, err, errcode
249 end
250 end
251 end
252
253 local ok, err = driver.run(rockspec, no_install)
254 if not ok then
255 return nil, "Build error: " .. err
256 end
257 return true
258end
259
260local install_files
261do
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279 local function install_to(files, location, is_module_path, perms)
280 if not files then
281 return true
282 end
283 for k, file in pairs(files) do
284 local dest = location
285 local filename = dir.base_name(file)
286 if type(k) == "string" then
287 local modname = k
288 if is_module_path then
289 dest = dir.path(location, path.module_to_path(modname))
290 local ok, err = fs.make_dir(dest)
291 if not ok then return nil, err end
292 if filename:match("%.lua$") then
293 local basename = modname:match("([^.]+)$")
294 filename = basename .. ".lua"
295 end
296 else
297 dest = dir.path(location, dir.dir_name(modname))
298 local ok, err = fs.make_dir(dest)
299 if not ok then return nil, err end
300 filename = dir.base_name(modname)
301 end
302 else
303 local ok, err = fs.make_dir(dest)
304 if not ok then return nil, err end
305 end
306 local ok = fs.copy(file, dir.path(dest, filename), perms)
307 if not ok then
308 return nil, "Failed copying " .. file
309 end
310 end
311 return true
312 end
313
314 local function install_default_docs(name, version)
315 local patterns = { "readme", "license", "copying", ".*%.md" }
316 local dest = dir.path(path.install_dir(name, version), "doc")
317 local has_dir = false
318 for file in fs.dir() do
319 for _, pattern in ipairs(patterns) do
320 if file:lower():match("^" .. pattern) then
321 if not has_dir then
322 fs.make_dir(dest)
323 has_dir = true
324 end
325 fs.copy(file, dest, "read")
326 break
327 end
328 end
329 end
330 end
331
332 install_files = function(rockspec, dirs)
333 local name, version = rockspec.name, rockspec.version
334
335 if rockspec.build.install then
336 for k, d in pairs(dirs) do
337 local ok, err = install_to((rockspec.build.install)[k], d.name, d.is_module_path, d.perms)
338 if not ok then return nil, err end
339 end
340 end
341
342 local copy_directories = rockspec.build.copy_directories
343 local copying_default = false
344 if not copy_directories then
345 copy_directories = { "doc" }
346 copying_default = true
347 end
348
349 local any_docs = false
350 for _, copy_dir in ipairs(copy_directories) do
351 if fs.is_dir(copy_dir) then
352 local dest = dir.path(path.install_dir(name, version), copy_dir)
353 fs.make_dir(dest)
354 fs.copy_contents(copy_dir, dest)
355 any_docs = true
356 else
357 if not copying_default then
358 return nil, "Directory '" .. copy_dir .. "' not found"
359 end
360 end
361 end
362 if not any_docs then
363 install_default_docs(name, version)
364 end
365
366 return true
367 end
368end
369
370
371
372
373
374
375function build.build_rockspec(rockspec, opts, cwd)
376
377 cwd = cwd or dir.path(".")
378
379 if not rockspec.build then
380 if rockspec:format_is_at_least("3.0") then
381 rockspec.build = {
382 type = "builtin",
383 }
384 else
385 return nil, "Rockspec error: build table not specified"
386 end
387 end
388
389 if not rockspec.build.type then
390 if rockspec:format_is_at_least("3.0") then
391 rockspec.build.type = "builtin"
392 else
393 return nil, "Rockspec error: build type not specified"
394 end
395 end
396
397 local ok, err = fetch_and_change_to_source_dir(rockspec, opts)
398 if not ok then return nil, err end
399
400 if opts.pin then
401 deplocks.init(rockspec.name, ".")
402 end
403
404 ok, err = process_dependencies(rockspec, opts, cwd)
405 if not ok then return nil, err end
406
407 local name, version = rockspec.name, rockspec.version
408 if opts.build_only_deps then
409 if opts.pin then
410 deplocks.write_file()
411 end
412 return name, version
413 end
414
415 local dirs, err
416 local rollback
417 if not opts.no_install then
418 if repos.is_installed(name, version) then
419 repo_writer.delete_version(name, version, opts.deps_mode)
420 end
421
422 dirs, err = prepare_install_dirs(name, version)
423 if not dirs then return nil, err end
424
425 rollback = util.schedule_function(function()
426 fs.delete(path.install_dir(name, version))
427 fs.remove_dir_if_empty(path.versions_dir(name))
428 end)
429 end
430
431 ok, err = build.apply_patches(rockspec)
432 if not ok then return nil, err end
433
434 ok, err = check_macosx_deployment_target(rockspec)
435 if not ok then return nil, err end
436
437 ok, err = run_build_driver(rockspec, opts.no_install)
438 if not ok then return nil, err end
439
440 if opts.no_install then
441 fs.pop_dir()
442 if opts.need_to_fetch then
443 fs.pop_dir()
444 end
445 return name, version
446 end
447
448 ok, err = install_files(rockspec, dirs)
449 if not ok then return nil, err end
450
451 for _, d in pairs(dirs) do
452 fs.remove_dir_if_empty(d.name)
453 end
454
455 fs.pop_dir()
456 if opts.need_to_fetch then
457 fs.pop_dir()
458 end
459
460 if opts.pin then
461 deplocks.write_file()
462 end
463
464 fs.copy(rockspec.local_abs_filename, path.rockspec_file(name, version), "read")
465
466 local deplock_file = deplocks.get_abs_filename(name)
467 if deplock_file then
468 fs.copy(deplock_file, dir.path(path.install_dir(name, version), "luarocks.lock"), "read")
469 end
470
471 ok, err = repo_writer.deploy_files(name, version, repos.should_wrap_bin_scripts(rockspec), opts.deps_mode, opts.namespace)
472 if not ok then return nil, err end
473
474 util.remove_scheduled_function(rollback)
475 rollback = util.schedule_function(function()
476 repo_writer.delete_version(name, version, opts.deps_mode)
477 end)
478
479 ok, err = repos.run_hook(rockspec, "post_install")
480 if not ok then return nil, err end
481
482 util.announce_install(rockspec)
483 util.remove_scheduled_function(rollback)
484 return name, version
485end
486
487return build
diff --git a/src/luarocks/build/builtin.lua b/src/luarocks/build/builtin.lua
new file mode 100644
index 00000000..24434fef
--- /dev/null
+++ b/src/luarocks/build/builtin.lua
@@ -0,0 +1,403 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack
2
3local builtin = {}
4
5
6
7
8
9
10
11
12
13
14
15
16builtin.skip_lua_inc_lib_check = true
17
18local dir_sep = package.config:sub(1, 1)
19
20local fs = require("luarocks.fs")
21local path = require("luarocks.path")
22local util = require("luarocks.util")
23local cfg = require("luarocks.core.cfg")
24local dir = require("luarocks.dir")
25local deps = require("luarocks.deps")
26
27local function autoextract_libs(external_dependencies, variables)
28 if not external_dependencies then
29 return nil, nil, nil
30 end
31 local libs = {}
32 local incdirs = {}
33 local libdirs = {}
34 for name, data in pairs(external_dependencies) do
35 if data.library then
36 table.insert(libs, data.library)
37 table.insert(incdirs, variables[name .. "_INCDIR"])
38 table.insert(libdirs, variables[name .. "_LIBDIR"])
39 end
40 end
41 return libs, incdirs, libdirs
42end
43
44do
45 local function get_cmod_name(file)
46 local fd = io.open(dir.path(fs.current_dir(), file), "r")
47 if not fd then return nil end
48 local data = fd:read("*a")
49 fd:close()
50 return (data:match("int%s+luaopen_([a-zA-Z0-9_]+)"))
51 end
52
53 local skiplist = {
54 ["spec"] = true,
55 [".luarocks"] = true,
56 ["lua_modules"] = true,
57 ["test.lua"] = true,
58 ["tests.lua"] = true,
59 }
60
61 function builtin.autodetect_modules(libs, incdirs, libdirs)
62 local modules = {}
63 local install
64 local copy_directories
65
66 local prefix = ""
67 for _, parent in ipairs({ "src", "lua", "lib" }) do
68 if fs.is_dir(parent) then
69 fs.change_dir(parent)
70 prefix = parent .. dir_sep
71 break
72 end
73 end
74
75 for _, file in ipairs(fs.find()) do
76 local base = file:match("^([^\\/]*)")
77 if not skiplist[base] then
78 local luamod = file:match("(.*)%.lua$")
79 if luamod then
80 modules[path.path_to_module(file)] = prefix .. file
81 else
82 local cmod = file:match("(.*)%.c$")
83 if cmod then
84 local modname = get_cmod_name(file) or path.path_to_module((file:gsub("%.c$", ".lua")))
85 modules[modname] = {
86 sources = prefix .. file,
87 libraries = libs,
88 incdirs = incdirs,
89 libdirs = libdirs,
90 }
91 end
92 end
93 end
94 end
95
96 if prefix ~= "" then
97 fs.pop_dir()
98 end
99
100 local bindir = (fs.is_dir(dir.path("src", "bin")) and dir.path("src", "bin")) or
101 (fs.is_dir("bin") and "bin")
102 if bindir then
103 install = { bin = {} }
104 for _, file in ipairs(fs.list_dir(bindir)) do
105 table.insert((install.bin), dir.path(bindir, file))
106 end
107 end
108
109 for _, directory in ipairs({ "doc", "docs", "samples", "tests" }) do
110 if fs.is_dir(directory) then
111 if not copy_directories then
112 copy_directories = {}
113 end
114 table.insert(copy_directories, directory)
115 end
116 end
117
118 return modules, install, copy_directories
119 end
120end
121
122
123
124
125local function execute(...)
126 io.stdout:write(table.concat({ ... }, " ") .. "\n")
127 return fs.execute(...)
128end
129
130
131
132
133
134function builtin.run(rockspec, no_install)
135 local compile_object
136 local compile_library
137 local compile_static_library
138
139 local build = rockspec.build
140 local variables = rockspec.variables
141 local checked_lua_h = false
142
143 for _, var in ipairs({ "CC", "CFLAGS", "LDFLAGS" }) do
144 variables[var] = variables[var] or os.getenv(var) or ""
145 end
146
147 local function add_flags(extras, flag, flags)
148 if flags then
149 if not (type(flags) == "table") then
150 flags = { tostring(flags) }
151 end
152 util.variable_substitutions(flags, variables)
153 for _, v in ipairs(flags) do
154 table.insert(extras, flag:format(v))
155 end
156 end
157 end
158
159 if cfg.is_platform("mingw32") then
160 compile_object = function(object, source, defines, incdirs)
161 local extras = {}
162 add_flags(extras, "-D%s", defines)
163 add_flags(extras, "-I%s", incdirs)
164 return execute(variables.CC .. " " .. variables.CFLAGS, "-c", "-o", object, "-I" .. variables.LUA_INCDIR, source, _tl_table_unpack(extras))
165 end
166 compile_library = function(library, objects, libraries, libdirs, name)
167 local extras = { _tl_table_unpack(objects) }
168 add_flags(extras, "-L%s", libdirs)
169 add_flags(extras, "-l%s", libraries)
170 extras[#extras + 1] = dir.path(variables.LUA_LIBDIR, variables.LUALIB)
171
172 if variables.CC == "clang" or variables.CC == "clang-cl" then
173 local exported_name = name:gsub("%.", "_")
174 exported_name = exported_name:match('^[^%-]+%-(.+)$') or exported_name
175 extras[#extras + 1] = string.format("-Wl,-export:luaopen_%s", exported_name)
176 else
177 extras[#extras + 1] = "-l" .. (variables.MSVCRT or "m")
178 end
179
180 local ok = execute(variables.LD .. " " .. variables.LDFLAGS .. " " .. variables.LIBFLAG, "-o", library, _tl_table_unpack(extras))
181 return ok
182 end
183
184
185
186
187
188
189
190
191
192 elseif cfg.is_platform("win32") then
193 compile_object = function(object, source, defines, incdirs)
194 local extras = {}
195 add_flags(extras, "-D%s", defines)
196 add_flags(extras, "-I%s", incdirs)
197 return execute(variables.CC .. " " .. variables.CFLAGS, "-c", "-Fo" .. object, "-I" .. variables.LUA_INCDIR, source, _tl_table_unpack(extras))
198 end
199 compile_library = function(library, objects, libraries, libdirs, name)
200 local extras = { _tl_table_unpack(objects) }
201 add_flags(extras, "-libpath:%s", libdirs)
202 add_flags(extras, "%s.lib", libraries)
203 local basename = dir.base_name(library):gsub(".[^.]*$", "")
204 local deffile = basename .. ".def"
205 local def = io.open(dir.path(fs.current_dir(), deffile), "w+")
206 local exported_name = name:gsub("%.", "_")
207 exported_name = exported_name:match('^[^%-]+%-(.+)$') or exported_name
208 def:write("EXPORTS\n")
209 def:write("luaopen_" .. exported_name .. "\n")
210 def:close()
211 local ok = execute(variables.LD, "-dll", "-def:" .. deffile, "-out:" .. library, dir.path(variables.LUA_LIBDIR, variables.LUALIB), _tl_table_unpack(extras))
212 local basedir = ""
213 if name:find("%.") then
214 basedir = name:gsub("%.%w+$", "\\")
215 basedir = basedir:gsub("%.", "\\")
216 end
217 local manifestfile = basedir .. basename .. ".dll.manifest"
218
219 if ok and fs.exists(manifestfile) then
220 ok = execute(variables.MT, "-manifest", manifestfile, "-outputresource:" .. basedir .. basename .. ".dll;2")
221 end
222 return ok
223 end
224
225
226
227
228
229
230 else
231 compile_object = function(object, source, defines, incdirs)
232 local extras = {}
233 add_flags(extras, "-D%s", defines)
234 add_flags(extras, "-I%s", incdirs)
235 return execute(variables.CC .. " " .. variables.CFLAGS, "-I" .. variables.LUA_INCDIR, "-c", source, "-o", object, _tl_table_unpack(extras))
236 end
237 compile_library = function(library, objects, libraries, libdirs)
238 local extras = { _tl_table_unpack(objects) }
239 add_flags(extras, "-L%s", libdirs)
240 if cfg.gcc_rpath then
241 add_flags(extras, "-Wl,-rpath,%s", libdirs)
242 end
243 add_flags(extras, "-l%s", libraries)
244 if cfg.link_lua_explicitly then
245 extras[#extras + 1] = "-L" .. variables.LUA_LIBDIR
246 extras[#extras + 1] = "-llua"
247 end
248 return execute(variables.LD .. " " .. variables.LDFLAGS .. " " .. variables.LIBFLAG, "-o", library, _tl_table_unpack(extras))
249 end
250 compile_static_library = function(library, objects, libraries, libdirs, name)
251 local ok = execute(variables.AR, "rc", library, _tl_table_unpack(objects))
252 if ok then
253 ok = execute(variables.RANLIB, library)
254 end
255 return ok
256 end
257 end
258
259 local ok, err
260 local lua_modules = {}
261 local lib_modules = {}
262 local luadir = path.lua_dir(rockspec.name, rockspec.version)
263 local libdir = path.lib_dir(rockspec.name, rockspec.version)
264
265 local autolibs, autoincdirs, autolibdirs = autoextract_libs(rockspec.external_dependencies, rockspec.variables)
266
267 if not build.modules then
268 if rockspec:format_is_at_least("3.0") then
269 local install, copy_directories
270 build.modules, install, copy_directories = builtin.autodetect_modules(autolibs, autoincdirs, autolibdirs)
271 build.install = build.install or install
272 build.copy_directories = build.copy_directories or copy_directories
273 else
274 return nil, "Missing build.modules table"
275 end
276 end
277
278 local compile_temp_dir
279
280 local mkdir_cache = {}
281 local function cached_make_dir(name)
282 if name == "" or mkdir_cache[name] then
283 return true
284 end
285 mkdir_cache[name] = true
286 return fs.make_dir(name)
287 end
288
289 for name, info in pairs(build.modules) do
290 local moddir = path.module_to_path(name)
291 if type(info) == "string" then
292 local ext = info:match("%.([^.]+)$")
293 if ext == "lua" then
294 local filename = dir.base_name(info)
295 if filename == "init.lua" and not name:match("%.init$") then
296 moddir = path.module_to_path(name .. ".init")
297 else
298 local basename = name:match("([^.]+)$")
299 filename = basename .. ".lua"
300 end
301 local dest = dir.path(luadir, moddir, filename)
302 lua_modules[info] = dest
303 else
304 info = { info }
305 end
306 end
307 if type(info) == "table" then
308 if not checked_lua_h then
309 local ok, err, errcode = deps.check_lua_incdir(rockspec.variables)
310 if not ok then
311 return nil, err, errcode
312 end
313
314 if cfg.link_lua_explicitly then
315 local ok, err, errcode = deps.check_lua_libdir(rockspec.variables)
316 if not ok then
317 return nil, err, errcode
318 end
319 end
320 checked_lua_h = true
321 end
322 local objects = {}
323 local sources = info.sources
324 if info[1] then sources = info end
325 if type(sources) == "string" then sources = { sources } end
326 if not (type(sources) == "table") then
327 return nil, "error in rockspec: module '" .. name .. "' entry has no 'sources' list"
328 end
329 for _, source in ipairs(sources) do
330 if not (type(source) == "string") then
331 return nil, "error in rockspec: module '" .. name .. "' does not specify source correctly."
332 end
333 local object = source:gsub("%.[^.]*$", "." .. cfg.obj_extension)
334 if not object then
335 object = source .. "." .. cfg.obj_extension
336 end
337 ok = compile_object(object, source, info.defines, info.incdirs or autoincdirs)
338 if not ok then
339 return nil, "Failed compiling object " .. object
340 end
341 table.insert(objects, object)
342 end
343
344 if not compile_temp_dir then
345 compile_temp_dir = fs.make_temp_dir("build-" .. rockspec.package .. "-" .. rockspec.version)
346 util.schedule_function(fs.delete, compile_temp_dir)
347 end
348
349 local module_name = name:match("([^.]*)$") .. "." .. util.matchquote(cfg.lib_extension)
350 if moddir ~= "" then
351 module_name = dir.path(moddir, module_name)
352 end
353
354 local build_name = dir.path(compile_temp_dir, module_name)
355 local build_dir = dir.dir_name(build_name)
356 cached_make_dir(build_dir)
357
358 lib_modules[build_name] = dir.path(libdir, module_name)
359 ok = compile_library(build_name, objects, info.libraries, info.libdirs or autolibdirs, name)
360 if not ok then
361 return nil, "Failed compiling module " .. module_name
362 end
363
364
365
366 if cached_make_dir(dir.dir_name(module_name)) then
367 fs.copy(build_name, module_name)
368 end
369
370
371
372
373
374
375
376
377
378
379
380
381 end
382 end
383 if not no_install then
384 for _, mods in ipairs({ { tbl = lua_modules, perms = "read" }, { tbl = lib_modules, perms = "exec" } }) do
385 for name, dest in pairs(mods.tbl) do
386 cached_make_dir(dir.dir_name(dest))
387 ok, err = fs.copy(name, dest, mods.perms)
388 if not ok then
389 return nil, "Failed installing " .. name .. " in " .. dest .. ": " .. err
390 end
391 end
392 end
393 if fs.is_dir("lua") then
394 ok, err = fs.copy_contents("lua", luadir)
395 if not ok then
396 return nil, "Failed copying contents of 'lua' directory: " .. err
397 end
398 end
399 end
400 return true
401end
402
403return builtin
diff --git a/src/luarocks/build/cmake.lua b/src/luarocks/build/cmake.lua
new file mode 100644
index 00000000..57d7535c
--- /dev/null
+++ b/src/luarocks/build/cmake.lua
@@ -0,0 +1,91 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local io = _tl_compat and _tl_compat.io or io; local os = _tl_compat and _tl_compat.os or os; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string
2
3
4
5local cmake = {CMakeBuild = {Install = {}, }, }
6
7
8
9
10
11
12
13
14
15
16local fs = require("luarocks.fs")
17local util = require("luarocks.util")
18local cfg = require("luarocks.core.cfg")
19
20
21
22
23
24
25
26function cmake.run(rockspec, no_install)
27 local build = rockspec.build
28 local variables = build.variables or {}
29
30
31 variables.CMAKE_MODULE_PATH = os.getenv("CMAKE_MODULE_PATH")
32 variables.CMAKE_LIBRARY_PATH = os.getenv("CMAKE_LIBRARY_PATH")
33 variables.CMAKE_INCLUDE_PATH = os.getenv("CMAKE_INCLUDE_PATH")
34
35 util.variable_substitutions(variables, rockspec.variables)
36
37 local ok, err_msg = fs.is_tool_available(rockspec.variables.CMAKE, "CMake")
38 if not ok then
39 return nil, err_msg
40 end
41
42
43 local build_cmake = build.cmake
44 if type(build_cmake) == "string" then
45 local cmake_handler = assert((io.open(fs.current_dir() .. "/CMakeLists.txt", "w")))
46 cmake_handler:write(build.cmake)
47 cmake_handler:close()
48 end
49
50
51 local args = ""
52
53
54 if cfg.cmake_generator then
55 args = args .. ' -G"' .. cfg.cmake_generator .. '"'
56 elseif cfg.is_platform("windows") and cfg.target_cpu:match("x86_64$") then
57 args = args .. " -DCMAKE_GENERATOR_PLATFORM=x64"
58 end
59
60 for k, v in pairs(variables) do
61 args = args .. ' -D' .. k .. '="' .. tostring(v) .. '"'
62 end
63
64 if not fs.execute_string(rockspec.variables.CMAKE .. " -H. -Bbuild.luarocks " .. args) then
65 return nil, "Failed cmake."
66 end
67
68 local do_build, do_install
69 if rockspec:format_is_at_least("3.0") then
70 do_build = (build.build_pass == nil) and true or build.build_pass
71 do_install = (build.install_pass == nil) and true or build.install_pass
72 else
73 do_build = true
74 do_install = true
75 end
76
77 if do_build then
78 if not fs.execute_string(rockspec.variables.CMAKE .. " --build build.luarocks --config Release") then
79 return nil, "Failed building."
80 end
81 end
82 if do_install and not no_install then
83 if not fs.execute_string(rockspec.variables.CMAKE .. " --build build.luarocks --target install --config Release") then
84 return nil, "Failed installing."
85 end
86 end
87
88 return true
89end
90
91return cmake
diff --git a/src/luarocks/build/command.lua b/src/luarocks/build/command.lua
new file mode 100644
index 00000000..f795321b
--- /dev/null
+++ b/src/luarocks/build/command.lua
@@ -0,0 +1,51 @@
1
2
3
4
5local command = {CommandBuild = {Install = {}, }, }
6
7
8
9
10
11
12
13
14local fs = require("luarocks.fs")
15local util = require("luarocks.util")
16local cfg = require("luarocks.core.cfg")
17
18
19
20
21
22
23
24function command.run(rockspec, not_install)
25
26 local build = rockspec.build
27
28 util.variable_substitutions(build, rockspec.variables)
29
30 local env = {
31 CC = cfg.variables.CC,
32
33
34 }
35
36 if build.build_command then
37 util.printout(build.build_command)
38 if not fs.execute_env(env, build.build_command) then
39 return nil, "Failed building."
40 end
41 end
42 if build.install_command and not not_install then
43 util.printout(build.install_command)
44 if not fs.execute_env(env, build.install_command) then
45 return nil, "Failed installing."
46 end
47 end
48 return true
49end
50
51return command
diff --git a/src/luarocks/build/make.lua b/src/luarocks/build/make.lua
new file mode 100644
index 00000000..3110198c
--- /dev/null
+++ b/src/luarocks/build/make.lua
@@ -0,0 +1,107 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack
2
3
4
5local make = {MakeBuild = {Install = {}, }, }
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20local fs = require("luarocks.fs")
21local util = require("luarocks.util")
22local cfg = require("luarocks.core.cfg")
23
24
25
26
27
28
29
30
31
32
33
34
35
36local function make_pass(make_cmd, pass, target, variables)
37 local assignments = {}
38 for k, v in pairs(variables) do
39 table.insert(assignments, k .. "=" .. v)
40 end
41 if pass then
42 return fs.execute(make_cmd .. " " .. target, _tl_table_unpack(assignments))
43 else
44 return true
45 end
46end
47
48
49
50
51
52function make.run(rockspec, not_install)
53
54 local build = rockspec.build
55
56 if build.build_pass == nil then build.build_pass = true end
57 if build.install_pass == nil then build.install_pass = true end
58 build.build_variables = build.build_variables or {}
59 build.install_variables = build.install_variables or {}
60 build.build_target = build.build_target or ""
61 build.install_target = build.install_target or "install"
62 local makefile = build.makefile or cfg.makefile
63 if makefile then
64
65 build.build_target = "-f " .. makefile .. " " .. build.build_target
66 build.install_target = "-f " .. makefile .. " " .. build.install_target
67 end
68
69 if build.variables then
70 for var, val in pairs(build.variables) do
71 build.build_variables[var] = val
72 build.install_variables[var] = val
73 end
74 end
75
76 util.warn_if_not_used(build.build_variables, { CFLAGS = true }, "variable %s was not passed in build_variables")
77 util.variable_substitutions(build.build_variables, rockspec.variables)
78 util.variable_substitutions(build.install_variables, rockspec.variables)
79
80 local auto_variables = { "CC" }
81
82 for _, variable in ipairs(auto_variables) do
83 if not build.build_variables[variable] then
84 build.build_variables[variable] = rockspec.variables[variable]
85 end
86 if not build.install_variables[variable] then
87 build.install_variables[variable] = rockspec.variables[variable]
88 end
89 end
90
91
92 local make_cmd = cfg.make or rockspec.variables.MAKE
93
94 local ok = make_pass(make_cmd, build.build_pass, build.build_target, build.build_variables)
95 if not ok then
96 return nil, "Failed building."
97 end
98 if not not_install then
99 ok = make_pass(make_cmd, build.install_pass, build.install_target, build.install_variables)
100 if not ok then
101 return nil, "Failed installing."
102 end
103 end
104 return true
105end
106
107return make
diff --git a/src/luarocks/cmd.lua b/src/luarocks/cmd.lua
new file mode 100644
index 00000000..e9c81a0f
--- /dev/null
+++ b/src/luarocks/cmd.lua
@@ -0,0 +1,807 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local debug = _tl_compat and _tl_compat.debug or debug; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local loadfile = _tl_compat and _tl_compat.loadfile or loadfile; local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local _tl_table_pack = table.pack or function(...) return { n = select("#", ...), ... } end; local _tl_table_unpack = unpack or table.unpack; local xpcall = _tl_compat and _tl_compat.xpcall or xpcall
2
3local cmd = {Module = {}, }
4
5
6
7
8
9
10
11
12
13
14
15local manif = require("luarocks.manif")
16local config = require("luarocks.config")
17local util = require("luarocks.util")
18local path = require("luarocks.path")
19local cfg = require("luarocks.core.cfg")
20local dir = require("luarocks.dir")
21local fun = require("luarocks.fun")
22local fs = require("luarocks.fs")
23local argparse = require("luarocks.vendor.argparse")
24
25
26
27
28
29
30
31
32
33
34
35
36
37local hc_ok, hardcoded = pcall(require, "luarocks.core.hardcoded")
38if not hc_ok then
39 hardcoded = {}
40end
41
42local program = util.this_program("luarocks")
43
44cmd.errorcodes = {
45 OK = 0,
46 UNSPECIFIED = 1,
47 PERMISSIONDENIED = 2,
48 CONFIGFILE = 3,
49 LOCK = 4,
50 CRASH = 99,
51}
52
53local function check_popen()
54 local popen_ok, popen_result = pcall(io.popen, "")
55 if popen_ok then
56 if popen_result then
57 popen_result:close()
58 end
59 else
60 io.stderr:write("Your version of Lua does not support io.popen,\n")
61 io.stderr:write("which is required by LuaRocks. Please check your Lua installation.\n")
62 os.exit(cmd.errorcodes.UNSPECIFIED)
63 end
64end
65
66local process_tree_args
67do
68 local function replace_tree(args, root, tree)
69 root = dir.normalize(root)
70 args.tree = root
71 path.use_tree(tree or root)
72 end
73
74 local function strip_trailing_slashes()
75 local cfg_root_dir = cfg.root_dir
76 if type(cfg_root_dir) == "string" then
77 cfg.root_dir = (cfg.root_dir):gsub("/+$", "")
78 else
79 (cfg.root_dir).root = (cfg.root_dir).root:gsub("/+$", "")
80 end
81 cfg.rocks_dir = cfg.rocks_dir:gsub("/+$", "")
82 cfg.deploy_bin_dir = cfg.deploy_bin_dir:gsub("/+$", "")
83 cfg.deploy_lua_dir = cfg.deploy_lua_dir:gsub("/+$", "")
84 cfg.deploy_lib_dir = cfg.deploy_lib_dir:gsub("/+$", "")
85 end
86
87 local function set_named_tree(args, name)
88 for _, tree in ipairs(cfg.rocks_trees) do
89 if type(tree) == "table" and name == tree.name then
90 if not tree.root then
91 return nil, "Configuration error: tree '" .. tree.name .. "' has no 'root' field."
92 end
93 replace_tree(args, tree.root, tree)
94 return true
95 end
96 end
97 return false
98 end
99
100 process_tree_args = function(args, project_dir)
101
102 if args.global then
103 local ok, err = set_named_tree(args, "system")
104 if not ok then
105 return nil, err
106 end
107 elseif args.tree then
108 local named = set_named_tree(args, args.tree)
109 if not named then
110 local root_dir = fs.absolute_name(args.tree)
111 replace_tree(args, root_dir)
112 if (args.deps_mode or cfg.deps_mode) ~= "order" then
113 table.insert(cfg.rocks_trees, 1, { name = "arg", root = root_dir })
114 end
115 end
116 elseif args["local"] then
117 if fs.is_superuser() then
118 return nil, "The --local flag is meant for operating in a user's home directory.\n" ..
119 "You are running as a superuser, which is intended for system-wide operation.\n" ..
120 "To force using the superuser's home, use --tree explicitly."
121 else
122 local ok, err = set_named_tree(args, "user")
123 if not ok then
124 return nil, err
125 end
126 end
127 elseif args.project_tree then
128 local tree = args.project_tree
129 table.insert(cfg.rocks_trees, 1, { name = "project", root = tree })
130 manif.load_rocks_tree_manifests()
131 path.use_tree(tree)
132 elseif project_dir then
133 local project_tree = project_dir .. "/lua_modules"
134 table.insert(cfg.rocks_trees, 1, { name = "project", root = project_tree })
135 manif.load_rocks_tree_manifests()
136 path.use_tree(project_tree)
137 elseif cfg.local_by_default then
138 local ok, err = set_named_tree(args, "user")
139 if not ok then
140 return nil, err
141 end
142 else
143 local trees = cfg.rocks_trees
144 path.use_tree(trees[#trees])
145 end
146
147 strip_trailing_slashes()
148
149 cfg.variables.ROCKS_TREE = cfg.rocks_dir
150 cfg.variables.SCRIPTS_DIR = cfg.deploy_bin_dir
151
152 return true
153 end
154end
155
156local function process_server_args(args)
157 if args.server then
158 local protocol, pathname = dir.split_url(args.server)
159 table.insert(cfg.rocks_servers, 1, protocol .. "://" .. pathname)
160 end
161
162 if args.dev then
163 for i, server in ipairs(cfg.rocks_servers) do
164 if type(server) == "string" then
165 cfg.rocks_servers[i] = dir.path(server, "dev")
166 else
167 for j, mirror in ipairs(server) do
168 server[j] = dir.path(mirror, "dev")
169 end
170 end
171 end
172 end
173
174 if args.only_server then
175 if args.dev then
176 return nil, "--only-server cannot be used with --dev"
177 end
178 if args.server then
179 return nil, "--only-server cannot be used with --server"
180 end
181 cfg.rocks_servers = { args.only_server }
182 end
183
184 return true
185end
186
187local function error_handler(err)
188 if not debug then
189 return err
190 end
191 local mode = "Arch.: " .. (cfg and cfg.arch or "unknown")
192 if package.config:sub(1, 1) == "\\" then
193 if cfg and cfg.fs_use_modules then
194 mode = mode .. " (fs_use_modules = true)"
195 end
196 end
197 if cfg and cfg.is_binary then
198 mode = mode .. " (binary)"
199 end
200 return debug.traceback("LuaRocks " .. cfg.program_version ..
201 " bug (please report at https://github.com/luarocks/luarocks/issues).\n" ..
202 mode .. "\n" .. err, 2)
203end
204
205
206
207
208local function die(message, exitcode)
209 assert(type(message) == "string", "bad error, expected string, got: " .. type(message))
210 assert(exitcode == nil or type(exitcode) == "number", "bad error, expected number, got: " .. type(exitcode) .. " - " .. tostring(exitcode))
211 util.printerr("\nError: " .. message)
212
213 local ok, err = xpcall(util.run_scheduled_functions, error_handler)
214 if not ok then
215 util.printerr("\nError: " .. err)
216 exitcode = cmd.errorcodes.CRASH
217 end
218
219 os.exit(exitcode or cmd.errorcodes.UNSPECIFIED)
220end
221
222local function search_lua(lua_version, verbose, search_at)
223 if search_at then
224 return util.find_lua(search_at, lua_version, verbose)
225 end
226
227 local path_sep = (package.config:sub(1, 1) == "\\" and ";" or ":")
228 local all_tried = {}
229 for bindir in (os.getenv("PATH") or ""):gmatch("[^" .. path_sep .. "]+") do
230 local searchdir = (bindir:gsub("[\\/]+bin[\\/]?$", ""))
231 local detected, tried = util.find_lua(searchdir, lua_version)
232 if detected then
233 return detected
234 else
235 table.insert(all_tried, tried)
236 end
237 end
238 return nil, "Could not find " ..
239 (lua_version and "Lua " .. lua_version or "Lua") ..
240 " in PATH." ..
241 (verbose and " Tried:\n" .. table.concat(all_tried, "\n") or "")
242end
243
244local init_config
245do
246 local detect_config_via_args
247 do
248 local function find_project_dir(project_tree)
249 if project_tree then
250 return project_tree:gsub("[/\\][^/\\]+$", ""), true
251 else
252 local try = "."
253 for _ = 1, 10 do
254 if util.exists(try .. "/.luarocks") and util.exists(try .. "/lua_modules") then
255 return dir.normalize(try), false
256 elseif util.exists(try .. "/.luarocks-no-project") then
257 break
258 end
259 try = try .. "/.."
260 end
261 end
262 return nil
263 end
264
265 local function find_default_lua_version(args, project_dir)
266 if hardcoded.FORCE_CONFIG then
267 return nil
268 end
269
270 local dirs = {}
271 if project_dir then
272 table.insert(dirs, dir.path(project_dir, ".luarocks"))
273 end
274 if cfg.homeconfdir then
275 table.insert(dirs, cfg.homeconfdir)
276 end
277 table.insert(dirs, cfg.sysconfdir)
278 for _, d in ipairs(dirs) do
279 local f = dir.path(d, "default-lua-version.lua")
280 local mod, _ = loadfile(f, "t")
281 if mod then
282 local pok, ver = pcall(mod)
283 if pok and type(ver) == "string" and ver:match("%d+.%d+") then
284 if args.verbose then
285 util.printout("Defaulting to Lua " .. ver .. " based on " .. f .. " ...")
286 end
287 return ver
288 end
289 end
290 end
291 return nil
292 end
293
294 local function find_version_from_config(dirname)
295 return fun.find(util.lua_versions("descending"), function(v)
296 if util.exists(dir.path(dirname, ".luarocks", "config-" .. v .. ".lua")) then
297 return v
298 end
299 end)
300 end
301
302 local function detect_lua_via_args(args, project_dir)
303 local lua_version = args.lua_version or
304 find_default_lua_version(args, project_dir) or
305 (project_dir and find_version_from_config(project_dir))
306
307 if args.lua_dir then
308 local detected, err = util.find_lua(args.lua_dir, lua_version)
309 if not detected then
310 local suggestion = (not args.lua_version) and
311 "\nYou may want to specify a different Lua version with --lua-version\n" or
312 ""
313 die(err .. suggestion)
314 end
315 return detected
316 end
317
318 if lua_version then
319 local detected = search_lua(lua_version)
320 if detected then
321 return detected
322 end
323 return {
324 lua_version = lua_version,
325 }
326 end
327
328 return {}
329 end
330
331 detect_config_via_args = function(args)
332 local project_dir, given
333 if not args.no_project then
334 project_dir, given = find_project_dir(args.project_tree)
335 end
336
337 local detected = detect_lua_via_args(args, project_dir)
338 if args.lua_version then
339 detected.given_lua_version = args.lua_version
340 end
341 if args.lua_dir then
342 detected.given_lua_dir = args.lua_dir
343 end
344 if given then
345 detected.given_project_dir = project_dir
346 end
347 detected.project_dir = project_dir
348 return detected
349 end
350 end
351
352 init_config = function(args)
353 local detected = detect_config_via_args(args)
354
355 local ok, err = cfg.init(detected, util.warning)
356 if not ok then
357 return nil, err
358 end
359
360 return (detected.lua_dir ~= nil)
361 end
362end
363
364local variables_help = [[
365Variables:
366 Variables from the "variables" table of the configuration file can be
367 overridden with VAR=VALUE assignments.
368
369]]
370
371local lua_example = package.config:sub(1, 1) == "\\" and
372"<d:\\path\\lua.exe>" or
373"</path/lua>"
374
375local function show_status(file, status, err)
376 return (file and file .. " " or "") .. (status and "(ok)" or ("(" .. (err or "not found") .. ")"))
377end
378
379local function use_to_fix_location(key, what)
380 local buf = " ****************************************\n"
381 buf = buf .. " Use the command\n\n"
382 buf = buf .. " luarocks config " .. key .. " " .. (what or "<dir>") .. "\n\n"
383 buf = buf .. " to fix the location\n"
384 buf = buf .. " ****************************************\n"
385 return buf
386end
387
388local function get_config_text(cfg)
389 local deps = require("luarocks.deps")
390
391 local libdir_ok = deps.check_lua_libdir(cfg.variables)
392 local incdir_ok = deps.check_lua_incdir(cfg.variables)
393 local lua_ok = cfg.variables.LUA and fs.exists(cfg.variables.LUA)
394
395 local buf = "Configuration:\n"
396 buf = buf .. " Lua:\n"
397 buf = buf .. " Version : " .. cfg.lua_version .. "\n"
398 if cfg.luajit_version then
399 buf = buf .. " LuaJIT : " .. cfg.luajit_version .. "\n"
400 end
401 buf = buf .. " LUA : " .. show_status(cfg.variables.LUA, lua_ok, "interpreter not found") .. "\n"
402 if not lua_ok then
403 buf = buf .. use_to_fix_location("variables.LUA", lua_example)
404 end
405 buf = buf .. " LUA_INCDIR : " .. show_status(cfg.variables.LUA_INCDIR, incdir_ok, "lua.h not found") .. "\n"
406 if lua_ok and not incdir_ok then
407 buf = buf .. use_to_fix_location("variables.LUA_INCDIR")
408 end
409 buf = buf .. " LUA_LIBDIR : " .. show_status(cfg.variables.LUA_LIBDIR, libdir_ok, "Lua library itself not found") .. "\n"
410 if lua_ok and not libdir_ok then
411 buf = buf .. use_to_fix_location("variables.LUA_LIBDIR")
412 end
413
414 buf = buf .. "\n Configuration files:\n"
415 local conf = cfg.config_files
416 buf = buf .. " System : " .. show_status(fs.absolute_name(conf.system.file), conf.system.found) .. "\n"
417 if conf.user.file then
418 buf = buf .. " User : " .. show_status(fs.absolute_name(conf.user.file), conf.user.found) .. "\n"
419 else
420 buf = buf .. " User : disabled in this LuaRocks installation.\n"
421 end
422 if conf.project then
423 buf = buf .. " Project : " .. show_status(fs.absolute_name(conf.project.file), conf.project.found) .. "\n"
424 end
425 buf = buf .. "\n Rocks trees in use: \n"
426 for _, tree in ipairs(cfg.rocks_trees) do
427 if type(tree) == "string" then
428 buf = buf .. " " .. fs.absolute_name(tree)
429 else
430 local name = tree.name and " (\"" .. tree.name .. "\")" or ""
431 buf = buf .. " " .. fs.absolute_name(tree.root) .. name
432 end
433 buf = buf .. "\n"
434 end
435
436 return buf
437end
438
439local function get_parser(description, cmd_modules)
440 local basename = dir.base_name(program)
441 local parser = argparse(
442 basename, "LuaRocks " .. cfg.program_version .. ", the Lua package manager\n\n" ..
443 program .. " - " .. description, variables_help .. "Run '" .. basename ..
444 "' without any arguments to see the configuration."):
445 help_max_width(80):
446 add_help_command():
447 add_complete_command({
448 help_max_width = 100,
449 summary = "Output a shell completion script.",
450 description = [[
451Output a shell completion script.
452
453Enabling completions for Bash:
454
455 Add the following line to your ~/.bashrc:
456 source <(]] .. basename .. [[ completion bash)
457 or save the completion script to the local completion directory:
458 ]] .. basename .. [[ completion bash > ~/.local/share/bash-completion/completions/]] .. basename .. [[
459
460
461Enabling completions for Zsh:
462
463 Save the completion script to a file in your $fpath.
464 You can add a new directory to your $fpath by adding e.g.
465 fpath=(~/.zfunc $fpath)
466 to your ~/.zshrc.
467 Then run:
468 ]] .. basename .. [[ completion zsh > ~/.zfunc/_]] .. basename .. [[
469
470
471Enabling completion for Fish:
472
473 Add the following line to your ~/.config/fish/config.fish:
474 ]] .. basename .. [[ completion fish | source
475 or save the completion script to the local completion directory:
476 ]] .. basename .. [[ completion fish > ~/.config/fish/completions/]] .. basename .. [[.fish
477]], }):
478 command_target("command"):
479 require_command(false)
480
481 parser:flag("--version", "Show version info and exit."):
482 action(function()
483 util.printout(program .. " " .. cfg.program_version)
484 util.printout(description)
485 util.printout()
486 os.exit(cmd.errorcodes.OK)
487 end)
488 parser:flag("--dev", "Enable the sub-repositories in rocks servers for " ..
489 "rockspecs of in-development versions.")
490 parser:option("--server", "Fetch rocks/rockspecs from this server " ..
491 "(takes priority over config file)."):
492 hidden_name("--from")
493 parser:option("--only-server", "Fetch rocks/rockspecs from this server only " ..
494 "(overrides any entries in the config file)."):
495 argname("<server>"):
496 hidden_name("--only-from")
497 parser:option("--only-sources", "Restrict downloads to paths matching the given URL."):
498 argname("<url>"):
499 hidden_name("--only-sources-from")
500 parser:option("--namespace", "Specify the rocks server namespace to use."):
501 convert(string.lower)
502 parser:option("--lua-dir", "Which Lua installation to use."):
503 argname("<prefix>")
504 parser:option("--lua-version", "Which Lua version to use."):
505 argname("<ver>"):
506 convert(function(s) return (s:match("^%d+%.%d+$")) end)
507 parser:option("--tree", "Which tree to operate on."):
508 hidden_name("--to")
509 parser:flag("--local", "Use the tree in the user's home directory.\n" ..
510 "To enable it, see '" .. program .. " help path'.")
511 parser:flag("--global", "Use the system tree when `local_by_default` is `true`.")
512 parser:flag("--no-project", "Do not use project tree even if running from a project folder.")
513 parser:flag("--force-lock", "Attempt to overwrite the lock for commands " ..
514 "that require exclusive access, such as 'install'")
515 parser:flag("--verbose", "Display verbose output of commands executed.")
516 parser:option("--timeout", "Timeout on network operations, in seconds.\n" ..
517 "0 means no timeout (wait forever). Default is " ..
518 tostring(cfg.connection_timeout) .. "."):
519 argname("<seconds>"):
520 convert(tonumber)
521
522
523 parser:option("--project-tree"):hidden(true)
524
525 for _, module in util.sortedpairs(cmd_modules) do
526 module.add_to_parser(parser)
527 end
528
529 return parser
530end
531
532local function get_first_arg()
533 if not arg then
534 return
535 end
536 local first_arg = arg[0]
537 local i = -1
538 while arg[i] do
539 first_arg = arg[i]
540 i = i - 1
541 end
542 return first_arg
543end
544
545
546
547
548
549
550
551
552
553function cmd.run_command(description, commands, external_namespace, ...)
554
555 check_popen()
556
557
558 cfg.init()
559
560 fs.init()
561
562 for _, module_name in ipairs(fs.modules(external_namespace)) do
563 if not commands[module_name] then
564 commands[module_name] = external_namespace .. "." .. module_name
565 end
566 end
567
568 local cmd_modules = {}
569 for name, module in pairs(commands) do
570 local pok, mod = pcall(require, module)
571 if pok and type(mod) == "table" then
572 local original_command = mod.command
573 if original_command then
574 if not mod.add_to_parser then
575 mod.add_to_parser = function(parser)
576 parser:command(name, mod.help, util.see_also()):
577 summary(mod.help_summary):
578 handle_options(false):
579 argument("input"):
580 args("*")
581 end
582
583 mod.command = function(args)
584 return original_command(args, _tl_table_unpack(args.input))
585 end
586 end
587 cmd_modules[name] = mod
588 else
589 util.warning("command module " .. module .. " does not implement command(), skipping")
590 end
591 else
592 util.warning("failed to load command module " .. module .. ": " .. tostring(mod))
593 end
594 end
595
596 local function process_cmdline_vars(...)
597 local args = _tl_table_pack(...)
598 local cmdline_vars = {}
599 local last = args.n
600 for i = 1, args.n do
601 if args[i] == "--" then
602 last = i - 1
603 break
604 end
605 end
606 for i = last, 1, -1 do
607 local arg = args[i]
608 if arg:match("^[^-][^=]*=") then
609 local var, val = arg:match("^([A-Z_][A-Z0-9_]*)=(.*)")
610 if val then
611 cmdline_vars[var] = val
612 table.remove(args, i)
613 else
614 die("Invalid assignment: " .. arg)
615 end
616 end
617 end
618
619 return args, cmdline_vars
620 end
621
622 local cmdline_args, cmdline_vars = process_cmdline_vars(...)
623 local parser = get_parser(description, cmd_modules)
624 local args = parser:parse(cmdline_args)
625
626
627 if args.nodeps then
628 args.deps_mode = "none"
629 end
630
631 if args.timeout then
632 cfg.connection_timeout = args.timeout
633 end
634
635 if args.command == "config" then
636 if args.key == "lua_version" and args.value then
637 args.lua_version = args.value
638 elseif args.key == "lua_dir" and args.value then
639 args.lua_dir = args.value
640 end
641 end
642
643
644 local lua_found, err = init_config(args)
645 if err then
646 die(err)
647 end
648
649
650
651
652 fs.init()
653
654
655
656 local tried
657 if not lua_found then
658 local detected
659 detected, tried = search_lua(cfg.lua_version, args.verbose, cfg.variables.LUA_DIR)
660 if detected then
661 lua_found = true
662 cfg.variables.LUA = detected.lua
663 cfg.variables.LUA_DIR = detected.lua_dir
664 cfg.variables.LUA_BINDIR = detected.lua_bindir
665 if args.lua_dir then
666 cfg.variables.LUA_INCDIR = nil
667 cfg.variables.LUA_LIBDIR = nil
668 end
669 else
670 cfg.variables.LUA = nil
671 cfg.variables.LUA_DIR = nil
672 cfg.variables.LUA_BINDIR = nil
673 cfg.variables.LUA_INCDIR = nil
674 cfg.variables.LUA_LIBDIR = nil
675 end
676 end
677
678 if lua_found then
679 assert(cfg.variables.LUA)
680 else
681
682
683
684
685 if not cfg.variables.LUA then
686 local first_arg = get_first_arg()
687 local bin_dir = dir.dir_name(fs.absolute_name(first_arg))
688 local exe = dir.base_name(first_arg)
689 exe = exe:match("rocks") and ("lua" .. (cfg.arch:match("win") and ".exe" or "")) or exe
690 local full_path = dir.path(bin_dir, exe)
691 if util.check_lua_version(full_path, cfg.lua_version) then
692 cfg.variables.LUA = dir.path(bin_dir, exe)
693 cfg.variables.LUA_DIR = bin_dir:gsub("[/\\]bin[/\\]?$", "")
694 cfg.variables.LUA_BINDIR = bin_dir
695 cfg.variables.LUA_INCDIR = nil
696 cfg.variables.LUA_LIBDIR = nil
697 end
698 end
699 end
700
701 cfg.lua_found = lua_found
702
703 if cfg.project_dir then
704 cfg.project_dir = fs.absolute_name(cfg.project_dir)
705 end
706
707 if args.verbose then
708 cfg.verbose = true
709 print(("-"):rep(79))
710 print("Current configuration:")
711 print(("-"):rep(79))
712 print(config.to_string(cfg))
713 print(("-"):rep(79))
714 fs.verbose()
715 end
716
717 if (not fs.current_dir()) or fs.current_dir() == "" then
718 die("Current directory does not exist. Please run LuaRocks from an existing directory.")
719 end
720
721 local ok, err = process_tree_args(args, cfg.project_dir)
722 if not ok then
723 die(err)
724 end
725
726 ok, err = process_server_args(args)
727 if not ok then
728 die(err)
729 end
730
731 if args.only_sources then
732 cfg.only_sources_from = args.only_sources
733 end
734
735 for k, v in pairs(cmdline_vars) do
736 cfg.variables[k] = v
737 end
738
739
740 if fs.is_superuser() then
741 cfg.local_cache = dir.path(fs.system_cache_dir(), "luarocks")
742 end
743
744 if args.no_manifest then
745 cfg.no_manifest = true
746 end
747
748 if not args.command then
749 parser:epilog(variables_help .. get_config_text(cfg))
750 util.printout()
751 util.printout(parser:get_help())
752 util.printout()
753 os.exit(cmd.errorcodes.OK)
754 end
755
756 if not cfg.variables["LUA"] and args.command ~= "config" and args.command ~= "help" then
757 local flag = (not cfg.project_tree) and
758 "--local " or
759 ""
760 if args.lua_version then
761 flag = "--lua-version=" .. args.lua_version .. " " .. flag
762 end
763 die((tried or "Lua interpreter not found.") ..
764 "\nPlease set your Lua interpreter with:\n\n" ..
765 " luarocks " .. flag .. "config variables.LUA " .. lua_example .. "\n")
766 end
767
768 local cmd_mod = cmd_modules[args.command]
769
770 local lock
771 if cmd_mod.needs_lock and cmd_mod.needs_lock(args) then
772 local ok, err = fs.check_command_permissions(args)
773 if not ok then
774 die(err, cmd.errorcodes.PERMISSIONDENIED)
775 end
776
777 lock, err = fs.lock_access(path.root_dir(cfg.root_dir), args.force_lock)
778 if not lock then
779 err = args.force_lock and
780 ("failed to force the lock" .. (err and ": " .. err or "")) or
781 (err and err ~= "File exists") and
782 err or
783 "try --force-lock to overwrite the lock"
784
785 die("command '" .. args.command .. "' " ..
786 "requires exclusive write access to " .. path.root_dir(cfg.root_dir) .. " - " ..
787 err, cmd.errorcodes.LOCK)
788 end
789 end
790
791 local call_ok, ok, err, exitcode = xpcall(function()
792 return cmd_mod.command(args)
793 end, error_handler)
794
795 if lock then
796 fs.unlock_access(lock)
797 end
798
799 if not call_ok then
800 die(tostring(ok), cmd.errorcodes.CRASH)
801 elseif not ok then
802 die(err, exitcode)
803 end
804 util.run_scheduled_functions()
805end
806
807return cmd
diff --git a/src/luarocks/cmd/build.lua b/src/luarocks/cmd/build.lua
new file mode 100644
index 00000000..fb894c20
--- /dev/null
+++ b/src/luarocks/cmd/build.lua
@@ -0,0 +1,211 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string
2
3
4local cmd_build = {}
5
6
7
8local pack = require("luarocks.pack")
9local path = require("luarocks.path")
10local dir = require("luarocks.dir")
11local util = require("luarocks.util")
12local fetch = require("luarocks.fetch")
13local fs = require("luarocks.fs")
14local deps = require("luarocks.deps")
15local remove = require("luarocks.remove")
16local cfg = require("luarocks.core.cfg")
17local build = require("luarocks.build")
18local search = require("luarocks.search")
19local make = require("luarocks.cmd.make")
20local repos = require("luarocks.repos")
21
22
23
24
25
26
27
28
29
30function cmd_build.add_to_parser(parser)
31 local cmd = parser:command("build", "Build and install a rock, compiling its C parts if any.\n" ..
32 "If the sources contain a luarocks.lock file, uses it as an authoritative source for " ..
33 "exact version of dependencies.\n" ..
34 "If no arguments are given, behaves as luarocks make.", util.see_also()):
35 summary("Build/compile a rock.")
36
37 cmd:argument("rock", "A rockspec file, a source rock file, or the name of " ..
38 "a rock to be fetched from a repository."):
39 args("?"):
40 action(util.namespaced_name_action)
41 cmd:argument("version", "Rock version."):
42 args("?")
43
44 cmd:flag("--only-deps --deps-only", "Install only the dependencies of the rock.")
45 cmd:option("--branch", "Override the `source.branch` field in the loaded " ..
46 "rockspec. Allows to specify a different branch to fetch. Particularly " ..
47 'for "dev" rocks.'):
48 argname("<name>")
49 cmd:flag("--pin", "Create a luarocks.lock file listing the exact " ..
50 "versions of each dependency found for this rock (recursively), " ..
51 "and store it in the rock's directory. " ..
52 "Ignores any existing luarocks.lock file in the rock's sources.")
53 make.cmd_options(cmd)
54end
55
56
57
58
59
60
61local function build_rock(rock_filename, opts)
62
63 local cwd = fs.absolute_name(dir.path("."))
64
65 local ok, err, errcode
66
67 local unpack_dir
68 unpack_dir, err, errcode = fetch.fetch_and_unpack_rock(rock_filename, nil, opts.verify)
69 if not unpack_dir then
70 return nil, err, errcode
71 end
72
73 local rockspec_filename = path.rockspec_name_from_rock(rock_filename)
74
75 ok, err = fs.change_dir(unpack_dir)
76 if not ok then return nil, err end
77
78 local rockspec
79 rockspec, err, errcode = fetch.load_rockspec(rockspec_filename)
80 if not rockspec then
81 return nil, err, errcode
82 end
83
84 local n, v = build.build_rockspec(rockspec, opts, cwd)
85
86 ok, err, errcode = n ~= nil, v, nil
87
88 fs.pop_dir()
89 return ok, err, errcode
90end
91
92local function do_build(name, namespace, version, opts)
93
94 local url, err
95 if name:match("%.rockspec$") or name:match("%.rock$") then
96 url = name
97 else
98 url, err = search.find_src_or_rockspec(name, namespace, version, opts.check_lua_versions)
99 if not url then
100 return nil, err
101 end
102 end
103
104 name, version = path.parse_name(url)
105 if name and repos.is_installed(name, version) then
106 if not opts.rebuild then
107 util.printout(name .. " " .. version .. " is already installed in " .. path.root_dir(cfg.root_dir))
108 util.printout("Use --force to reinstall.")
109 return name, version, "skip"
110 end
111 end
112
113 if url:match("%.rockspec$") then
114 local cwd = fs.absolute_name(dir.path("."))
115 local rockspec, err = fetch.load_rockspec(url, nil, opts.verify)
116 if not rockspec then
117 return nil, err
118 end
119 return build.build_rockspec(rockspec, opts, cwd)
120 end
121
122 if url:match("%.src%.rock$") then
123 opts.need_to_fetch = false
124 end
125
126 local ok, err, errcode = build_rock(url, opts)
127 if not ok then
128 return nil, err, errcode
129 end
130 return name, version
131end
132
133
134
135
136
137
138
139function cmd_build.command(args)
140 if not args.rock then
141 return make.command(args)
142 end
143
144 local opts = {
145 need_to_fetch = true,
146 minimal_mode = false,
147 deps_mode = deps.get_deps_mode(args),
148 build_only_deps = not not (args.only_deps and not args.pack_binary_rock),
149 namespace = args.namespace,
150 branch = args.branch,
151 verify = not not args.verify,
152 check_lua_versions = not not args.check_lua_versions,
153 pin = not not args.pin,
154 rebuild = not not (args.force or args.force_fast),
155 no_install = false,
156 }
157
158 if args.sign and not args.pack_binary_rock then
159 return nil, "In the build command, --sign is meant to be used only with --pack-binary-rock"
160 end
161
162 if args.pack_binary_rock then
163 return pack.pack_binary_rock(args.rock, args.namespace, args.version, args.sign, function()
164 local name, version = do_build(args.rock, args.namespace, args.version, opts)
165 if name and args.no_doc then
166 util.remove_doc_dir(name, version)
167 end
168 return name, version
169 end)
170 end
171
172 local name, version, skip = do_build(args.rock, args.namespace, args.version, opts)
173 if not name then
174 return nil, version
175 end
176 if skip == "skip" then
177 return name ~= nil, version
178 end
179
180 if args.no_doc then
181 util.remove_doc_dir(name, version)
182 end
183
184 if opts.build_only_deps then
185 util.printout("Stopping after installing dependencies for " .. name .. " " .. version)
186 util.printout()
187 else
188 if (not args.keep) and not cfg.keep_other_versions then
189 local ok, err, warn = remove.remove_other_versions(name, version, args.force, args.force_fast)
190 if not ok then
191 return nil, err
192 elseif warn then
193 util.printerr(err)
194 end
195 end
196 end
197
198 if opts.deps_mode ~= "none" then
199 deps.check_dependencies(nil, deps.get_deps_mode(args))
200 end
201 return name ~= nil, version
202end
203
204cmd_build.needs_lock = function(args)
205 if args.pack_binary_rock then
206 return false
207 end
208 return true
209end
210
211return cmd_build
diff --git a/src/luarocks/cmd/config.lua b/src/luarocks/cmd/config.lua
new file mode 100644
index 00000000..b0b04913
--- /dev/null
+++ b/src/luarocks/cmd/config.lua
@@ -0,0 +1,402 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string
2
3local config_cmd = {}
4
5
6local persist = require("luarocks.persist")
7local config = require("luarocks.config")
8local cfg = require("luarocks.core.cfg")
9local util = require("luarocks.util")
10local deps = require("luarocks.deps")
11local dir = require("luarocks.dir")
12local fs = require("luarocks.fs")
13local json = require("luarocks.vendor.dkjson")
14
15
16
17
18
19
20
21function config_cmd.add_to_parser(parser)
22 local cmd = parser:command("config", [[
23Query information about the LuaRocks configuration.
24
25* When given a configuration key, it prints the value of that key according to
26 the currently active configuration (taking into account all config files and
27 any command-line flags passed)
28
29 Examples:
30 luarocks config variables.LUA_INCDIR
31 luarocks config lua_version
32
33* When given a configuration key and a value, it overwrites the config file (see
34 the --scope option below to determine which) and replaces the value of the
35 given key with the given value.
36
37 * `lua_dir` is a special key as it checks for a valid Lua installation
38 (equivalent to --lua-dir) and sets several keys at once.
39 * `lua_version` is a special key as it changes the default Lua version
40 used by LuaRocks commands (equivalent to passing --lua-version).
41
42 Examples:
43 luarocks config variables.OPENSSL_DIR /usr/local/openssl
44 luarocks config lua_dir /usr/local
45 luarocks config lua_version 5.3
46
47* When given a configuration key and --unset, it overwrites the config file (see
48 the --scope option below to determine which) and deletes that key from the
49 file.
50
51 Example: luarocks config variables.OPENSSL_DIR --unset
52
53* When given no arguments, it prints the entire currently active configuration,
54 resulting from reading the config files from all scopes.
55
56 Example: luarocks config]], util.see_also([[
57 https://github.com/luarocks/luarocks/wiki/Config-file-format
58 for detailed information on the LuaRocks config file format.
59]])):
60 summary("Query information about the LuaRocks configuration.")
61
62 cmd:argument("key", "The configuration key."):
63 args("?")
64 cmd:argument("value", "The configuration value."):
65 args("?")
66
67 cmd:option("--scope", "The scope indicates which config file should be rewritten.\n" ..
68 '* Using a wrapper created with `luarocks init`, the default is "project".\n' ..
69 '* Using --local (or when `local_by_default` is `true`), the default is "user".\n' ..
70 '* Otherwise, the default is "system".'):
71 choices({ "system", "user", "project" })
72 cmd:flag("--unset", "Delete the key from the configuration file.")
73 cmd:flag("--json", "Output as JSON.")
74
75
76 cmd:flag("--lua-incdir"):hidden(true)
77 cmd:flag("--lua-libdir"):hidden(true)
78 cmd:flag("--lua-ver"):hidden(true)
79 cmd:flag("--system-config"):hidden(true)
80 cmd:flag("--user-config"):hidden(true)
81 cmd:flag("--rock-trees"):hidden(true)
82end
83
84local function config_file(conf)
85 print(dir.normalize(conf.file))
86 if conf.found then
87 return true
88 else
89 return nil, "file not found"
90 end
91end
92
93local function traverse_varstring(var, tbl, fn, missing_parent)
94 local k
95 local r
96 k, r = var:match("^%[([0-9]+)%]%.(.*)$")
97 if k then
98 k = tonumber(k)
99 else
100 k, r = var:match("^([^.[]+)%.(.*)$")
101 if not k then
102 k, r = var:match("^([^[]+)(%[.*)$")
103 end
104 end
105
106 if k then
107 if not tbl[k] and missing_parent then
108 missing_parent(tbl, k)
109 end
110
111 if tbl[k] then
112 return traverse_varstring(r, tbl[k], fn, missing_parent)
113 else
114 return nil, "Unknown entry " .. tostring(k)
115 end
116 end
117
118 local i = var:match("^%[([0-9]+)%]$")
119 if i then
120 return fn(tbl, tonumber(i))
121 end
122
123 return fn(tbl, var)
124end
125
126local function print_json(value)
127 print(json.encode(value))
128 return true
129end
130
131local function print_entry(var, tbl, is_json)
132 return traverse_varstring(var, tbl, function(t, k)
133 if not t[k] then
134 return nil, "Unknown entry " .. k
135 end
136 local val = t[k]
137
138 if not config.should_skip(var, val) then
139 if is_json then
140 return print_json(val)
141 elseif type(val) == "string" then
142 print(val)
143 else
144 persist.write_value(io.stdout, val)
145 end
146 end
147 return true
148 end)
149end
150
151local function infer_type(var)
152 local typ
153 traverse_varstring(var, cfg, function(t, k)
154 if t[k] then
155 typ = type(t[k])
156 end
157 end)
158 return typ
159end
160
161local function write_entries(keys, scope, do_unset)
162 local wrote = {}
163 if scope == "project" and not cfg.config_files.project then
164 return nil, "Current directory is not part of a project. You may want to run `luarocks init`."
165 end
166
167 local file_name = (cfg.config_files)[scope].file
168
169 local tbl, err = persist.load_config_file_if_basic(file_name, cfg)
170 if not tbl then
171 return nil, err
172 end
173
174 for var, val in util.sortedpairs(keys) do
175 traverse_varstring(var, tbl, function(t, k)
176 if do_unset then
177 t[k] = nil
178 wrote[var] = ""
179 else
180 local typ = infer_type(var)
181 local v
182 if typ == "number" and tonumber(val) then
183 v = tonumber(val)
184 elseif typ == "boolean" and val == "true" then
185 v = true
186 elseif typ == "boolean" and val == "false" then
187 v = false
188 else
189 v = val
190 end
191 t[k] = v
192 wrote[var] = v
193 end
194 return true
195 end, function(p, k)
196 p[k] = {}
197 end)
198 end
199
200 local ok, err = fs.make_dir(dir.dir_name(file_name))
201 if not ok then
202 return nil, err
203 end
204
205 ok, err = persist.save_from_table(file_name, tbl)
206 if ok then
207 print(do_unset and "Removed" or "Wrote")
208 for var, val in util.sortedpairs(wrote) do
209 if do_unset then
210 print(("\t%s"):format(var))
211 else
212 if type(val) == "string" then
213 print(("\t%s = %q"):format(var, val))
214 else
215 print(("\t%s = %s"):format(var, tostring(val)))
216 end
217 end
218 end
219 print(do_unset and "from" or "to")
220 print("\t" .. file_name)
221 return true
222 else
223 return nil, err
224 end
225end
226
227local function get_scope(args)
228 return args.scope or
229 (args["local"] and "user") or
230 (args.project_tree and "project") or
231 (cfg.local_by_default and "user") or
232 (fs.is_writable(cfg.config_files["system"].file) and "system") or
233 "user"
234end
235
236local function report_on_lua_incdir_config(value)
237 local variables = {
238 ["LUA_DIR"] = cfg.variables.LUA_DIR,
239 ["LUA_BINDIR"] = cfg.variables.LUA_BINDIR,
240 ["LUA_INCDIR"] = value,
241 ["LUA_LIBDIR"] = cfg.variables.LUA_LIBDIR,
242 ["LUA"] = cfg.variables.LUA,
243 }
244
245 local ok, err = deps.check_lua_incdir(variables)
246 if not ok then
247 util.printerr()
248 util.warning((err:gsub(" You can use.*", "")))
249 end
250 return ok
251end
252
253local function report_on_lua_libdir_config(value)
254 local variables = {
255 ["LUA_DIR"] = cfg.variables.LUA_DIR,
256 ["LUA_BINDIR"] = cfg.variables.LUA_BINDIR,
257 ["LUA_INCDIR"] = cfg.variables.LUA_INCDIR,
258 ["LUA_LIBDIR"] = value,
259 ["LUA"] = cfg.variables.LUA,
260 }
261
262 local ok, err, _, err_files = deps.check_lua_libdir(variables)
263 if not ok then
264 util.printerr()
265 util.warning((err:gsub(" You can use.*", "")))
266 util.printerr("Tried:")
267 for _, l in pairs(err_files or {}) do
268 for _, d in ipairs(l) do
269 util.printerr("\t" .. d)
270 end
271 end
272 end
273 return ok
274end
275
276local function warn_bad_c_config()
277 util.printerr()
278 util.printerr("LuaRocks may not work correctly when building C modules using this configuration.")
279 util.printerr()
280end
281
282
283
284function config_cmd.command(args)
285
286 deps.check_lua_incdir(cfg.variables)
287 deps.check_lua_libdir(cfg.variables)
288
289
290 if args.lua_incdir then
291 print(cfg.variables.LUA_INCDIR)
292 return true
293 end
294 if args.lua_libdir then
295 print(cfg.variables.LUA_LIBDIR)
296 return true
297 end
298 if args.lua_ver then
299 print(cfg.lua_version)
300 return true
301 end
302 if args.system_config then
303 return config_file(cfg.config_files.system)
304 end
305 if args.user_config then
306 return config_file(cfg.config_files.user)
307 end
308 if args.rock_trees then
309 for _, tree in ipairs(cfg.rocks_trees) do
310 if type(tree) == "string" then
311 util.printout(dir.normalize(tree))
312 else
313 local name = tree.name and "\t" .. tree.name or ""
314 util.printout(dir.normalize(tree.root) .. name)
315 end
316 end
317 return true
318 end
319
320 if args.key == "lua_version" and args.value then
321 local scope = get_scope(args)
322 if scope == "project" and not cfg.config_files.project then
323 return nil, "Current directory is not part of a project. You may want to run `luarocks init`."
324 end
325
326 local location = (cfg.config_files)[scope]
327 if (not location) or (not location.file) then
328 return nil, "could not get config file location for " .. tostring(scope) .. " scope"
329 end
330
331 local prefix = dir.dir_name(location.file)
332 local ok, err = persist.save_default_lua_version(prefix, args.value)
333 if not ok then
334 return nil, "could not set default Lua version: " .. err
335 end
336 print("Lua version will default to " .. args.value .. " in " .. prefix)
337 end
338
339 if args.key == "lua_dir" and args.value then
340 local scope = get_scope(args)
341 local keys = {
342 ["variables.LUA_DIR"] = cfg.variables.LUA_DIR,
343 ["variables.LUA_BINDIR"] = cfg.variables.LUA_BINDIR,
344 ["variables.LUA_INCDIR"] = cfg.variables.LUA_INCDIR,
345 ["variables.LUA_LIBDIR"] = cfg.variables.LUA_LIBDIR,
346 ["variables.LUA"] = cfg.variables.LUA,
347 }
348 if args.lua_version then
349 local prefix = dir.dir_name((cfg.config_files)[scope].file)
350 persist.save_default_lua_version(prefix, args.lua_version)
351 end
352 local ok, err = write_entries(keys, scope, args.unset)
353 if ok then
354 local inc_ok = report_on_lua_incdir_config(cfg.variables.LUA_INCDIR)
355 local lib_ok = ok and report_on_lua_libdir_config(cfg.variables.LUA_LIBDIR)
356 if not (inc_ok and lib_ok) then
357 warn_bad_c_config()
358 end
359 end
360
361 return ok, err
362 end
363
364 if args.key then
365 if args.key:match("^[A-Z]") then
366 args.key = "variables." .. args.key
367 end
368
369 if args.value or args.unset then
370 local scope = get_scope(args)
371
372 local ok, err = write_entries({ [args.key] = args.value or "" }, scope, args.unset)
373
374 if ok then
375 if args.key == "variables.LUA_INCDIR" then
376 local ok = report_on_lua_incdir_config(args.value)
377 if not ok then
378 warn_bad_c_config()
379 end
380 elseif args.key == "variables.LUA_LIBDIR" then
381 local ok = report_on_lua_libdir_config(args.value)
382 if not ok then
383 warn_bad_c_config()
384 end
385 end
386 end
387
388 return ok, err
389 else
390 return print_entry(args.key, cfg, args.json)
391 end
392 end
393
394 if args.json then
395 return print_json(config.get_config_for_display(cfg))
396 else
397 print(config.to_string(cfg))
398 return true
399 end
400end
401
402return config_cmd
diff --git a/src/luarocks/cmd/doc.lua b/src/luarocks/cmd/doc.lua
new file mode 100644
index 00000000..1389d80c
--- /dev/null
+++ b/src/luarocks/cmd/doc.lua
@@ -0,0 +1,158 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local string = _tl_compat and _tl_compat.string or string
2
3
4local doc = {}
5
6
7local util = require("luarocks.util")
8local queries = require("luarocks.queries")
9local search = require("luarocks.search")
10local path = require("luarocks.path")
11local dir = require("luarocks.dir")
12local fetch = require("luarocks.fetch")
13local fs = require("luarocks.fs")
14local download = require("luarocks.download")
15
16
17
18
19
20function doc.add_to_parser(parser)
21 local cmd = parser:command("doc", "Show documentation for an installed rock.\n\n" ..
22 "Without any flags, tries to load the documentation using a series of heuristics.\n" ..
23 "With flags, return only the desired information.", util.see_also([[
24 For more information about a rock, see the 'show' command.
25]])):
26 summary("Show documentation for an installed rock.")
27
28 cmd:argument("rock", "Name of the rock."):
29 action(util.namespaced_name_action)
30 cmd:argument("version", "Version of the rock."):
31 args("?")
32
33 cmd:flag("--home", "Open the home page of project.")
34 cmd:flag("--list", "List documentation files only.")
35 cmd:flag("--porcelain", "Produce machine-friendly output.")
36end
37
38local function show_homepage(homepage, name, namespace, version)
39 if not homepage then
40 return nil, "No 'homepage' field in rockspec for " .. util.format_rock_name(name, namespace, version)
41 end
42 util.printout("Opening " .. homepage .. " ...")
43 fs.browser(homepage)
44 return true
45end
46
47local function try_to_open_homepage(name, namespace, version)
48 local temp_dir, err = fs.make_temp_dir("doc-" .. name .. "-" .. (version or ""))
49 if not temp_dir then
50 return nil, "Failed creating temporary directory: " .. err
51 end
52 util.schedule_function(fs.delete, temp_dir)
53 local ok, err = fs.change_dir(temp_dir)
54 if not ok then return nil, err end
55 local filename, err = download.download_file("rockspec", name, namespace, version)
56 if not filename then return nil, err end
57 local rockspec, err = fetch.load_local_rockspec(filename)
58 if not rockspec then return nil, err end
59 fs.pop_dir()
60 local descript = rockspec.description or {}
61 return show_homepage(descript.homepage, name, namespace, version)
62end
63
64
65
66function doc.command(args)
67 local query = queries.new(args.rock, args.namespace, args.version)
68 local iname, iversion, repo = search.pick_installed_rock(query, args.tree)
69 if not iname then
70 local rock = util.format_rock_name(args.rock, args.namespace, args.version)
71 util.printout(rock .. " is not installed. Looking for it in the rocks servers...")
72 return try_to_open_homepage(args.rock, args.namespace, args.version)
73 end
74 local name, version = iname, iversion
75
76 local rockspec, err = fetch.load_local_rockspec(path.rockspec_file(name, version, repo))
77 if not rockspec then return nil, err end
78 local descript = rockspec.description or {}
79
80 if args.home then
81 return show_homepage(descript.homepage, name, args.namespace, version)
82 end
83
84 local directory = path.install_dir(name, version, repo)
85
86 local docdir
87 local directories = { "doc", "docs" }
88 for _, d in ipairs(directories) do
89 local dirname = dir.path(directory, d)
90 if fs.is_dir(dirname) then
91 docdir = dirname
92 break
93 end
94 end
95 if not docdir then
96 if descript.homepage and not args.list then
97 util.printout("Local documentation directory not found -- opening " .. descript.homepage .. " ...")
98 fs.browser(descript.homepage)
99 return true
100 end
101 return nil, "Documentation directory not found for " .. name .. " " .. version
102 end
103
104 docdir = dir.normalize(docdir)
105 local files = fs.find(docdir)
106 local htmlpatt = "%.html?$"
107 local extensions = { htmlpatt, "%.md$", "%.txt$", "%.textile$", "" }
108 local basenames = { "index", "readme", "manual" }
109
110 local porcelain = args.porcelain
111 if #files > 0 then
112 util.title("Documentation files for " .. name .. " " .. version, porcelain)
113 if porcelain then
114 for _, file in ipairs(files) do
115 util.printout(docdir .. "/" .. file)
116 end
117 else
118 util.printout(docdir .. "/")
119 for _, file in ipairs(files) do
120 util.printout("\t" .. file)
121 end
122 end
123 end
124
125 if args.list then
126 return true
127 end
128
129 for _, extension in ipairs(extensions) do
130 for _, basename in ipairs(basenames) do
131 local filename = basename .. extension
132 local found
133 for _, file in ipairs(files) do
134 if file:lower():match(filename) and ((not found) or #file < #found) then
135 found = file
136 end
137 end
138 if found then
139 local pathname = dir.path(docdir, found)
140 util.printout()
141 util.printout("Opening " .. pathname .. " ...")
142 util.printout()
143 local ok = fs.browser(pathname)
144 if not ok and not pathname:match(htmlpatt) then
145 local fd = io.open(pathname, "r")
146 util.printout(fd:read("*a"))
147 fd:close()
148 end
149 return true
150 end
151 end
152 end
153
154 return true
155end
156
157
158return doc
diff --git a/src/luarocks/cmd/download.lua b/src/luarocks/cmd/download.lua
new file mode 100644
index 00000000..3be0e1ec
--- /dev/null
+++ b/src/luarocks/cmd/download.lua
@@ -0,0 +1,61 @@
1
2
3
4local cmd_download = {}
5
6
7local util = require("luarocks.util")
8local download = require("luarocks.download")
9
10
11
12
13
14function cmd_download.add_to_parser(parser)
15 local cmd = parser:command("download", "Download a specific rock file from a rocks server.", util.see_also())
16
17 cmd:argument("name", "Name of the rock."):
18 args("?"):
19 action(util.namespaced_name_action)
20 cmd:argument("version", "Version of the rock."):
21 args("?")
22
23 cmd:flag("--all", "Download all files if there are multiple matches.")
24 cmd:mutex(
25 cmd:flag("--source", "Download .src.rock if available."),
26 cmd:flag("--rockspec", "Download .rockspec if available."),
27 cmd:option("--arch", "Download rock for a specific architecture."))
28 cmd:flag("--check-lua-versions", "If the rock can't be found, check repository " ..
29 "and report if it is available for another Lua version.")
30end
31
32
33
34
35function cmd_download.command(args)
36 if not args.name and not args.all then
37 return nil, "Argument missing. " .. util.see_help("download")
38 end
39
40 args.name = args.name or ""
41
42 local arch
43
44 if args.source then
45 arch = "src"
46 elseif args.rockspec then
47 arch = "rockspec"
48 elseif args.arch then
49 arch = args.arch
50 end
51
52 if args.all then
53 local ok, err = download.download_all(arch, args.name, args.namespace, args.version)
54 return ok, err
55 else
56 local dl, err = download.download_file(arch, args.name, args.namespace, args.version, args.check_lua_versions)
57 return dl and true, err
58 end
59end
60
61return cmd_download
diff --git a/src/luarocks/cmd/init.lua b/src/luarocks/cmd/init.lua
new file mode 100644
index 00000000..9b124974
--- /dev/null
+++ b/src/luarocks/cmd/init.lua
@@ -0,0 +1,230 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2local init = {}
3
4
5
6local cfg = require("luarocks.core.cfg")
7local fs = require("luarocks.fs")
8local path = require("luarocks.path")
9local deps = require("luarocks.deps")
10local dir = require("luarocks.dir")
11local util = require("luarocks.util")
12local persist = require("luarocks.persist")
13local write_rockspec = require("luarocks.cmd.write_rockspec")
14
15
16
17
18
19
20
21
22
23
24function init.add_to_parser(parser)
25 local cmd = parser:command("init", "Initialize a directory for a Lua project using LuaRocks.", util.see_also())
26
27 cmd:argument("name", "The project name."):
28 args("?")
29 cmd:argument("version", "An optional project version."):
30 args("?")
31 cmd:option("--wrapper-dir", "Location where the 'lua' and 'luarocks' wrapper scripts " ..
32 "should be generated; if not given, the current directory is used as a default.")
33 cmd:flag("--reset", "Delete any .luarocks/config-5.x.lua and ./lua and generate new ones.")
34 cmd:flag("--no-wrapper-scripts", "Do not generate wrapper ./lua and ./luarocks launcher scripts.")
35 cmd:flag("--no-gitignore", "Do not generate a .gitignore file.")
36
37 cmd:group("Options for specifying rockspec data", write_rockspec.cmd_options(cmd))
38end
39
40local function gitignore_path(pwd, wrapper_dir, filename)
41 local norm_cur = fs.absolute_name(pwd)
42 local norm_file = fs.absolute_name(dir.path(wrapper_dir, filename))
43 if norm_file:sub(1, #norm_cur) == norm_cur then
44 return norm_file:sub(#norm_cur + 2)
45 else
46 return filename
47 end
48end
49
50local function write_gitignore(entries)
51 local gitignore = ""
52 local fd = io.open(".gitignore", "r")
53 if fd then
54 gitignore = fd:read("*a")
55 fd:close()
56 gitignore = "\n" .. gitignore .. "\n"
57 end
58
59 fd = io.open(".gitignore", gitignore and "a" or "w")
60 if fd then
61 for _, entry in ipairs(entries) do
62 entry = "/" .. entry
63 if not gitignore:find("\n" .. entry .. "\n", 1, true) then
64 fd:write(entry .. "\n")
65 end
66 end
67 fd:close()
68 end
69end
70
71local function inject_tree(tree)
72 path.use_tree(tree)
73 local tree_set = false
74 for _, t in ipairs(cfg.rocks_trees) do
75 if type(t) == "table" then
76 if t.name == "project" then
77 t.root = tree
78 tree_set = true
79 end
80 end
81 end
82 if not tree_set then
83 table.insert(cfg.rocks_trees, 1, { name = "project", root = tree })
84 end
85end
86
87local function write_wrapper_scripts(wrapper_dir, luarocks_wrapper, lua_wrapper)
88 local tree = dir.path(fs.current_dir(), "lua_modules")
89
90 fs.make_dir(wrapper_dir)
91
92 luarocks_wrapper = dir.path(wrapper_dir, luarocks_wrapper)
93 if not fs.exists(luarocks_wrapper) then
94 util.printout("Preparing " .. luarocks_wrapper .. " ...")
95 fs.wrap_script(arg[0], luarocks_wrapper, "none", nil, nil, "--project-tree", tree)
96 else
97 util.printout(luarocks_wrapper .. " already exists. Not overwriting it!")
98 end
99
100 lua_wrapper = dir.path(wrapper_dir, lua_wrapper)
101 local write_lua_wrapper = true
102 if fs.exists(lua_wrapper) then
103 if not util.lua_is_wrapper(lua_wrapper) then
104 util.printout(lua_wrapper .. " already exists and does not look like a wrapper script. Not overwriting.")
105 write_lua_wrapper = false
106 end
107 end
108
109 if write_lua_wrapper then
110 if util.check_lua_version(cfg.variables.LUA, cfg.lua_version) then
111 util.printout("Preparing " .. lua_wrapper .. " for version " .. cfg.lua_version .. "...")
112
113
114 inject_tree(tree)
115
116 fs.wrap_script(nil, lua_wrapper, "all")
117 else
118 util.warning("No Lua interpreter detected for version " .. cfg.lua_version .. ". Not creating " .. lua_wrapper)
119 end
120 end
121end
122
123
124
125function init.command(args)
126 local do_gitignore = not args.no_gitignore
127 local do_wrapper_scripts = not args.no_wrapper_scripts
128 local wrapper_dir = args.wrapper_dir or "."
129
130 local pwd = fs.current_dir()
131
132 if not args.name then
133 args.name = dir.base_name(pwd)
134 if args.name == "/" then
135 return nil, "When running from the root directory, please specify the <name> argument"
136 end
137 end
138
139 util.title("Initializing project '" .. args.name .. "' for Lua " .. cfg.lua_version .. " ...")
140
141 local ok, err = deps.check_lua_incdir(cfg.variables)
142 if not ok then
143 return nil, err
144 end
145
146 local has_rockspec = false
147 for file in fs.dir() do
148 if file:match("%.rockspec$") then
149 has_rockspec = true
150 break
151 end
152 end
153
154 if not has_rockspec then
155 args.version = args.version or "dev"
156 args.location = pwd
157 local ok, err = write_rockspec.command(args)
158 if not ok then
159 util.printerr(err)
160 end
161 end
162
163 local ext = cfg.wrapper_suffix
164 local luarocks_wrapper = "luarocks" .. ext
165 local lua_wrapper = "lua" .. ext
166
167 if do_gitignore then
168 util.printout("Adding entries to .gitignore ...")
169 local ignores = { "lua_modules", ".luarocks" }
170 if do_wrapper_scripts then
171 table.insert(ignores, 1, gitignore_path(pwd, wrapper_dir, luarocks_wrapper))
172 table.insert(ignores, 2, gitignore_path(pwd, wrapper_dir, lua_wrapper))
173 end
174 write_gitignore(ignores)
175 end
176
177 util.printout("Preparing ./.luarocks/ ...")
178 fs.make_dir(".luarocks")
179 local config_file = ".luarocks/config-" .. cfg.lua_version .. ".lua"
180
181 if args.reset then
182 if do_wrapper_scripts then
183 fs.delete(fs.absolute_name(dir.path(wrapper_dir, lua_wrapper)))
184 end
185 fs.delete(fs.absolute_name(config_file))
186 end
187
188 local config_tbl, err = persist.load_config_file_if_basic(config_file, cfg)
189 if config_tbl then
190 local varnames = {
191 "LUA_DIR",
192 "LUA_INCDIR",
193 "LUA_LIBDIR",
194 "LUA_BINDIR",
195 "LUA",
196 }
197 for _, varname in ipairs(varnames) do
198 if cfg.variables[varname] then
199 config_tbl.variables = config_tbl.variables or {};
200 (config_tbl.variables)[varname] = cfg.variables[varname]
201 end
202 end
203 local ok, err = persist.save_from_table(config_file, config_tbl)
204 if ok then
205 util.printout("Wrote " .. config_file)
206 else
207 util.printout("Failed writing " .. config_file .. ": " .. err)
208 end
209 else
210 util.printout("Will not attempt to overwrite " .. config_file)
211 end
212
213 ok, err = persist.save_default_lua_version(".luarocks", cfg.lua_version)
214 if not ok then
215 util.printout("Failed setting default Lua version: " .. err)
216 end
217
218 util.printout("Preparing ./lua_modules/ ...")
219 fs.make_dir("lua_modules/lib/luarocks/rocks-" .. cfg.lua_version)
220
221 if do_wrapper_scripts then
222 write_wrapper_scripts(wrapper_dir, luarocks_wrapper, lua_wrapper)
223 end
224
225 return true
226end
227
228init.needs_lock = function() return true end
229
230return init
diff --git a/src/luarocks/cmd/install.lua b/src/luarocks/cmd/install.lua
new file mode 100644
index 00000000..e582f6e1
--- /dev/null
+++ b/src/luarocks/cmd/install.lua
@@ -0,0 +1,259 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string
2
3local install = {}
4
5
6
7local dir = require("luarocks.dir")
8local path = require("luarocks.path")
9local repos = require("luarocks.repos")
10local fetch = require("luarocks.fetch")
11local util = require("luarocks.util")
12local fs = require("luarocks.fs")
13local deps = require("luarocks.deps")
14local repo_writer = require("luarocks.repo_writer")
15local remove = require("luarocks.remove")
16local search = require("luarocks.search")
17local queries = require("luarocks.queries")
18local cfg = require("luarocks.core.cfg")
19
20
21
22
23
24
25
26
27function install.add_to_parser(parser)
28 local cmd = parser:command("install", "Install a rock.", util.see_also())
29
30 cmd:argument("rock", "The name of a rock to be fetched from a repository " ..
31 "or a filename of a locally available rock."):
32 action(util.namespaced_name_action)
33 cmd:argument("version", "Version of the rock."):
34 args("?")
35
36 cmd:flag("--keep", "Do not remove previously installed versions of the " ..
37 "rock after building a new one. This behavior can be made permanent by " ..
38 "setting keep_other_versions=true in the configuration file.")
39 cmd:flag("--force", "If --keep is not specified, force removal of " ..
40 "previously installed versions if it would break dependencies. " ..
41 "If rock is already installed, reinstall it anyway.")
42 cmd:flag("--force-fast", "Like --force, but performs a forced removal " ..
43 "without reporting dependency issues.")
44 cmd:flag("--only-deps --deps-only", "Install only the dependencies of the rock.")
45 cmd:flag("--no-doc", "Install the rock without its documentation.")
46 cmd:flag("--verify", "Verify signature of the rockspec or src.rock being " ..
47 "built. If the rockspec or src.rock is being downloaded, LuaRocks will " ..
48 "attempt to download the signature as well. Otherwise, the signature " ..
49 "file should be already available locally in the same directory.\n" ..
50 "You need the signer’s public key in your local keyring for this " ..
51 "option to work properly.")
52 cmd:flag("--check-lua-versions", "If the rock can't be found, check repository " ..
53 "and report if it is available for another Lua version.")
54 util.deps_mode_option(cmd)
55 cmd:flag("--no-manifest", "Skip creating/updating the manifest")
56 cmd:flag("--pin", "If the installed rock is a Lua module, create a " ..
57 "luarocks.lock file listing the exact versions of each dependency found for " ..
58 "this rock (recursively), and store it in the rock's directory. " ..
59 "Ignores any existing luarocks.lock file in the rock's sources.")
60
61 parser:flag("--pack-binary-rock"):hidden(true)
62 parser:option("--branch"):hidden(true)
63 parser:flag("--sign"):hidden(true)
64end
65
66
67
68
69
70
71
72function install.install_binary_rock(rock_file, opts)
73
74 local namespace = opts.namespace
75 local deps_mode = opts.deps_mode
76
77 local name, version, arch = path.parse_name(rock_file)
78 if not name then
79 return nil, "Filename " .. rock_file .. " does not match format 'name-version-revision.arch.rock'."
80 end
81
82 if arch ~= "all" and arch ~= cfg.arch then
83 return nil, "Incompatible architecture " .. arch, "arch"
84 end
85 if repos.is_installed(name, version) then
86 if not (opts.force or opts.force_fast) then
87 util.printout(name .. " " .. version .. " is already installed in " .. path.root_dir(cfg.root_dir))
88 util.printout("Use --force to reinstall.")
89 return name, version
90 end
91 repo_writer.delete_version(name, version, opts.deps_mode)
92 end
93
94 local install_dir = path.install_dir(name, version)
95
96 local rollback = util.schedule_function(function()
97 fs.delete(install_dir)
98 fs.remove_dir_if_empty(path.versions_dir(name))
99 end)
100 local ok
101 local oks, err, errcode = fetch.fetch_and_unpack_rock(rock_file, install_dir, opts.verify)
102 if not oks then return nil, err, errcode end
103
104 local rockspec, err = fetch.load_rockspec(path.rockspec_file(name, version))
105 if err then
106 return nil, "Failed loading rockspec for installed package: " .. err, errcode
107 end
108
109 if opts.deps_mode ~= "none" then
110 ok, err, errcode = deps.check_external_deps(rockspec, "install")
111 if err then return nil, err, errcode end
112 end
113
114 if deps_mode ~= "none" then
115 local deplock_dir = fs.exists(dir.path(".", "luarocks.lock")) and
116 "." or
117 install_dir
118 ok, err, errcode = deps.fulfill_dependencies(rockspec, "dependencies", deps_mode, opts.verify, deplock_dir)
119 if err then return nil, err, errcode end
120 end
121
122 ok, err = repo_writer.deploy_files(name, version, repos.should_wrap_bin_scripts(rockspec), deps_mode, namespace)
123 if err then return nil, err end
124
125 util.remove_scheduled_function(rollback)
126 rollback = util.schedule_function(function()
127 repo_writer.delete_version(name, version, deps_mode)
128 end)
129
130 ok, err = repos.run_hook(rockspec, "post_install")
131 if err then return nil, err end
132
133 util.announce_install(rockspec)
134 util.remove_scheduled_function(rollback)
135 return name, version
136end
137
138
139
140
141
142
143
144function install.install_binary_rock_deps(rock_file, opts)
145
146 local name, version, arch = path.parse_name(rock_file)
147 if not name then
148 return nil, "Filename " .. rock_file .. " does not match format 'name-version-revision.arch.rock'."
149 end
150
151 if arch ~= "all" and arch ~= cfg.arch then
152 return nil, "Incompatible architecture " .. arch, "arch"
153 end
154
155 local install_dir = path.install_dir(name, version)
156
157 local ok
158 local oks, err, errcode = fetch.fetch_and_unpack_rock(rock_file, install_dir, opts.verify)
159 if not oks then return nil, err, errcode end
160
161 local rockspec, err = fetch.load_rockspec(path.rockspec_file(name, version))
162 if err then
163 return nil, "Failed loading rockspec for installed package: " .. err, errcode
164 end
165
166 local deplock_dir = fs.exists(dir.path(".", "luarocks.lock")) and
167 "." or
168 install_dir
169 ok, err, errcode = deps.fulfill_dependencies(rockspec, "dependencies", opts.deps_mode, opts.verify, deplock_dir)
170 if err then return nil, err, errcode end
171
172 util.printout()
173 util.printout("Successfully installed dependencies for " .. name .. " " .. version)
174
175 return name, version
176end
177
178local function install_rock_file_deps(filename, opts)
179
180 local name, version = install.install_binary_rock_deps(filename, opts)
181 if not name then return nil, version end
182
183 deps.check_dependencies(nil, opts.deps_mode)
184 return true
185end
186
187local function install_rock_file(filename, opts)
188
189 local name, version = install.install_binary_rock(filename, opts)
190 if not name then return nil, version end
191
192 if opts.no_doc then
193 util.remove_doc_dir(name, version)
194 end
195
196 if (not opts.keep) and not cfg.keep_other_versions then
197 local ok, err, warn = remove.remove_other_versions(name, version, opts.force, opts.force_fast)
198 if not ok then
199 return nil, err
200 elseif warn then
201 util.printerr(err)
202 end
203 end
204
205 deps.check_dependencies(nil, opts.deps_mode)
206 return true
207end
208
209
210
211
212
213
214
215
216
217function install.command(args)
218 if args.rock:match("%.rockspec$") or args.rock:match("%.src%.rock$") then
219 local build = require("luarocks.cmd.build")
220 return build.command(args)
221 elseif args.rock:match("%.rock$") then
222 local deps_mode = deps.get_deps_mode(args)
223 local opts = {
224 namespace = args.namespace,
225 keep = not not args.keep,
226 force = not not args.force,
227 force_fast = not not args.force_fast,
228 no_doc = not not args.no_doc,
229 deps_mode = deps_mode,
230 verify = not not args.verify,
231 }
232 if args.only_deps then
233 return install_rock_file_deps(args.rock, opts)
234 else
235 return install_rock_file(args.rock, opts)
236 end
237 else
238 local url, err = search.find_rock_checking_lua_versions(
239 queries.new(args.rock, args.namespace, args.version),
240 args.check_lua_versions)
241 if not url then
242 return nil, err
243 end
244 util.printout("Installing " .. url)
245 args.rock = url
246 return install.command(args)
247 end
248end
249
250install.needs_lock = function(args)
251 if args.pack_binary_rock then
252 return false
253 end
254 return true
255end
256
257deps.installer = install.command
258
259return install
diff --git a/src/luarocks/cmd/lint.lua b/src/luarocks/cmd/lint.lua
new file mode 100644
index 00000000..7a8443cc
--- /dev/null
+++ b/src/luarocks/cmd/lint.lua
@@ -0,0 +1,59 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string
2
3
4local lint = {}
5
6
7local util = require("luarocks.util")
8local download = require("luarocks.download")
9local fetch = require("luarocks.fetch")
10
11
12
13
14
15function lint.add_to_parser(parser)
16 local cmd = parser:command("lint", "Check syntax of a rockspec.\n\n" ..
17 "Returns success if the text of the rockspec is syntactically correct, else failure.",
18 util.see_also()):
19 summary("Check syntax of a rockspec.")
20
21 cmd:argument("rockspec", "The rockspec to check.")
22end
23
24function lint.command(args)
25
26 local filename = args.rockspec
27 if not filename:match(".rockspec$") then
28 local err
29 filename, err = download.download_file("rockspec", filename:lower())
30 if not filename then
31 return nil, err
32 end
33 end
34
35 local rs, err = fetch.load_local_rockspec(filename)
36 if not rs then
37 return nil, "Failed loading rockspec: " .. err
38 end
39
40 local ok = true
41
42
43
44
45
46
47 if not rs.description or not rs.description.license then
48 util.printerr("Rockspec has no description.license field.")
49 ok = false
50 end
51
52 if ok then
53 return ok
54 end
55
56 return nil, filename .. " failed consistency checks."
57end
58
59return lint
diff --git a/src/luarocks/cmd/list.lua b/src/luarocks/cmd/list.lua
new file mode 100644
index 00000000..84fb6588
--- /dev/null
+++ b/src/luarocks/cmd/list.lua
@@ -0,0 +1,113 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2
3
4local list = {Outdated = {}, }
5
6
7
8
9
10
11
12
13local search = require("luarocks.search")
14local queries = require("luarocks.queries")
15local vers = require("luarocks.core.vers")
16local cfg = require("luarocks.core.cfg")
17local util = require("luarocks.util")
18local path = require("luarocks.path")
19
20
21
22
23
24
25
26
27
28
29
30function list.add_to_parser(parser)
31 local cmd = parser:command("list", "List currently installed rocks.", util.see_also())
32
33 cmd:argument("filter", "A substring of a rock name to filter by."):
34 args("?")
35 cmd:argument("version", "Rock version to filter by."):
36 args("?")
37
38 cmd:flag("--outdated", "List only rocks for which there is a higher " ..
39 "version available in the rocks server.")
40 cmd:flag("--porcelain", "Produce machine-friendly output.")
41end
42
43local function check_outdated(trees, query)
44 local results_installed = {}
45 for _, tree in ipairs(trees) do
46 search.local_manifest_search(results_installed, path.rocks_dir(tree), query)
47 end
48 local outdated = {}
49 for name, versions in util.sortedpairs(results_installed) do
50 local versionsk = util.keys(versions)
51 table.sort(versionsk, vers.compare_versions)
52 local latest_installed = versionsk[1]
53
54 local query_available = queries.new(name:lower())
55 local results_available = search.search_repos(query_available)
56
57 if results_available[name] then
58 local available_versions = util.keys(results_available[name])
59 table.sort(available_versions, vers.compare_versions)
60 local latest_available = available_versions[1]
61 local latest_available_repo = results_available[name][latest_available][1].repo
62
63 if vers.compare_versions(latest_available, latest_installed) then
64 table.insert(outdated, { name = name, installed = latest_installed, available = latest_available, repo = latest_available_repo })
65 end
66 end
67 end
68 return outdated
69end
70
71local function list_outdated(trees, query, porcelain)
72 util.title("Outdated rocks:", porcelain)
73 local outdated = check_outdated(trees, query)
74 for _, item in ipairs(outdated) do
75 if porcelain then
76 util.printout(item.name, item.installed, item.available, item.repo)
77 else
78 util.printout(item.name)
79 util.printout(" " .. item.installed .. " < " .. item.available .. " at " .. item.repo)
80 util.printout()
81 end
82 end
83 return true
84end
85
86
87
88function list.command(args)
89 local query = queries.new(args.filter and args.filter:lower() or "", args.namespace, args.version, true)
90 local trees = cfg.rocks_trees
91 local title = "Rocks installed for Lua " .. cfg.lua_version
92 if args.tree then
93 trees = { args.tree }
94 title = title .. " in " .. args.tree
95 end
96
97 if args.outdated then
98 return list_outdated(trees, query, args.porcelain)
99 end
100
101 local results = {}
102 for _, tree in ipairs(trees) do
103 local ok, err, errcode = search.local_manifest_search(results, path.rocks_dir(tree), query)
104 if not ok and errcode ~= "open" then
105 util.warning(err)
106 end
107 end
108 util.title(title, args.porcelain)
109 search.print_result_tree(results, args.porcelain)
110 return true
111end
112
113return list
diff --git a/src/luarocks/cmd/make.lua b/src/luarocks/cmd/make.lua
new file mode 100644
index 00000000..e8a52906
--- /dev/null
+++ b/src/luarocks/cmd/make.lua
@@ -0,0 +1,179 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string
2
3
4
5
6local make = {}
7
8
9
10local build = require("luarocks.build")
11local util = require("luarocks.util")
12local cfg = require("luarocks.core.cfg")
13local fetch = require("luarocks.fetch")
14local pack = require("luarocks.pack")
15local remove = require("luarocks.remove")
16local deps = require("luarocks.deps")
17local dir = require("luarocks.dir")
18local fs = require("luarocks.fs")
19
20
21
22
23
24
25
26function make.cmd_options(parser)
27 parser:flag("--no-install", "Do not install the rock.")
28 parser:flag("--no-doc", "Install the rock without its documentation.")
29 parser:flag("--pack-binary-rock", "Do not install rock. Instead, produce a " ..
30 ".rock file with the contents of compilation in the current directory.")
31 parser:flag("--keep", "Do not remove previously installed versions of the " ..
32 "rock after building a new one. This behavior can be made permanent by " ..
33 "setting keep_other_versions=true in the configuration file.")
34 parser:flag("--force", "If --keep is not specified, force removal of " ..
35 "previously installed versions if it would break dependencies. " ..
36 "If rock is already installed, reinstall it anyway.")
37 parser:flag("--force-fast", "Like --force, but performs a forced removal " ..
38 "without reporting dependency issues.")
39 parser:flag("--verify", "Verify signature of the rockspec or src.rock being " ..
40 "built. If the rockspec or src.rock is being downloaded, LuaRocks will " ..
41 "attempt to download the signature as well. Otherwise, the signature " ..
42 "file should be already available locally in the same directory.\n" ..
43 "You need the signer's public key in your local keyring for this " ..
44 "option to work properly.")
45 parser:flag("--sign", "To be used with --pack-binary-rock. Also produce a " ..
46 "signature file for the generated .rock file.")
47 parser:flag("--check-lua-versions", "If the rock can't be found, check repository " ..
48 "and report if it is available for another Lua version.")
49 parser:flag("--pin", "Pin the exact dependencies used for the rockspec" ..
50 "being built into a luarocks.lock file in the current directory.")
51 parser:flag("--no-manifest", "Skip creating/updating the manifest")
52 parser:flag("--only-deps --deps-only", "Install only the dependencies of the rock.")
53 util.deps_mode_option(parser)
54end
55
56function make.add_to_parser(parser)
57
58 local cmd = parser:command("make", [[
59Builds sources in the current directory, but unlike "build", it does not fetch
60sources, etc., assuming everything is available in the current directory. If no
61argument is given, it looks for a rockspec in the current directory and in
62"rockspec/" and "rockspecs/" subdirectories, picking the rockspec with newest
63version or without version name. If rockspecs for different rocks are found or
64there are several rockspecs without version, you must specify which to use,
65through the command-line.
66
67This command is useful as a tool for debugging rockspecs.
68To install rocks, you'll normally want to use the "install" and "build"
69commands. See the help on those for details.
70
71If the current directory contains a luarocks.lock file, it is used as the
72authoritative source for exact version of dependencies. The --pin flag
73overrides and recreates this file scanning dependency based on ranges.
74]], util.see_also()):
75 summary("Compile package in current directory using a rockspec.")
76
77
78 cmd:argument("rockspec", "Rockspec for the rock to build."):
79 args("?")
80
81 make.cmd_options(cmd)
82end
83
84
85
86
87function make.command(args)
88 local name, namespace, version
89 local rockspec_filename = args.rockspec
90 if not rockspec_filename then
91 local err
92 rockspec_filename, err = util.get_default_rockspec()
93 if not rockspec_filename then
94 return nil, err
95 end
96 end
97 if not rockspec_filename:match("rockspec$") then
98 return nil, "Invalid argument: 'make' takes a rockspec as a parameter. " .. util.see_help("make")
99 end
100
101 local cwd = fs.absolute_name(dir.path("."))
102 local rockspec, err, errcode = fetch.load_rockspec(rockspec_filename)
103 if not rockspec then
104 return nil, err
105 end
106
107 name, namespace = util.split_namespace(rockspec.name)
108 namespace = namespace or args.namespace
109
110 local opts = {
111 need_to_fetch = false,
112 minimal_mode = true,
113 deps_mode = deps.get_deps_mode(args),
114 build_only_deps = not not (args.only_deps and not args.pack_binary_rock),
115 namespace = namespace,
116 branch = args.branch,
117 verify = not not args.verify,
118 check_lua_versions = not not args.check_lua_versions,
119 pin = not not args.pin,
120 rebuild = true,
121 no_install = not not args.no_install,
122 }
123
124 if args.sign and not args.pack_binary_rock then
125 return nil, "In the make command, --sign is meant to be used only with --pack-binary-rock"
126 end
127
128 if args.no_install then
129 name, version = build.build_rockspec(rockspec, opts, cwd)
130 if name then
131 return true
132 else
133 return nil, version
134 end
135 elseif args.pack_binary_rock then
136 return pack.pack_binary_rock(name, namespace, rockspec.version, args.sign, function()
137 name, version = build.build_rockspec(rockspec, opts, cwd)
138 if name and args.no_doc then
139 util.remove_doc_dir(name, version)
140 end
141 return name, version
142 end)
143 else
144 local ok, err = build.build_rockspec(rockspec, opts, cwd)
145 if not ok then return nil, err end
146 name, version = ok, err
147
148 if opts.build_only_deps then
149 util.printout("Stopping after installing dependencies for " .. name .. " " .. version)
150 util.printout()
151 return name ~= nil, version
152 end
153
154 if args.no_doc then
155 util.remove_doc_dir(name, version)
156 end
157
158 if (not args.keep) and not cfg.keep_other_versions then
159 local ok, err, warn = remove.remove_other_versions(name, version, args.force, args.force_fast)
160 if not ok then
161 return nil, err
162 elseif warn then
163 util.printerr(warn)
164 end
165 end
166
167 deps.check_dependencies(nil, deps.get_deps_mode(args))
168 return name ~= nil, version
169 end
170end
171
172make.needs_lock = function(args)
173 if args.pack_binary_rock or args.no_install then
174 return false
175 end
176 return true
177end
178
179return make
diff --git a/src/luarocks/cmd/new_version.lua b/src/luarocks/cmd/new_version.lua
new file mode 100644
index 00000000..ea70a874
--- /dev/null
+++ b/src/luarocks/cmd/new_version.lua
@@ -0,0 +1,237 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local math = _tl_compat and _tl_compat.math or math; local string = _tl_compat and _tl_compat.string or string
2
3
4local new_version = {}
5
6
7local util = require("luarocks.util")
8local download = require("luarocks.download")
9local fetch = require("luarocks.fetch")
10local persist = require("luarocks.persist")
11local fs = require("luarocks.fs")
12local dir = require("luarocks.dir")
13local type_rockspec = require("luarocks.type.rockspec")
14
15
16
17
18
19
20
21
22
23function new_version.add_to_parser(parser)
24 local cmd = parser:command("new_version", [[
25This is a utility function that writes a new rockspec, updating data from a
26previous one.
27
28If a package name is given, it downloads the latest rockspec from the default
29server. If a rockspec is given, it uses it instead. If no argument is given, it
30looks for a rockspec same way 'luarocks make' does.
31
32If the version number is not given and tag is passed using --tag, it is used as
33the version, with 'v' removed from beginning. Otherwise, it only increments the
34revision number of the given (or downloaded) rockspec.
35
36If a URL is given, it replaces the one from the old rockspec with the given URL.
37If a URL is not given and a new version is given, it tries to guess the new URL
38by replacing occurrences of the version number in the URL or tag; if the guessed
39URL is invalid, the old URL is restored. It also tries to download the new URL
40to determine the new MD5 checksum.
41
42If a tag is given, it replaces the one from the old rockspec. If there is an old
43tag but no new one passed, it is guessed in the same way URL is.
44
45If a directory is not given, it defaults to the current directory.
46
47WARNING: it writes the new rockspec to the given directory, overwriting the file
48if it already exists.]], util.see_also()):
49 summary("Auto-write a rockspec for a new version of a rock.")
50
51 cmd:argument("rock", "Package name or rockspec."):
52 args("?")
53 cmd:argument("new_version", "New version of the rock."):
54 args("?")
55 cmd:argument("new_url", "New URL of the rock."):
56 args("?")
57
58 cmd:option("--dir", "Output directory for the new rockspec.")
59 cmd:option("--tag", "New SCM tag.")
60end
61
62
63local function try_replace(tbl, field, old, new)
64 if not tbl[field] then
65 return false
66 end
67 local old_field = tbl[field]
68 local new_field = tbl[field]:gsub(old, new)
69 if new_field ~= old_field then
70 util.printout("Guessing new '" .. field .. "' field as " .. new_field)
71 tbl[field] = new_field
72 return true
73 end
74 return false
75end
76
77
78
79
80
81local function check_url_and_update_md5(out_rs, invalid_is_error)
82 local file, temp_dir = fetch.fetch_url_at_temp_dir(out_rs.source.url, "luarocks-new-version-" .. out_rs.package)
83 if not file then
84 if invalid_is_error then
85 return nil, "invalid URL - " .. temp_dir
86 end
87 util.warning("invalid URL - " .. temp_dir)
88 return true, false
89 end
90 do
91 local inferred_dir, found_dir = fetch.find_base_dir(file, temp_dir, out_rs.source.url, out_rs.source.dir)
92 if not inferred_dir then
93 return nil, found_dir
94 end
95
96 if found_dir and found_dir ~= inferred_dir then
97 out_rs.source.dir = found_dir
98 end
99 end
100 if file then
101 if out_rs.source.md5 then
102 util.printout("File successfully downloaded. Updating MD5 checksum...")
103 local new_md5, err = fs.get_md5(file)
104 if not new_md5 then
105 return nil, err
106 end
107 local old_md5 = out_rs.source.md5
108 out_rs.source.md5 = new_md5
109 return true, new_md5 ~= old_md5
110 else
111 util.printout("File successfully downloaded.")
112 return true, false
113 end
114 end
115end
116
117local function update_source_section(out_rs, url, tag, old_ver, new_ver)
118 if tag then
119 out_rs.source.tag = tag
120 end
121 if url then
122 out_rs.source.url = url
123 return check_url_and_update_md5(out_rs)
124 end
125 if new_ver == old_ver then
126 return true
127 end
128 if out_rs.source.dir then
129 try_replace(out_rs.source, "dir", old_ver, new_ver)
130 end
131 if out_rs.source.file then
132 try_replace(out_rs.source, "file", old_ver, new_ver)
133 end
134
135 local old_url = out_rs.source.url
136 if try_replace(out_rs.source, "url", old_ver, new_ver) then
137 local ok, md5_changed = check_url_and_update_md5(out_rs, true)
138 if ok then
139 return ok, md5_changed
140 end
141 out_rs.source.url = old_url
142 end
143 if tag or try_replace(out_rs.source, "tag", old_ver, new_ver) then
144 return true
145 end
146
147 local ok, md5_changed = check_url_and_update_md5(out_rs)
148 if not ok then
149 return nil, md5_changed
150 end
151 if md5_changed then
152 util.warning("URL is the same, but MD5 has changed. Old rockspec is broken.")
153 end
154 return true
155end
156
157function new_version.command(args)
158 if not args.rock then
159 local err
160 args.rock, err = util.get_default_rockspec()
161 if not args.rock then
162 return nil, err
163 end
164 end
165
166 local filename, err
167 if args.rock:match("rockspec$") then
168 filename, err = fetch.fetch_url(args.rock)
169 if not filename then
170 return nil, err
171 end
172 else
173 filename, err = download.download_file("rockspec", args.rock:lower())
174 if not filename then
175 return nil, err
176 end
177 end
178
179 local valid_rs, err = fetch.load_rockspec(filename)
180 if not valid_rs then
181 return nil, err
182 end
183
184 local old_ver, old_rev = valid_rs.version:match("(.*)%-(%d+)$")
185 local new_ver, new_rev_str, new_rev
186
187 if args.tag and not args.new_version then
188 args.new_version = args.tag:gsub("^v", "")
189 end
190
191 local out_dir
192 if args.dir then
193 out_dir = dir.normalize(args.dir)
194 end
195
196 if args.new_version then
197 new_ver, new_rev_str = args.new_version:match("(.*)%-(%d+)$")
198 new_rev = math.tointeger(new_rev_str)
199 if not new_rev then
200 new_ver = args.new_version
201 new_rev = 1
202 end
203 else
204 new_ver = old_ver
205 new_rev = math.tointeger(old_rev) + 1
206 end
207 local new_rockver = new_ver:gsub("-", "")
208
209 local out_rs, err = persist.load_into_table(filename), string
210 local out_name = out_rs.package:lower()
211 out_rs.version = new_rockver .. "-" .. tostring(new_rev)
212
213 local ok, err = update_source_section(out_rs, args.new_url, args.tag, old_ver, new_ver)
214 if not ok then return nil, err end
215
216 if out_rs.build and out_rs.build.type == "module" then
217 out_rs.build.type = "builtin"
218 end
219
220 local out_filename = out_name .. "-" .. new_rockver .. "-" .. tostring(new_rev) .. ".rockspec"
221 if out_dir then
222 out_filename = dir.path(out_dir, out_filename)
223 fs.make_dir(out_dir)
224 end
225 persist.save_from_table(out_filename, out_rs, type_rockspec.order)
226
227 util.printout("Wrote " .. out_filename)
228
229 local valid_out_rs, err = fetch.load_local_rockspec(out_filename)
230 if not valid_out_rs then
231 return nil, "Failed loading generated rockspec: " .. err
232 end
233
234 return true
235end
236
237return new_version
diff --git a/src/luarocks/cmd/pack.lua b/src/luarocks/cmd/pack.lua
new file mode 100644
index 00000000..7c67d9cf
--- /dev/null
+++ b/src/luarocks/cmd/pack.lua
@@ -0,0 +1,41 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string
2
3
4local cmd_pack = {}
5
6
7local util = require("luarocks.util")
8local pack = require("luarocks.pack")
9local queries = require("luarocks.queries")
10
11
12
13
14
15function cmd_pack.add_to_parser(parser)
16 local cmd = parser:command("pack", "Create a rock, packing sources or binaries.", util.see_also())
17
18 cmd:argument("rock", "A rockspec file, for creating a source rock, or the " ..
19 "name of an installed package, for creating a binary rock."):
20 action(util.namespaced_name_action)
21 cmd:argument("version", "A version may be given if the first argument is a rock name."):
22 args("?")
23
24 cmd:flag("--sign", "Produce a signature file as well.")
25end
26
27
28
29
30function cmd_pack.command(args)
31 local file, err
32 if args.rock:match(".*%.rockspec") then
33 file, err = pack.pack_source_rock(args.rock)
34 else
35 local query = queries.new(args.rock, args.namespace, args.version)
36 file, err = pack.pack_installed_rock(query, args.tree)
37 end
38 return pack.report_and_sign_local_file(file, err, args.sign)
39end
40
41return cmd_pack
diff --git a/src/luarocks/cmd/path.lua b/src/luarocks/cmd/path.lua
new file mode 100644
index 00000000..96fb3b9b
--- /dev/null
+++ b/src/luarocks/cmd/path.lua
@@ -0,0 +1,88 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package
2
3
4local path_cmd = {}
5
6
7local util = require("luarocks.util")
8local cfg = require("luarocks.core.cfg")
9local fs = require("luarocks.fs")
10
11
12
13
14
15function path_cmd.add_to_parser(parser)
16 local cmd = parser:command("path", [[
17Returns the package path currently configured for this installation
18of LuaRocks, formatted as shell commands to update LUA_PATH and LUA_CPATH.
19
20On Unix systems, you may run:
21 eval `luarocks path`
22And on Windows:
23 luarocks path > "%temp%\_lrp.bat"
24 call "%temp%\_lrp.bat" && del "%temp%\_lrp.bat"]],
25 util.see_also()):
26 summary("Return the currently configured package path.")
27
28 cmd:flag("--no-bin", "Do not export the PATH variable.")
29 cmd:flag("--append", "Appends the paths to the existing paths. Default is " ..
30 "to prefix the LR paths to the existing paths.")
31 cmd:flag("--lr-path", "Prints Lua path components defined by the configured rocks trees " ..
32 "(not formatted as a shell command)")
33 cmd:flag("--lr-cpath", "Prints Lua cpath components defined by the configured rocks trees " ..
34 "(not formatted as a shell command)")
35 cmd:flag("--full", "By default, --lr-path and --lr-cpath only include the paths " ..
36 "derived by the LuaRocks rocks_trees. Using --full includes any other components " ..
37 "defined in your system's package.(c)path, either via the running interpreter's " ..
38 "default paths or via LUA_(C)PATH(_5_x) environment variables (in short, using " ..
39 "--full produces the same lists as shown in the shell outputs of 'luarocks path').")
40 cmd:flag("--lr-bin", "Exports the system path (not formatted as shell command).")
41 cmd:flag("--bin"):hidden(true)
42end
43
44
45
46function path_cmd.command(args)
47 local lr_path, lr_cpath, lr_bin = cfg.package_paths(args.tree)
48 local path_sep = cfg.export_path_separator
49
50 local full_list = ((not args.lr_path) and (not args.lr_cpath) and (not args.lr_bin)) or
51 args.full
52
53 local clean_path = util.cleanup_path(os.getenv("PATH") or "", path_sep, nil, true)
54
55 if full_list then
56 if args.append then
57 lr_path = package.path .. ";" .. lr_path
58 lr_cpath = package.cpath .. ";" .. lr_cpath
59 lr_bin = clean_path .. path_sep .. lr_bin
60 else
61 lr_path = lr_path .. ";" .. package.path
62 lr_cpath = lr_cpath .. ";" .. package.cpath
63 lr_bin = lr_bin .. path_sep .. clean_path
64 end
65 end
66
67 if args.lr_path then
68 util.printout(util.cleanup_path(lr_path, ';', cfg.lua_version, true))
69 return true
70 elseif args.lr_cpath then
71 util.printout(util.cleanup_path(lr_cpath, ';', cfg.lua_version, true))
72 return true
73 elseif args.lr_bin then
74 util.printout(util.cleanup_path(lr_bin, path_sep, nil, true))
75 return true
76 end
77
78 local lpath_var, lcpath_var = util.lua_path_variables()
79
80 util.printout(fs.export_cmd(lpath_var, util.cleanup_path(lr_path, ';', cfg.lua_version, args.append)))
81 util.printout(fs.export_cmd(lcpath_var, util.cleanup_path(lr_cpath, ';', cfg.lua_version, args.append)))
82 if not args.no_bin then
83 util.printout(fs.export_cmd("PATH", util.cleanup_path(lr_bin, path_sep, nil, args.append)))
84 end
85 return true
86end
87
88return path_cmd
diff --git a/src/luarocks/cmd/purge.lua b/src/luarocks/cmd/purge.lua
new file mode 100644
index 00000000..b7d28f82
--- /dev/null
+++ b/src/luarocks/cmd/purge.lua
@@ -0,0 +1,79 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local package = _tl_compat and _tl_compat.package or package
2
3
4local purge = {}
5
6
7
8local util = require("luarocks.util")
9local path = require("luarocks.path")
10local search = require("luarocks.search")
11local vers = require("luarocks.core.vers")
12local repo_writer = require("luarocks.repo_writer")
13local cfg = require("luarocks.core.cfg")
14local remove = require("luarocks.remove")
15local queries = require("luarocks.queries")
16
17
18
19
20
21
22function purge.add_to_parser(parser)
23
24 local cmd = parser:command("purge", [[
25This command removes rocks en masse from a given tree.
26By default, it removes all rocks from a tree.
27
28The --tree option is mandatory: luarocks purge does not assume a default tree.]],
29 util.see_also()):
30 summary("Remove all installed rocks from a tree.")
31
32
33 cmd:flag("--old-versions", "Keep the highest-numbered version of each " ..
34 "rock and remove the other ones. By default it only removes old " ..
35 "versions if they are not needed as dependencies. This can be " ..
36 "overridden with the flag --force.")
37 cmd:flag("--force", "If --old-versions is specified, force removal of " ..
38 "previously installed versions if it would break dependencies.")
39 cmd:flag("--force-fast", "Like --force, but performs a forced removal " ..
40 "without reporting dependency issues.")
41end
42
43function purge.command(args)
44 local tree = args.tree
45
46 local results = {}
47 search.local_manifest_search(results, path.rocks_dir(tree), queries.all())
48
49 local sort = function(a, b) return vers.compare_versions(b, a) end
50 if args.old_versions then
51 sort = vers.compare_versions
52 end
53
54 for package, versions in util.sortedpairs(results) do
55 for version, _ in util.sortedpairs(versions, sort) do
56 if args.old_versions then
57 util.printout("Keeping " .. package .. " " .. version .. "...")
58 local ok, err, warn = remove.remove_other_versions(package, version, args.force, args.force_fast)
59 if not ok then
60 util.printerr(err)
61 elseif warn then
62 util.printerr(err)
63 end
64 break
65 else
66 util.printout("Removing " .. package .. " " .. version .. "...")
67 local ok, err = repo_writer.delete_version(package, version, "none", true)
68 if not ok then
69 util.printerr(err)
70 end
71 end
72 end
73 end
74 return repo_writer.refresh_manifest(cfg.rocks_dir)
75end
76
77purge.needs_lock = function() return true end
78
79return purge
diff --git a/src/luarocks/cmd/remove.lua b/src/luarocks/cmd/remove.lua
new file mode 100644
index 00000000..5a0f7dc5
--- /dev/null
+++ b/src/luarocks/cmd/remove.lua
@@ -0,0 +1,77 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string
2
3
4local cmd_remove = {}
5
6
7
8local remove = require("luarocks.remove")
9local util = require("luarocks.util")
10local cfg = require("luarocks.core.cfg")
11local search = require("luarocks.search")
12local path = require("luarocks.path")
13local deps = require("luarocks.deps")
14local queries = require("luarocks.queries")
15
16
17
18
19
20function cmd_remove.add_to_parser(parser)
21
22 local cmd = parser:command("remove", [[
23Uninstall a rock.
24
25If a version is not given, try to remove all versions at once.
26Will only perform the removal if it does not break dependencies.
27To override this check and force the removal, use --force or --force-fast.]],
28 util.see_also()):
29 summary("Uninstall a rock.")
30
31
32 cmd:argument("rock", "Name of the rock to be uninstalled."):
33 action(util.namespaced_name_action)
34 cmd:argument("version", "Version of the rock to uninstall."):
35 args("?")
36
37 cmd:flag("--force", "Force removal if it would break dependencies.")
38 cmd:flag("--force-fast", "Perform a forced removal without reporting dependency issues.")
39 util.deps_mode_option(cmd)
40end
41
42
43
44
45function cmd_remove.command(args)
46 local name = args.rock
47 local deps_mode = deps.get_deps_mode(args)
48
49 local rock_type = name:match("%.(rock)$") or name:match("%.(rockspec)$")
50 local version = args.version
51 local filename = name
52 if rock_type then
53 name, version = path.parse_name(filename)
54 if not name then return nil, "Invalid " .. rock_type .. " filename: " .. filename end
55 end
56
57 name = name:lower()
58
59 local results = {}
60 search.local_manifest_search(results, cfg.rocks_dir, queries.new(name, args.namespace, version))
61 if not results[name] then
62 local rock = util.format_rock_name(name, args.namespace, version)
63 return nil, "Could not find rock '" .. rock .. "' in " .. path.rocks_tree_to_string(cfg.root_dir)
64 end
65
66 local ok, err = remove.remove_search_results(results, name, deps_mode, args.force, args.force_fast)
67 if not ok then
68 return nil, err
69 end
70
71 deps.check_dependencies(nil, deps.get_deps_mode(args))
72 return true
73end
74
75cmd_remove.needs_lock = function() return true end
76
77return cmd_remove
diff --git a/src/luarocks/cmd/search.lua b/src/luarocks/cmd/search.lua
new file mode 100644
index 00000000..3eaeda4d
--- /dev/null
+++ b/src/luarocks/cmd/search.lua
@@ -0,0 +1,91 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs
2
3
4local cmd_search = {}
5
6
7local cfg = require("luarocks.core.cfg")
8local util = require("luarocks.util")
9local search = require("luarocks.search")
10local queries = require("luarocks.queries")
11local results = require("luarocks.results")
12
13
14
15
16
17
18
19function cmd_search.add_to_parser(parser)
20 local cmd = parser:command("search", "Query the LuaRocks servers.", util.see_also())
21
22 cmd:argument("name", "Name of the rock to search for."):
23 args("?"):
24 action(util.namespaced_name_action)
25 cmd:argument("version", "Rock version to search for."):
26 args("?")
27
28 cmd:flag("--source", "Return only rockspecs and source rocks, to be used " ..
29 'with the "build" command.')
30 cmd:flag("--binary", "Return only pure Lua and binary rocks (rocks that " ..
31 'can be used with the "install" command without requiring a C toolchain).')
32 cmd:flag("--all", "List all contents of the server that are suitable to " ..
33 "this platform, do not filter by name.")
34 cmd:flag("--porcelain", "Return a machine readable format.")
35end
36
37
38
39
40
41
42
43local function split_source_and_binary_results(result_tree)
44 local sources, binaries = {}, {}
45 for name, versions in pairs(result_tree) do
46 for version, repositories in pairs(versions) do
47 for _, repo in ipairs(repositories) do
48 local where = sources
49 if repo.arch == "all" or repo.arch == cfg.arch then
50 where = binaries
51 end
52 local entry = results.new(name, version, repo.repo, repo.arch)
53 search.store_result(where, entry)
54 end
55 end
56 end
57 return sources, binaries
58end
59
60
61
62
63function cmd_search.command(args)
64 local name = args.name
65
66 if args.all then
67 name, args.version = "", nil
68 end
69
70 if not args.name and not args.all then
71 return nil, "Enter name and version or use --all. " .. util.see_help("search")
72 end
73
74 local query = queries.new(name, args.namespace, args.version, true)
75 local result_tree = search.search_repos(query)
76 local porcelain = args.porcelain
77 local full_name = util.format_rock_name(name, args.namespace, args.version)
78 util.title(full_name .. " - Search results for Lua " .. cfg.lua_version .. ":", porcelain, "=")
79 local sources, binaries = split_source_and_binary_results(result_tree)
80 if next(sources) and not args.binary then
81 util.title("Rockspecs and source rocks:", porcelain)
82 search.print_result_tree(sources, porcelain)
83 end
84 if next(binaries) and not args.source then
85 util.title("Binary and pure-Lua rocks:", porcelain)
86 search.print_result_tree(binaries, porcelain)
87 end
88 return true
89end
90
91return cmd_search
diff --git a/src/luarocks/cmd/show.lua b/src/luarocks/cmd/show.lua
new file mode 100644
index 00000000..58d6701e
--- /dev/null
+++ b/src/luarocks/cmd/show.lua
@@ -0,0 +1,341 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local math = _tl_compat and _tl_compat.math or math; local os = _tl_compat and _tl_compat.os or os; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2
3local show = {Return = {}, }
4
5
6
7
8
9
10
11local queries = require("luarocks.queries")
12local search = require("luarocks.search")
13local dir = require("luarocks.core.dir")
14local fs = require("luarocks.fs")
15local cfg = require("luarocks.core.cfg")
16local util = require("luarocks.util")
17local path = require("luarocks.path")
18local fetch = require("luarocks.fetch")
19local manif = require("luarocks.manif")
20local repos = require("luarocks.repos")
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38function show.add_to_parser(parser)
39 local cmd = parser:command("show", [[
40Show information about an installed rock.
41
42Without any flags, show all module information.
43With flags, return only the desired information.]], util.see_also()):
44 summary("Show information about an installed rock.")
45
46 cmd:argument("rock", "Name of an installed rock."):
47 action(util.namespaced_name_action)
48 cmd:argument("version", "Rock version."):
49 args("?")
50
51 cmd:flag("--home", "Show home page of project.")
52 cmd:flag("--modules", "Show all modules provided by the package as used by require().")
53 cmd:flag("--deps", "Show packages the package depends on.")
54 cmd:flag("--build-deps", "Show build-only dependencies for the package.")
55 cmd:flag("--test-deps", "Show dependencies for testing the package.")
56 cmd:flag("--rockspec", "Show the full path of the rockspec file.")
57 cmd:flag("--mversion", "Show the package version.")
58 cmd:flag("--rock-tree", "Show local tree where rock is installed.")
59 cmd:flag("--rock-namespace", "Show rock namespace.")
60 cmd:flag("--rock-dir", "Show data directory of the installed rock.")
61 cmd:flag("--rock-license", "Show rock license.")
62 cmd:flag("--issues", "Show URL for project's issue tracker.")
63 cmd:flag("--labels", "List the labels of the rock.")
64 cmd:flag("--porcelain", "Produce machine-friendly output.")
65end
66
67local friendly_template = [[
68 :
69?namespace:${namespace}/${package} ${version} - ${summary}
70!namespace:${package} ${version} - ${summary}
71 :
72*detailed :${detailed}
73?detailed :
74?license :License: \t${license}
75?homepage :Homepage: \t${homepage}
76?issues :Issues: \t${issues}
77?labels :Labels: \t${labels}
78?location :Installed in: \t${location}
79?commands :
80?commands :Commands:
81*commands :\t${name} (${file})
82?modules :
83?modules :Modules:
84*modules :\t${name} (${file})
85?bdeps :
86?bdeps :Has build dependency on:
87*bdeps :\t${name} (${label})
88?tdeps :
89?tdeps :Tests depend on:
90*tdeps :\t${name} (${label})
91?deps :
92?deps :Depends on:
93*deps :\t${name} (${label})
94?ideps :
95?ideps :Indirectly pulling:
96*ideps :\t${name} (${label})
97 :
98]]
99
100local porcelain_template = [[
101?namespace:namespace\t${namespace}
102?package :package\t${package}
103?version :version\t${version}
104?summary :summary\t${summary}
105*detailed :detailed\t${detailed}
106?license :license\t${license}
107?homepage :homepage\t${homepage}
108?issues :issues\t${issues}
109?labels :labels\t${labels}
110?location :location\t${location}
111*commands :command\t${name}\t${file}
112*modules :module\t${name}\t${file}
113*bdeps :build_dependency\t${name}\t${label}
114*tdeps :test_dependency\t${name}\t${label}
115*deps :dependency\t${name}\t${label}
116*ideps :indirect_dependency\t${name}\t${label}
117]]
118
119local function keys_as_string(t, sep)
120 local keys = util.keys(t)
121 table.sort(keys)
122 return table.concat(keys, sep or " ")
123end
124
125local function word_wrap(line)
126 local width = math.tointeger(os.getenv("COLUMNS")) or 80
127 if width > 80 then width = 80 end
128 if #line > width then
129 local brk = width
130 while brk > 0 and line:sub(brk, brk) ~= " " do
131 brk = brk - 1
132 end
133 if brk > 0 then
134 return line:sub(1, brk - 1) .. "\n" .. word_wrap(line:sub(brk + 1))
135 end
136 end
137 return line
138end
139
140local function format_text(text)
141 text = text:gsub("^%s*", ""):gsub("%s$", ""):gsub("\n[ \t]+", "\n"):gsub("([^\n])\n([^\n])", "%1 %2")
142 local paragraphs = util.split_string(text, "\n\n")
143 for n, line in ipairs(paragraphs) do
144 paragraphs[n] = word_wrap(line)
145 end
146 return (table.concat(paragraphs, "\n\n"):gsub("%s$", ""))
147end
148
149local function installed_rock_label(dep, tree)
150 local installed, version
151 local rocks_provided = util.get_rocks_provided()
152 if rocks_provided[dep.name] then
153 installed, version = true, rocks_provided[dep.name]
154 else
155 local name
156 name, version = search.pick_installed_rock(dep, tree)
157 installed = name ~= nil
158 end
159 return installed and "using " .. version or "missing"
160end
161
162local function render(template, data)
163 local out = {}
164 for cmd, var, line in template:gmatch("(.)([a-z]*)%s*:([^\n]*)\n") do
165 line = line:gsub("\\t", "\t")
166 local d = data[var]
167 if cmd == " " then
168 table.insert(out, line)
169 elseif cmd == "?" or cmd == "*" or cmd == "!" then
170 if (cmd == "!" and d == nil) or
171 (cmd ~= "!" and (type(d) == "string" or
172 (type(d) == "table" and next(d)))) then
173 local n = type(d) == "table" and #d or 1
174 if cmd ~= "*" then
175 n = 1
176 end
177 for i = 1, n do
178 local tbl = cmd == "*" and type(d) == "table" and d[i] or data
179 if type(tbl) == "string" then
180 tbl = tbl:gsub("%%", "%%%%")
181 end
182 table.insert(out, (line:gsub("${([a-z]+)}", tbl)))
183 end
184 end
185 end
186 end
187 return table.concat(out, "\n")
188end
189
190local function adjust_path(name, version, basedir, pathname, suffix)
191 pathname = dir.path(basedir, pathname)
192 local vpathname = path.versioned_name(pathname, basedir, name, version)
193 return (fs.exists(vpathname) and
194 vpathname or
195 pathname) .. (suffix or "")
196end
197
198local function modules_to_list(name, version, repo)
199 local ret = {}
200 local rock_manifest = manif.load_rock_manifest(name, version, repo)
201
202 local lua_dir = path.deploy_lua_dir(repo)
203 local lib_dir = path.deploy_lib_dir(repo)
204 repos.recurse_rock_manifest_entry(rock_manifest.lua, function(pathname)
205 table.insert(ret, {
206 name = path.path_to_module(pathname),
207 file = adjust_path(name, version, lua_dir, pathname),
208 })
209 end)
210 repos.recurse_rock_manifest_entry(rock_manifest.lib, function(pathname)
211 table.insert(ret, {
212 name = path.path_to_module(pathname),
213 file = adjust_path(name, version, lib_dir, pathname),
214 })
215 end)
216 table.sort(ret, function(a, b)
217 if a.name == b.name then
218 return a.file < b.file
219 end
220 return a.name < b.name
221 end)
222 return ret
223end
224
225local function commands_to_list(name, version, repo)
226 local ret = {}
227 local rock_manifest = manif.load_rock_manifest(name, version, repo)
228
229 local bin_dir = path.deploy_bin_dir(repo)
230 repos.recurse_rock_manifest_entry(rock_manifest.bin, function(pathname)
231 table.insert(ret, {
232 name = name,
233 file = adjust_path(name, version, bin_dir, pathname, cfg.wrapper_suffix),
234 })
235 end)
236 table.sort(ret, function(a, b)
237 if a.name == b.name then
238 return a.file < b.file
239 end
240 return a.name < b.name
241 end)
242 return ret
243end
244
245local function deps_to_list(dependencies, tree)
246 local ret = {}
247 for _, dep in ipairs(dependencies.queries or {}) do
248 table.insert(ret, { name = tostring(dep), label = installed_rock_label(dep, tree) })
249 end
250 return ret
251end
252
253local function indirect_deps(mdeps, rdeps, tree)
254 local ret = {}
255 local direct_deps = {}
256 for _, dep in ipairs(rdeps) do
257 direct_deps[dep] = true
258 end
259 for dep_name in util.sortedpairs(mdeps or {}) do
260 if not direct_deps[dep_name] then
261 table.insert(ret, { name = tostring(dep_name), label = installed_rock_label(queries.new(dep_name), tree) })
262 end
263 end
264 return ret
265end
266
267local function show_rock(template, namespace, name, version, rockspec, repo, minfo, tree)
268 local desc = rockspec.description or {}
269 local data = {
270 namespace = namespace,
271 package = rockspec.package,
272 version = rockspec.version,
273 summary = desc.summary or "",
274 detailed = desc.detailed and util.split_string(format_text(desc.detailed), "\n"),
275 license = desc.license,
276 homepage = desc.homepage,
277 issues = desc.issues_url,
278 labels = desc.labels and table.concat(desc.labels, ", "),
279 location = path.rocks_tree_to_string(repo),
280 commands = commands_to_list(name, version, repo),
281 modules = modules_to_list(name, version, repo),
282 bdeps = deps_to_list(rockspec.build_dependencies, tree),
283 tdeps = deps_to_list(rockspec.test_dependencies, tree),
284 deps = deps_to_list(rockspec.dependencies, tree),
285 ideps = indirect_deps(minfo.dependencies, rockspec.dependencies, tree),
286 }
287 util.printout(render(template, data))
288end
289
290
291
292function show.command(args)
293 local query = queries.new(args.rock, args.namespace, args.version, true)
294
295 local name, version, repo, repo_url = search.pick_installed_rock(query, args.tree)
296 if not name then
297 return nil, version
298 end
299 local tree = path.rocks_tree_to_string(repo)
300 local directory = path.install_dir(name, version, repo)
301 local namespace = path.read_namespace(name, version, tree)
302 local rockspec_file = path.rockspec_file(name, version, repo)
303 local rockspec, err = fetch.load_local_rockspec(rockspec_file)
304 if not rockspec then return nil, err end
305
306 local descript = rockspec.description or {}
307 local manifest, err = manif.load_manifest(repo_url)
308 if not manifest then return nil, err end
309 local minfo = manifest.repository[name][version][1]
310
311 if args.rock_tree then util.printout(tree)
312 elseif args.rock_namespace then util.printout(namespace)
313 elseif args.rock_dir then util.printout(directory)
314 elseif args.home then util.printout(descript.homepage)
315 elseif args.rock_license then util.printout(descript.license)
316 elseif args.issues then util.printout(descript.issues_url)
317 elseif args.labels then util.printout(descript.labels and table.concat(descript.labels, "\n"))
318 elseif args.modules then util.printout(keys_as_string(minfo.modules, "\n"))
319 elseif args.deps then
320 for _, dep in ipairs(rockspec.dependencies) do
321 util.printout(tostring(dep))
322 end
323 elseif args.build_deps then
324 for _, dep in ipairs(rockspec.build_dependencies) do
325 util.printout(tostring(dep))
326 end
327 elseif args.test_deps then
328 for _, dep in ipairs(rockspec.test_dependencies) do
329 util.printout(tostring(dep))
330 end
331 elseif args.rockspec then util.printout(rockspec_file)
332 elseif args.mversion then util.printout(version)
333 elseif args.porcelain then
334 show_rock(porcelain_template, namespace, name, version, rockspec, repo, minfo, args.tree)
335 else
336 show_rock(friendly_template, namespace, name, version, rockspec, repo, minfo, args.tree)
337 end
338 return true
339end
340
341return show
diff --git a/src/luarocks/cmd/test.lua b/src/luarocks/cmd/test.lua
new file mode 100644
index 00000000..9305edba
--- /dev/null
+++ b/src/luarocks/cmd/test.lua
@@ -0,0 +1,53 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2
3
4local cmd_test = {}
5
6
7local util = require("luarocks.util")
8local test = require("luarocks.test")
9
10
11
12
13
14function cmd_test.add_to_parser(parser)
15 local cmd = parser:command("test", [[
16Run the test suite for the Lua project in the current directory.
17
18If the first argument is a rockspec, it will use it to determine the parameters
19for running tests; otherwise, it will attempt to detect the rockspec.
20
21Any additional arguments are forwarded to the test suite.
22To make sure that test suite flags are not interpreted as LuaRocks flags, use --
23to separate LuaRocks arguments from test suite arguments.]],
24 util.see_also()):
25 summary("Run the test suite in the current directory.")
26
27 cmd:argument("rockspec", "Project rockspec."):
28 args("?")
29 cmd:argument("args", "Test suite arguments."):
30 args("*")
31 cmd:flag("--prepare", "Only install dependencies needed for testing only, but do not run the test")
32
33 cmd:option("--test-type", "Specify the test suite type manually if it was " ..
34 "not specified in the rockspec and it could not be auto-detected."):
35 argname("<type>")
36end
37
38function cmd_test.command(args)
39 if args.rockspec and args.rockspec:match("rockspec$") then
40 return test.run_test_suite(args.rockspec, args.test_type, args.args, args.prepare)
41 end
42
43 table.insert(args.args, 1, args.rockspec)
44
45 local rockspec, err = util.get_default_rockspec()
46 if not rockspec then
47 return nil, err
48 end
49
50 return test.run_test_suite(rockspec, args.test_type, args.args, args.prepare)
51end
52
53return cmd_test
diff --git a/src/luarocks/cmd/unpack.lua b/src/luarocks/cmd/unpack.lua
new file mode 100644
index 00000000..5a1ae799
--- /dev/null
+++ b/src/luarocks/cmd/unpack.lua
@@ -0,0 +1,172 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string
2
3
4local unpack = {}
5
6
7local fetch = require("luarocks.fetch")
8local fs = require("luarocks.fs")
9local util = require("luarocks.util")
10local build = require("luarocks.build")
11local dir = require("luarocks.dir")
12local search = require("luarocks.search")
13
14
15
16
17
18
19
20function unpack.add_to_parser(parser)
21 local cmd = parser:command("unpack", [[
22Unpacks the contents of a rock in a newly created directory.
23Argument may be a rock file, or the name of a rock in a rocks server.
24In the latter case, the rock version may be given as a second argument.]],
25 util.see_also()):
26 summary("Unpack the contents of a rock.")
27
28 cmd:argument("rock", "A rock file or the name of a rock."):
29 action(util.namespaced_name_action)
30 cmd:argument("version", "Rock version."):
31 args("?")
32
33 cmd:flag("--force", "Unpack files even if the output directory already exists.")
34 cmd:flag("--check-lua-versions", "If the rock can't be found, check repository " ..
35 "and report if it is available for another Lua version.")
36end
37
38
39
40
41
42
43
44local function unpack_rockspec(rockspec_file, dir_name)
45
46 local rockspec, err = fetch.load_rockspec(rockspec_file)
47 if not rockspec then
48 return nil, "Failed loading rockspec " .. rockspec_file .. ": " .. err
49 end
50 local ok, err = fs.change_dir(dir_name)
51 if not ok then return nil, err end
52 local filename, sources_dir = fetch.fetch_sources(rockspec, true, ".")
53 if not filename then
54 return nil, sources_dir
55 end
56 ok, err = fs.change_dir(sources_dir)
57 if not ok then return nil, err end
58 ok, err = build.apply_patches(rockspec)
59 fs.pop_dir()
60 if not ok then return nil, err end
61 return rockspec
62end
63
64
65
66
67
68
69
70
71local function unpack_rock(rock_file, dir_name, kind)
72
73 local ok, filename, err, errcode
74 filename, err, errcode = fetch.fetch_and_unpack_rock(rock_file, dir_name)
75 if not filename then
76 return nil, err, errcode
77 end
78 ok, err = fs.change_dir(dir_name)
79 if not ok then return nil, err end
80 local rockspec_file = dir_name .. ".rockspec"
81 local rockspec, err = fetch.load_rockspec(rockspec_file)
82 if not rockspec then
83 return nil, "Failed loading rockspec " .. rockspec_file .. ": " .. err
84 end
85 if kind == "src" then
86 if rockspec.source.file then
87 ok, err = fs.unpack_archive(rockspec.source.file)
88 if not ok then return nil, err end
89 ok, err = fetch.find_rockspec_source_dir(rockspec, ".")
90 if not ok then return nil, err end
91 ok, err = fs.change_dir(rockspec.source.dir)
92 if not ok then return nil, err end
93 ok, err = build.apply_patches(rockspec)
94 fs.pop_dir()
95 if not ok then return nil, err end
96 end
97 end
98 return rockspec
99end
100
101
102
103
104
105
106
107local function run_unpacker(file, force)
108
109 local base_name = dir.base_name(file)
110 local dir_name, kind, extension = base_name:match("(.*)%.([^.]+)%.(rock)$")
111 if not extension then
112 dir_name, extension = base_name:match("(.*)%.(rockspec)$")
113 kind = "rockspec"
114 end
115 if not extension then
116 return nil, file .. " does not seem to be a valid filename."
117 end
118
119 local exists = fs.exists(dir_name)
120 if exists and not force then
121 return nil, "Directory " .. dir_name .. " already exists."
122 end
123 if not exists then
124 local ok, err = fs.make_dir(dir_name)
125 if not ok then return nil, err end
126 end
127 local rollback = util.schedule_function(fs.delete, fs.absolute_name(dir_name))
128
129 local rockspec, err
130 if extension == "rock" then
131 rockspec, err = unpack_rock(file, dir_name, kind)
132 elseif extension == "rockspec" then
133 rockspec, err = unpack_rockspec(file, dir_name)
134 end
135 if not rockspec then
136 return nil, err
137 end
138 if kind == "src" or kind == "rockspec" then
139 fetch.find_rockspec_source_dir(rockspec, ".")
140 if rockspec.source.dir ~= "." then
141 local ok = fs.copy(rockspec.local_abs_filename, rockspec.source.dir, "read")
142 if not ok then
143 return nil, "Failed copying unpacked rockspec into unpacked source directory."
144 end
145 end
146 util.printout()
147 util.printout("Done. You may now enter directory ")
148 util.printout(dir.path(dir_name, rockspec.source.dir))
149 util.printout("and type 'luarocks make' to build.")
150 end
151 util.remove_scheduled_function(rollback)
152 return true
153end
154
155
156
157
158function unpack.command(args)
159 local url, err
160 if args.rock:match(".*%.rock") or args.rock:match(".*%.rockspec") then
161 url = args.rock
162 else
163 url, err = search.find_src_or_rockspec(args.rock, args.namespace, args.version, args.check_lua_versions)
164 if not url then
165 return nil, err
166 end
167 end
168
169 return run_unpacker(url, args.force)
170end
171
172return unpack
diff --git a/src/luarocks/cmd/upload.lua b/src/luarocks/cmd/upload.lua
new file mode 100644
index 00000000..70a1f9b9
--- /dev/null
+++ b/src/luarocks/cmd/upload.lua
@@ -0,0 +1,144 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string
2local upload = {Response = {version = {}, }, }
3
4
5
6
7
8
9
10
11
12
13
14local signing = require("luarocks.signing")
15local util = require("luarocks.util")
16local fetch = require("luarocks.fetch")
17local pack = require("luarocks.pack")
18local cfg = require("luarocks.core.cfg")
19local Api = require("luarocks.upload.api")
20
21
22
23
24
25
26
27function upload.add_to_parser(parser)
28 local cmd = parser:command("upload", "Pack a source rock file (.src.rock extension) " ..
29 "and upload it and the rockspec to the public rocks repository.", util.see_also()):
30 summary("Upload a rockspec to the public rocks repository.")
31
32 cmd:argument("rockspec", "Rockspec for the rock to upload.")
33 cmd:argument("src-rock", "A corresponding .src.rock file; if not given it will be generated."):
34 args("?")
35
36 cmd:flag("--skip-pack", "Do not pack and send source rock.")
37 cmd:option("--api-key", "Pass an API key. It will be stored for subsequent uses."):
38 argname("<key>")
39 cmd:option("--temp-key", "Use the given a temporary API key in this " ..
40 "invocation only. It will not be stored."):
41 argname("<key>")
42 cmd:flag("--force", "Replace existing rockspec if the same revision of a " ..
43 "module already exists. This should be used only in case of upload " ..
44 "mistakes: when updating a rockspec, increment the revision number " ..
45 "instead.")
46 cmd:flag("--sign", "Upload a signature file alongside each file as well.")
47 cmd:flag("--debug"):hidden(true)
48end
49
50local function is_dev_version(version)
51 return version:match("^dev") or version:match("^scm")
52end
53
54function upload.command(args)
55 local api, err = Api.new(args)
56 if not api then
57 return nil, err
58 end
59 if cfg.verbose then
60 api.debug = true
61 end
62
63 local rockspec, err, errcode = fetch.load_rockspec(args.rockspec)
64 if err then
65 return nil, err, errcode
66 end
67
68 util.printout("Sending " .. tostring(args.rockspec) .. " ...")
69 local res, err = api:method("check_rockspec", {
70 package = rockspec.package,
71 version = rockspec.version,
72 })
73 if not res then return nil, err end
74
75 if not res.module then
76 util.printout("Will create new module (" .. tostring(rockspec.package) .. ")")
77 end
78 if res.version and not args.force then
79 return nil, "Revision " .. rockspec.version .. " already exists on the server. " .. util.see_help("upload")
80 end
81
82 local sigfname
83 local rock_sigfname
84
85 if args.sign then
86 sigfname, err = signing.sign_file(args.rockspec)
87 if err then
88 return nil, "Failed signing rockspec: " .. err
89 end
90 util.printout("Signed rockspec: " .. sigfname)
91 end
92
93 local rock_fname
94 if args.src_rock then
95 rock_fname = args.src_rock
96 elseif not args.skip_pack and not is_dev_version(rockspec.version) then
97 util.printout("Packing " .. tostring(rockspec.package))
98 rock_fname, err = pack.pack_source_rock(args.rockspec)
99 if not rock_fname then
100 return nil, err
101 end
102 end
103
104 if rock_fname and args.sign then
105 rock_sigfname, err = signing.sign_file(rock_fname)
106 if err then
107 return nil, "Failed signing rock: " .. err
108 end
109 util.printout("Signed packed rock: " .. rock_sigfname)
110 end
111
112 local multipart = require("luarocks.upload.multipart")
113
114 res, err = api:method("upload", nil, {
115 rockspec_file = multipart.new_file(args.rockspec),
116 rockspec_sig = sigfname and multipart.new_file(sigfname),
117 })
118 if not res then return nil, err end
119
120 if res.is_new and #res.manifests == 0 then
121 util.printerr("Warning: module not added to root manifest due to name taken.")
122 end
123
124 local module_url = res.module_url
125
126 if rock_fname then
127 if (not res.version) or (not res.version.id) then
128 return nil, "Invalid response from server."
129 end
130 util.printout(("Sending " .. tostring(rock_fname) .. " ..."))
131 res, err = api:method("upload_rock/" .. ("%d"):format(res.version.id), nil, {
132 rock_file = multipart.new_file(rock_fname),
133 rock_sig = rock_sigfname and multipart.new_file(rock_sigfname),
134 })
135 if not res then return nil, err end
136 end
137
138 util.printout()
139 util.printout("Done: " .. tostring(module_url))
140 util.printout()
141 return true
142end
143
144return upload
diff --git a/src/luarocks/cmd/which.lua b/src/luarocks/cmd/which.lua
new file mode 100644
index 00000000..c49d43c4
--- /dev/null
+++ b/src/luarocks/cmd/which.lua
@@ -0,0 +1,44 @@
1
2
3
4local which_cmd = {}
5
6
7local loader = require("luarocks.loader")
8local cfg = require("luarocks.core.cfg")
9local util = require("luarocks.util")
10
11
12
13
14
15function which_cmd.add_to_parser(parser)
16 local cmd = parser:command("which", 'Given a module name like "foo.bar", ' ..
17 "output which file would be loaded to resolve that module by " ..
18 'luarocks.loader, like "/usr/local/lua/' .. cfg.lua_version .. '/foo/bar.lua".',
19 util.see_also()):
20 summary("Tell which file corresponds to a given module name.")
21
22 cmd:argument("modname", "Module name.")
23end
24
25
26
27function which_cmd.command(args)
28 local pathname, rock_name, rock_version, where = loader.which(args.modname, "lp")
29
30 if pathname then
31 util.printout(pathname)
32 if where == "l" then
33 util.printout("(provided by " .. tostring(rock_name) .. " " .. tostring(rock_version) .. ")")
34 else
35 local key = rock_name
36 util.printout("(found directly via package." .. key .. " -- not installed as a rock?)")
37 end
38 return true
39 end
40
41 return nil, "Module '" .. args.modname .. "' not found."
42end
43
44return which_cmd
diff --git a/src/luarocks/cmd/write_rockspec.lua b/src/luarocks/cmd/write_rockspec.lua
new file mode 100644
index 00000000..156339b8
--- /dev/null
+++ b/src/luarocks/cmd/write_rockspec.lua
@@ -0,0 +1,423 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2local write_rockspec = {}
3
4
5local builtin = require("luarocks.build.builtin")
6local cfg = require("luarocks.core.cfg")
7local dir = require("luarocks.dir")
8local fetch = require("luarocks.fetch")
9local fs = require("luarocks.fs")
10local persist = require("luarocks.persist")
11local rockspecs = require("luarocks.rockspecs")
12local type_rockspec = require("luarocks.type.rockspec")
13local util = require("luarocks.util")
14
15
16
17
18
19
20
21
22
23
24
25
26local lua_versions = {
27 "5.1",
28 "5.2",
29 "5.3",
30 "5.4",
31 "5.1,5.2",
32 "5.2,5.3",
33 "5.3,5.4",
34 "5.1,5.2,5.3",
35 "5.2,5.3,5.4",
36 "5.1,5.2,5.3,5.4",
37}
38
39function write_rockspec.cmd_options(parser)
40 parser:option("--output", "Write the rockspec with the given filename.\n" ..
41 "If not given, a file is written in the current directory with a " ..
42 "filename based on given name and version."):
43 argname("<file>")
44 parser:option("--license", 'A license string, such as "MIT/X11" or "GNU GPL v3".'):
45 argname("<string>")
46 parser:option("--summary", "A short one-line description summary."):
47 argname("<txt>")
48 parser:option("--detailed", "A longer description string."):
49 argname("<txt>")
50 parser:option("--homepage", "Project homepage."):
51 argname("<txt>")
52 parser:option("--lua-versions", 'Supported Lua versions. Accepted values are: "' ..
53 table.concat(lua_versions, '", "') .. '".'):
54 argname("<ver>"):
55 choices(lua_versions)
56 parser:option("--rockspec-format", 'Rockspec format version, such as "1.0" or "1.1".'):
57 argname("<ver>")
58 parser:option("--tag", "Tag to use. Will attempt to extract version number from it.")
59 parser:option("--lib", "A comma-separated list of libraries that C files need to link to."):
60 argname("<libs>")
61end
62
63function write_rockspec.add_to_parser(parser)
64 local cmd = parser:command("write_rockspec", [[
65This command writes an initial version of a rockspec file,
66based on a name, a version, and a location (an URL or a local path).
67If only two arguments are given, the first one is considered the name and the
68second one is the location.
69If only one argument is given, it must be the location.
70If no arguments are given, current directory is used as the location.
71LuaRocks will attempt to infer name and version if not given,
72using 'dev' as a fallback default version.
73
74Note that the generated file is a _starting point_ for writing a
75rockspec, and is not guaranteed to be complete or correct. ]], util.see_also()):
76 summary("Write a template for a rockspec file.")
77
78 cmd:argument("name", "Name of the rock."):
79 args("?")
80 cmd:argument("version", "Rock version."):
81 args("?")
82 cmd:argument("location", "URL or path to the rock sources."):
83 args("?")
84
85 write_rockspec.cmd_options(cmd)
86end
87
88local function open_file(name)
89 return io.open(dir.path(fs.current_dir(), name), "r")
90end
91
92local function fetch_url(rockspec)
93 local file, temp_dir, err_code, err_file, err_temp_dir = fetch.fetch_sources(rockspec, false)
94 if err_code == "source.dir" then
95 file, temp_dir = err_file, err_temp_dir
96 elseif not file then
97 util.warning("Could not fetch sources - " .. temp_dir)
98 return false
99 end
100 util.printout("File successfully downloaded. Making checksum and checking base dir...")
101 if dir.is_basic_protocol(rockspec.source.protocol) then
102 rockspec.source.md5 = fs.get_md5(file)
103 end
104 local inferred_dir, found_dir = fetch.find_base_dir(file, temp_dir, rockspec.source.url)
105 return true, found_dir or inferred_dir, temp_dir
106end
107
108local lua_version_dep = {
109 ["5.1"] = "lua ~> 5.1",
110 ["5.2"] = "lua ~> 5.2",
111 ["5.3"] = "lua ~> 5.3",
112 ["5.4"] = "lua ~> 5.4",
113 ["5.1,5.2"] = "lua >= 5.1, < 5.3",
114 ["5.2,5.3"] = "lua >= 5.2, < 5.4",
115 ["5.3,5.4"] = "lua >= 5.3, < 5.5",
116 ["5.1,5.2,5.3"] = "lua >= 5.1, < 5.4",
117 ["5.2,5.3,5.4"] = "lua >= 5.2, < 5.5",
118 ["5.1,5.2,5.3,5.4"] = "lua >= 5.1, < 5.5",
119}
120
121local simple_scm_protocols = {
122 git = true,
123 ["git+http"] = true,
124 ["git+https"] = true,
125 ["git+ssh"] = true,
126 hg = true,
127 ["hg+http"] = true,
128 ["hg+https"] = true,
129 ["hg+ssh"] = true,
130}
131
132local detect_url
133do
134 local function detect_url_from_command(program, args, directory)
135 local command = fs.Q(cfg.variables[program:upper()]) .. " " .. args
136 local pipe = io.popen(fs.command_at(directory, fs.quiet_stderr(command)))
137 if not pipe then return nil end
138 local url = pipe:read("*a"):match("^([^\r\n]+)")
139 pipe:close()
140 if not url then return nil end
141 if url:match("^[^@:/]+@[^@:/]+:.*$") then
142 local u, h, p = url:match("^([^@]+)@([^:]+):(.*)$")
143 url = program .. "+ssh://" .. u .. "@" .. h .. "/" .. p
144 elseif not util.starts_with(url, program .. "://") then
145 url = program .. "+" .. url
146 end
147
148 if (simple_scm_protocols)[dir.split_url(url)] then
149 return url
150 end
151 end
152
153 local function detect_scm_url(directory)
154 return detect_url_from_command("git", "config --get remote.origin.url", directory) or
155 detect_url_from_command("hg", "paths default", directory)
156 end
157
158 detect_url = function(url_or_dir)
159 if url_or_dir:match("://") then
160 return url_or_dir
161 else
162 return detect_scm_url(url_or_dir) or "*** please add URL for source tarball, zip or repository here ***"
163 end
164 end
165end
166
167local function detect_homepage(url, homepage)
168 if homepage then
169 return homepage
170 end
171 local url_protocol, url_path = dir.split_url(url)
172
173 if (simple_scm_protocols)[url_protocol] then
174 for _, domain in ipairs({ "github.com", "bitbucket.org", "gitlab.com" }) do
175 if util.starts_with(url_path, domain) then
176 return "https://" .. url_path:gsub("%.git$", "")
177 end
178 end
179 end
180
181 return "*** please enter a project homepage ***"
182end
183
184local function detect_description()
185 local fd = open_file("README.md") or open_file("README")
186 if not fd then return end
187 local data = fd:read("*a")
188 fd:close()
189 local paragraph = data:match("\n\n([^%[].-)\n\n")
190 if not paragraph then paragraph = data:match("\n\n(.*)") end
191 local summary, detailed
192 if paragraph then
193 detailed = paragraph
194
195 if #paragraph < 80 then
196 summary = paragraph:gsub("\n", "")
197 else
198 summary = paragraph:gsub("\n", " "):match("([^.]*%.) ")
199 end
200 end
201 return summary, detailed
202end
203
204local licenses = {
205 [78656] = "MIT",
206 [49311] = "ISC",
207}
208
209local function detect_license(data)
210 local strip_copyright = (data:gsub("^Copyright [^\n]*\n", ""))
211 local sum = 0
212 for i = 1, #strip_copyright do
213 local num = string.byte(strip_copyright:sub(i, i))
214 if num > 32 and num <= 128 then
215 sum = sum + num
216 end
217 end
218 return licenses[sum]
219end
220
221local function check_license()
222 local fd = open_file("COPYING") or open_file("LICENSE") or open_file("MIT-LICENSE.txt")
223 if not fd then return nil end
224 local data = fd:read("*a")
225 fd:close()
226 local license = detect_license(data)
227 if license then
228 return license, data
229 end
230 return nil, data
231end
232
233local function fill_as_builtin(rockspec, libs)
234 rockspec.build.type = "builtin"
235
236 local incdirs, libdirs
237 if libs then
238 incdirs, libdirs = {}, {}
239 for _, lib in ipairs(libs) do
240 local upper = lib:upper()
241 incdirs[#incdirs + 1] = "$(" .. upper .. "_INCDIR)"
242 libdirs[#libdirs + 1] = "$(" .. upper .. "_LIBDIR)"
243 end
244 end
245 (rockspec.build).modules, rockspec.build.install, rockspec.build.copy_directories = builtin.autodetect_modules(libs, incdirs, libdirs)
246end
247
248local function rockspec_cleanup(rockspec)
249 rockspec.source.file = nil
250 rockspec.source.protocol = nil
251 rockspec.source.identifier = nil
252 rockspec.source.dir = nil
253 rockspec.source.dir_set = nil
254 rockspec.source.pathname = nil
255 rockspec.variables = nil
256 rockspec.name = nil
257 rockspec.format_is_at_least = nil
258 rockspec.local_abs_filename = nil
259 rockspec.rocks_provided = nil
260
261 local dep_lists = {
262 dependencies = rockspec.dependencies,
263 build_dependencies = rockspec.build_dependencies,
264 test_dependencies = rockspec.test_dependencies,
265 }
266
267 for name, data in pairs(dep_lists) do
268 if not next(data) then
269 (rockspec)[name] = nil
270 else
271 for i, item in ipairs(data) do
272 data[i] = tostring(item)
273 end
274 end
275 end
276end
277
278function write_rockspec.command(args)
279 local name, version = args.name, args.version
280 local location = args.location
281
282 if not name then
283 location = "."
284 elseif not version then
285 location = name
286 name = nil
287 elseif not location then
288 location = version
289 version = nil
290 end
291
292 if args.tag then
293 if not version then
294 version = args.tag:gsub("^v", "")
295 end
296 end
297
298 local protocol, pathname = dir.split_url(location)
299 if protocol == "file" then
300 if pathname == "." then
301 name = name or dir.base_name(fs.current_dir())
302 end
303 elseif dir.is_basic_protocol(protocol) then
304 local filename = dir.base_name(location)
305 local newname, newversion = filename:match("(.*)-([^-]+)")
306 if newname then
307 name = name or newname
308 version = version or newversion:gsub("%.[a-z]+$", ""):gsub("%.tar$", "")
309 end
310 else
311 name = name or dir.base_name(location):gsub("%.[^.]+$", "")
312 end
313
314 if not name then
315 return nil, "Could not infer rock name. " .. util.see_help("write_rockspec")
316 end
317 version = version or "dev"
318
319 local filename = args.output or dir.path(fs.current_dir(), name:lower() .. "-" .. version .. "-1.rockspec")
320
321 local url = detect_url(location)
322 local homepage = detect_homepage(url, args.homepage)
323
324 local rockspec, err = rockspecs.from_persisted_table(filename, {
325 rockspec_format = args.rockspec_format,
326 package = name,
327 version = version .. "-1",
328 source = {
329 url = url,
330 tag = args.tag,
331 },
332 description = {
333 summary = args.summary or "*** please specify description summary ***",
334 detailed = args.detailed or "*** please enter a detailed description ***",
335 homepage = homepage,
336 license = args.license or "*** please specify a license ***",
337 },
338 dependencies = {
339 (lua_version_dep)[args.lua_versions],
340 },
341 build = {},
342 })
343 assert(not err, err)
344 rockspec.source.protocol = protocol
345
346 if not next(rockspec.dependencies) then
347 util.warning("Please specify supported Lua versions with --lua-versions=<ver>. " .. util.see_help("write_rockspec"))
348 end
349
350 local local_dir = location
351
352 if location:match("://") then
353 rockspec.source.file = dir.base_name(location)
354 if not dir.is_basic_protocol(rockspec.source.protocol) then
355 if version ~= "dev" then
356 rockspec.source.tag = args.tag or "v" .. version
357 end
358 end
359 rockspec.source.dir = nil
360 local ok, base_dir, temp_dir = fetch_url(rockspec)
361 if ok then
362 if base_dir ~= dir.base_name(location) then
363 rockspec.source.dir = base_dir
364 end
365 end
366 if base_dir then
367 local_dir = dir.path(temp_dir, base_dir)
368 else
369 local_dir = nil
370 end
371 end
372
373 if not local_dir then
374 local_dir = "."
375 end
376
377 local libs = nil
378 if args.lib then
379 libs = {}
380 rockspec.external_dependencies = {}
381 for lib in args.lib:gmatch("([^,]+)") do
382 table.insert(libs, lib)
383 rockspec.external_dependencies[lib:upper()] = {
384 library = lib,
385 }
386 end
387 end
388
389 local ok, err = fs.change_dir(local_dir)
390 if not ok then return nil, "Failed reaching files from project - error entering directory " .. local_dir end
391
392 if not (args.summary and args.detailed) then
393 local summary, detailed = detect_description()
394 rockspec.description.summary = args.summary or summary
395 rockspec.description.detailed = args.detailed or detailed
396 end
397
398 if not args.license then
399 local license, fulltext = check_license()
400 if license then
401 rockspec.description.license = license
402 elseif license then
403 util.title("Could not auto-detect type for project license:")
404 util.printout(fulltext)
405 util.printout()
406 util.title("Please fill in the source.license field manually or use --license.")
407 end
408 end
409
410 fill_as_builtin(rockspec, libs)
411
412 rockspec_cleanup(rockspec)
413
414 persist.save_from_table(filename, rockspec, type_rockspec.order)
415
416 util.printout()
417 util.printout("Wrote template at " .. filename .. " -- you should now edit and finish it.")
418 util.printout()
419
420 return true
421end
422
423return write_rockspec
diff --git a/src/luarocks/config.lua b/src/luarocks/config.lua
new file mode 100644
index 00000000..18ec90da
--- /dev/null
+++ b/src/luarocks/config.lua
@@ -0,0 +1,38 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local pairs = _tl_compat and _tl_compat.pairs or pairs; local config = {}
2
3local persist = require("luarocks.persist")
4
5local cfg_skip = {
6 errorcodes = true,
7 flags = true,
8 platforms = true,
9 root_dir = true,
10 upload_servers = true,
11}
12
13
14
15function config.should_skip(k, v)
16 return type(v) == "function" or cfg_skip[k]
17end
18
19local function cleanup(tbl)
20 local copy = {}
21 for k, v in pairs(tbl) do
22 if not (type(k) == "string" and config.should_skip(k, v)) then
23 copy[k] = v
24 end
25 end
26 return copy
27end
28
29function config.get_config_for_display(cfg)
30 return cleanup(cfg)
31end
32
33function config.to_string(cfg)
34 local cleancfg = config.get_config_for_display(cfg)
35 return persist.save_from_table_to_string(cleancfg)
36end
37
38return config
diff --git a/src/luarocks/core/dir.lua b/src/luarocks/core/dir.lua
new file mode 100644
index 00000000..b9b71c14
--- /dev/null
+++ b/src/luarocks/core/dir.lua
@@ -0,0 +1,95 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local package = _tl_compat and _tl_compat.package or package; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local dir = {}
2
3
4
5local dir_sep = package.config:sub(1, 1)
6
7local function unquote(c)
8 local first, last = c:sub(1, 1), c:sub(-1)
9 if (first == '"' and last == '"') or
10 (first == "'" and last == "'") then
11 return c:sub(2, -2)
12 end
13 return c
14end
15
16
17
18
19
20
21function dir.split_url(url)
22
23 url = unquote(url)
24 local protocol, pathname = url:match("^([^:]*)://(.*)")
25 if not protocol then
26 protocol = "file"
27 pathname = url
28 end
29 return protocol, pathname
30end
31
32
33
34
35
36
37
38function dir.normalize(name)
39 local protocol, pathname = dir.split_url(name)
40 pathname = pathname:gsub("\\", "/"):gsub("(.)/*$", "%1"):gsub("//", "/")
41 local pieces = {}
42 local drive = ""
43 if pathname:match("^.:") then
44 drive, pathname = pathname:match("^(.:)(.*)$")
45 end
46 pathname = pathname .. "/"
47 for piece in pathname:gmatch("(.-)/") do
48 if piece == ".." then
49 local prev = pieces[#pieces]
50 if not prev or prev == ".." then
51 table.insert(pieces, "..")
52 elseif prev ~= "" then
53 table.remove(pieces)
54 end
55 elseif piece ~= "." then
56 table.insert(pieces, piece)
57 end
58 end
59 if #pieces == 0 then
60 pathname = drive .. "."
61 elseif #pieces == 1 and pieces[1] == "" then
62 pathname = drive .. "/"
63 else
64 pathname = drive .. table.concat(pieces, "/")
65 end
66 if protocol ~= "file" then
67 pathname = protocol .. "://" .. pathname
68 else
69 pathname = pathname:gsub("/", dir_sep)
70 end
71 return pathname
72end
73
74
75
76
77
78
79
80
81
82
83
84function dir.path(...)
85 local t = { ... }
86 while t[1] == "" do
87 table.remove(t, 1)
88 end
89 for i, c in ipairs(t) do
90 t[i] = unquote(c)
91 end
92 return dir.normalize(table.concat(t, "/"))
93end
94
95return dir
diff --git a/src/luarocks/core/manif.lua b/src/luarocks/core/manif.lua
new file mode 100644
index 00000000..11a6cf41
--- /dev/null
+++ b/src/luarocks/core/manif.lua
@@ -0,0 +1,124 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local table = _tl_compat and _tl_compat.table or table
2
3local manif = {}
4
5
6local persist = require("luarocks.core.persist")
7local cfg = require("luarocks.core.cfg")
8local dir = require("luarocks.core.dir")
9local util = require("luarocks.core.util")
10local vers = require("luarocks.core.vers")
11local path = require("luarocks.core.path")
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26local manifest_cache = {}
27
28
29
30
31
32function manif.cache_manifest(repo_url, lua_version, manifest)
33 lua_version = lua_version or cfg.lua_version
34 manifest_cache[repo_url] = manifest_cache[repo_url] or {}
35 manifest_cache[repo_url][lua_version] = manifest
36end
37
38
39
40
41
42function manif.get_cached_manifest(repo_url, lua_version)
43 lua_version = lua_version or cfg.lua_version
44 return manifest_cache[repo_url] and manifest_cache[repo_url][lua_version]
45end
46
47
48
49
50
51
52
53
54function manif.manifest_loader(file, repo_url, lua_version)
55 local manifest, err, errcode = persist.load_into_table(file)
56 if not manifest and type(err) == "string" then
57 return nil, "Failed loading manifest for " .. repo_url .. ": " .. err, errcode
58 end
59
60 manif.cache_manifest(repo_url, lua_version, manifest)
61 return manifest, err, errcode
62end
63
64
65
66
67
68
69function manif.fast_load_local_manifest(repo_url)
70
71 local cached_manifest = manif.get_cached_manifest(repo_url)
72 if cached_manifest then
73 return cached_manifest
74 end
75
76 local pathname = dir.path(repo_url, "manifest")
77 return manif.manifest_loader(pathname, repo_url, nil)
78end
79
80function manif.load_rocks_tree_manifests(deps_mode)
81 local trees = {}
82 path.map_trees(deps_mode, function(tree)
83 local manifest = manif.fast_load_local_manifest(path.rocks_dir(tree))
84 if manifest then
85 table.insert(trees, { tree = tree, manifest = manifest })
86 end
87 end)
88 return trees
89end
90
91function manif.scan_dependencies(name, version, tree_manifests, dest)
92 if dest[name] then
93 return
94 end
95 dest[name] = version
96
97 for _, tree in ipairs(tree_manifests) do
98 local manifest = tree.manifest
99
100 local pkgdeps
101 if manifest.dependencies and manifest.dependencies[name] then
102 pkgdeps = manifest.dependencies[name][version]
103 end
104 if pkgdeps then
105 for _, dep in ipairs(pkgdeps) do
106 local pkg, constraints = dep.name, dep.constraints
107
108 for _, t in ipairs(tree_manifests) do
109 local entries = t.manifest.repository[pkg]
110 if entries then
111 for ver, _ in util.sortedpairs(entries, vers.compare_versions) do
112 if (not constraints) or vers.match_constraints(vers.parse_version(ver), constraints) then
113 manif.scan_dependencies(pkg, ver, tree_manifests, dest)
114 end
115 end
116 end
117 end
118 end
119 return
120 end
121 end
122end
123
124return manif
diff --git a/src/luarocks/core/path.lua b/src/luarocks/core/path.lua
new file mode 100644
index 00000000..b88b3bfc
--- /dev/null
+++ b/src/luarocks/core/path.lua
@@ -0,0 +1,151 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2local path = {}
3
4
5local cfg = require("luarocks.core.cfg")
6local dir = require("luarocks.core.dir")
7
8
9
10local dir_sep = package.config:sub(1, 1)
11
12
13function path.rocks_dir(tree)
14 if tree == nil then
15 tree = cfg.root_dir
16 end
17 if type(tree) == "string" then
18 return dir.path(tree, cfg.rocks_subdir)
19 end
20 return tree.rocks_dir or dir.path(tree.root, cfg.rocks_subdir)
21end
22
23
24
25
26
27
28
29function path.versioned_name(file, prefix, name, version)
30 assert(not name:match(dir_sep))
31
32 local rest = file:sub(#prefix + 1):gsub("^" .. dir_sep .. "*", "")
33 local name_version = (name .. "_" .. version):gsub("%-", "_"):gsub("%.", "_")
34 return dir.path(prefix, name_version .. "-" .. rest)
35end
36
37
38
39
40
41
42
43
44function path.path_to_module(file)
45
46 local exts = {}
47 local paths = package.path .. ";" .. package.cpath
48 for entry in paths:gmatch("[^;]+") do
49 local ext = entry:match("%.([a-z]+)$")
50 if ext then
51 exts[ext] = true
52 end
53 end
54
55 local name
56 for ext, _ in pairs(exts) do
57 name = file:match("(.*)%." .. ext .. "$")
58 if name then
59 name = name:gsub("[\\/]", ".")
60 break
61 end
62 end
63
64 if not name then name = file end
65
66
67 name = name:gsub("^%.+", ""):gsub("%.+$", "")
68
69 return name
70end
71
72function path.deploy_lua_dir(tree)
73 if type(tree) == "string" then
74 return dir.path(tree, cfg.lua_modules_path)
75 else
76 return tree.lua_dir or dir.path(tree.root, cfg.lua_modules_path)
77 end
78end
79
80function path.deploy_lib_dir(tree)
81 if type(tree) == "string" then
82 return dir.path(tree, cfg.lib_modules_path)
83 else
84 return tree.lib_dir or dir.path(tree.root, cfg.lib_modules_path)
85 end
86end
87
88local is_src_extension = { [".lua"] = true, [".tl"] = true, [".tld"] = true, [".moon"] = true }
89
90
91
92
93
94
95
96
97
98function path.which_i(file_name, name, version, tree, i)
99 local deploy_dir
100 local extension = file_name:match("%.[a-z]+$")
101 if is_src_extension[extension] then
102 deploy_dir = path.deploy_lua_dir(tree)
103 file_name = dir.path(deploy_dir, file_name)
104 else
105 deploy_dir = path.deploy_lib_dir(tree)
106 file_name = dir.path(deploy_dir, file_name)
107 end
108 if i > 1 then
109 file_name = path.versioned_name(file_name, deploy_dir, name, version)
110 end
111 return file_name
112end
113
114function path.rocks_tree_to_string(tree)
115 if type(tree) == "string" then
116 return tree
117 else
118 return tree.root
119 end
120end
121
122
123
124
125
126
127
128
129function path.map_trees(deps_mode, fn, ...)
130 local result = {}
131 local current = cfg.root_dir or cfg.rocks_trees[1]
132 if deps_mode == "one" then
133 table.insert(result, (fn(current, ...)) or 0)
134 else
135 local use = false
136 if deps_mode == "all" then
137 use = true
138 end
139 for _, tree in ipairs(cfg.rocks_trees or {}) do
140 if dir.normalize(path.rocks_tree_to_string(tree)) == dir.normalize(path.rocks_tree_to_string(current)) then
141 use = true
142 end
143 if use then
144 table.insert(result, (fn(tree, ...)) or 0)
145 end
146 end
147 end
148 return result
149end
150
151return path
diff --git a/src/luarocks/core/persist.lua b/src/luarocks/core/persist.lua
new file mode 100644
index 00000000..258a42c0
--- /dev/null
+++ b/src/luarocks/core/persist.lua
@@ -0,0 +1,70 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local load = _tl_compat and _tl_compat.load or load; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string
2local persist = {}
3
4
5
6
7
8
9
10
11
12
13function persist.run_file(filename, env)
14 local fd, open_err = io.open(filename)
15 if not fd then
16 return nil, open_err, "open"
17 end
18 local str, read_err = fd:read("*a")
19 fd:close()
20 if not str then
21 return nil, read_err, "open"
22 end
23 str = str:gsub("^#![^\n]*\n", "")
24 local chunk, ran, err
25 chunk, err = load(str, filename, "t", env)
26 if chunk then
27 ran, err = pcall(chunk)
28 end
29 if not chunk then
30 return nil, "Error loading file: " .. tostring(err), "load"
31 end
32 if not ran then
33 return nil, "Error running file: " .. tostring(err), "run"
34 end
35 return true, err
36end
37
38
39
40
41
42
43
44
45
46
47
48function persist.load_into_table(filename, tbl)
49
50 local result = tbl or {}
51 local globals = {}
52 local globals_mt = {
53 __index = function(_, k)
54 globals[k] = true
55 end,
56 }
57 local save_mt = getmetatable(result)
58 setmetatable(result, globals_mt)
59
60 local ok, err, errcode = persist.run_file(filename, result)
61
62 setmetatable(result, save_mt)
63
64 if not ok then
65 return nil, tostring(err), errcode
66 end
67 return result, globals
68end
69
70return persist
diff --git a/src/luarocks/core/sysdetect.lua b/src/luarocks/core/sysdetect.lua
new file mode 100644
index 00000000..3a2527f4
--- /dev/null
+++ b/src/luarocks/core/sysdetect.lua
@@ -0,0 +1,508 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2
3
4
5
6
7local sysdetect = {}
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59local function hex(s)
60 return (s:gsub("$(..)", function(x)
61 return string.char(tonumber(x, 16))
62 end))
63end
64
65local function read_int8(fd)
66 if io.type(fd) == "closed file" then
67 return nil
68 end
69 local s = fd:read(1)
70 if not s then
71 fd:close()
72 return nil
73 end
74 return s:byte()
75end
76
77local function bytes2number(s, endian)
78 local r = 0
79 if endian == "little" then
80 for i = #s, 1, -1 do
81 r = r * 256 + s:byte(i, i)
82 end
83 else
84 for i = 1, #s do
85 r = r * 256 + s:byte(i, i)
86 end
87 end
88 return r
89end
90
91local function read(fd, bytes, endian)
92 if io.type(fd) == "closed file" then
93 return nil
94 end
95 local s = fd:read(bytes)
96 if not s then
97 fd:close()
98 return nil
99 end
100 return bytes2number(s, endian)
101end
102
103local function read_int32le(fd)
104 return read(fd, 4, "little")
105end
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148local endians = {
149 [0x01] = "little",
150 [0x02] = "big",
151}
152
153local e_osabi = {
154 [0x00] = "sysv",
155 [0x01] = "hpux",
156 [0x02] = "netbsd",
157 [0x03] = "linux",
158 [0x04] = "hurd",
159 [0x06] = "solaris",
160 [0x07] = "aix",
161 [0x08] = "irix",
162 [0x09] = "freebsd",
163 [0x0c] = "openbsd",
164}
165
166local e_machines = {
167 [0x02] = "sparc",
168 [0x03] = "x86",
169 [0x08] = "mips",
170 [0x0f] = "hppa",
171 [0x12] = "sparcv8",
172 [0x14] = "ppc",
173 [0x15] = "ppc64",
174 [0x16] = "s390",
175 [0x28] = "arm",
176 [0x2a] = "superh",
177 [0x2b] = "sparcv9",
178 [0x32] = "ia_64",
179 [0x3E] = "x86_64",
180 [0xB6] = "alpha",
181 [0xB7] = "aarch64",
182 [0xF3] = "riscv64",
183 [0x9026] = "alpha",
184}
185
186local SHT_NOTE = 7
187
188local function read_elf_section_headers(fd, hdr)
189 local endian = endians[hdr.endian]
190 local word = hdr.word
191
192 local strtab_offset
193 local sections = {}
194 local secarray = {}
195 for i = 0, hdr.e_shnum - 1 do
196 fd:seek("set", hdr.e_shoff + (i * hdr.e_shentsize))
197 local section = {}
198 section.sh_name_off = read(fd, 4, endian)
199 section.sh_type = read(fd, 4, endian)
200 section.sh_flags = read(fd, word, endian)
201 section.sh_addr = read(fd, word, endian)
202 section.sh_offset = read(fd, word, endian)
203 section.sh_size = read(fd, word, endian)
204 section.sh_link = read(fd, 4, endian)
205 section.sh_info = read(fd, 4, endian)
206 if section.sh_type == SHT_NOTE then
207 fd:seek("set", section.sh_offset)
208 section.namesz = read(fd, 4, endian)
209 section.descsz = read(fd, 4, endian)
210 section.type = read(fd, 4, endian)
211 section.namedata = fd:read(section.namesz):gsub("%z.*", "")
212 section.descdata = fd:read(section.descsz)
213 elseif i == hdr.e_shstrndx then
214 strtab_offset = section.sh_offset
215 end
216 table.insert(secarray, section)
217 end
218 if strtab_offset then
219 for _, section in ipairs(secarray) do
220 fd:seek("set", strtab_offset + section.sh_name_off)
221 section.name = fd:read(32):gsub("%z.*", "")
222 sections[section.name] = section
223 end
224 end
225 return sections
226end
227
228local function detect_elf_system(fd, hdr, sections)
229 local system = e_osabi[hdr.osabi]
230 local endian = endians[hdr.endian]
231
232 if system == "sysv" then
233 local abitag = sections[".note.ABI-tag"]
234 if abitag then
235 if abitag.namedata == "GNU" and abitag.type == 1 and
236 abitag.descdata:sub(0, 4) == "\0\0\0\0" then
237 return "linux"
238 end
239 elseif sections[".SUNW_version"] or
240 sections[".SUNW_signature"] then
241 return "solaris"
242 elseif sections[".note.netbsd.ident"] then
243 return "netbsd"
244 elseif sections[".note.openbsd.ident"] then
245 return "openbsd"
246 elseif sections[".note.tag"] and
247 sections[".note.tag"].namedata == "DragonFly" then
248 return "dragonfly"
249 end
250
251 local gnu_version_r = sections[".gnu.version_r"]
252 if gnu_version_r then
253
254 local dynstr = sections[".dynstr"].sh_offset
255
256 local idx = 0
257 for _ = 0, gnu_version_r.sh_info - 1 do
258 fd:seek("set", gnu_version_r.sh_offset + idx)
259 assert(read(fd, 2, endian))
260 local vn_cnt = read(fd, 2, endian)
261 local vn_file = read(fd, 4, endian)
262 local vn_next = read(fd, 2, endian)
263
264 fd:seek("set", dynstr + vn_file)
265 local libname = fd:read(64):gsub("%z.*", "")
266
267 if hdr.e_type == 0x03 and libname == "libroot.so" then
268 return "haiku"
269 elseif libname:match("linux") then
270 return "linux"
271 end
272
273 idx = idx + (vn_next * (vn_cnt + 1))
274 end
275 end
276
277 local procfile = io.open("/proc/sys/kernel/ostype")
278 if procfile then
279 local version = procfile:read(6)
280 procfile:close()
281 if version == "Linux\n" then
282 return "linux"
283 end
284 end
285 end
286
287 return system
288end
289
290local function read_elf_header(fd)
291 local hdr = {}
292
293 hdr.bits = read_int8(fd)
294 hdr.endian = read_int8(fd)
295 hdr.elf_version = read_int8(fd)
296 if hdr.elf_version ~= 1 then
297 return nil
298 end
299 hdr.osabi = read_int8(fd)
300 if not hdr.osabi then
301 return nil
302 end
303
304 local endian = endians[hdr.endian]
305 fd:seek("set", 0x10)
306 hdr.e_type = read(fd, 2, endian)
307 local machine = read(fd, 2, endian)
308 local processor = e_machines[machine] or "unknown"
309 if endian == "little" and processor == "ppc64" then
310 processor = "ppc64le"
311 end
312
313 local elfversion = read(fd, 4, endian)
314 if elfversion ~= 1 then
315 return nil
316 end
317
318 local word = (hdr.bits == 1) and 4 or 8
319 hdr.word = word
320
321 hdr.e_entry = read(fd, word, endian)
322 hdr.e_phoff = read(fd, word, endian)
323 hdr.e_shoff = read(fd, word, endian)
324 hdr.e_flags = read(fd, 4, endian)
325 hdr.e_ehsize = read(fd, 2, endian)
326 hdr.e_phentsize = read(fd, 2, endian)
327 hdr.e_phnum = read(fd, 2, endian)
328 hdr.e_shentsize = read(fd, 2, endian)
329 hdr.e_shnum = read(fd, 2, endian)
330 hdr.e_shstrndx = read(fd, 2, endian)
331
332 return hdr, processor
333end
334
335local function detect_elf(fd)
336 local hdr, processor = read_elf_header(fd)
337 if not hdr then
338 return nil
339 end
340 local sections = read_elf_section_headers(fd, hdr)
341 local system = detect_elf_system(fd, hdr, sections)
342 return system, processor
343end
344
345
346
347
348
349local mach_l64 = {
350 [7] = "x86_64",
351 [12] = "aarch64",
352}
353
354local mach_b64 = {
355 [0] = "ppc64",
356}
357
358local mach_l32 = {
359 [7] = "x86",
360 [12] = "arm",
361}
362
363local mach_b32 = {
364 [0] = "ppc",
365}
366
367local function detect_mach(magic, fd)
368 if not magic then
369 return nil
370 end
371
372 if magic == hex("$CA$FE$BA$BE") then
373
374 fd:seek("set", 0x12)
375 local offs = read_int8(fd)
376 if not offs then
377 return nil
378 end
379 fd:seek("set", offs * 256)
380 magic = fd:read(4)
381 return detect_mach(magic, fd)
382 end
383
384 local cputype = read_int8(fd)
385
386 if magic == hex("$CF$FA$ED$FE") then
387 return "macosx", mach_l64[cputype] or "unknown"
388 elseif magic == hex("$FE$ED$CF$FA") then
389 return "macosx", mach_b64[cputype] or "unknown"
390 elseif magic == hex("$CE$FA$ED$FE") then
391 return "macosx", mach_l32[cputype] or "unknown"
392 elseif magic == hex("$FE$ED$FA$CE") then
393 return "macosx", mach_b32[cputype] or "unknown"
394 end
395end
396
397
398
399
400
401local pe_machine = {
402 [0x8664] = "x86_64",
403 [0x01c0] = "arm",
404 [0x01c4] = "armv7l",
405 [0xaa64] = "arm64",
406 [0x014c] = "x86",
407}
408
409local function detect_pe(fd)
410 fd:seek("set", 60)
411 local peoffset = read_int32le(fd)
412 if not peoffset then
413 return nil
414 end
415 local system = "windows"
416 fd:seek("set", peoffset + 4)
417 local machine = read(fd, 2, "little")
418 local processor = pe_machine[machine]
419
420 local rdata_pos_s = fd:read(736):match(".rdata%z%z............(....)")
421 if rdata_pos_s then
422 local rdata_pos = bytes2number(rdata_pos_s, "little")
423 fd:seek("set", rdata_pos)
424 local data = fd:read(512)
425 if data:match("cygwin") or data:match("cyggcc") then
426 system = "cygwin"
427 end
428 end
429
430 return system, processor or "unknown"
431end
432
433
434
435
436
437function sysdetect.detect_file(file)
438 local fd = io.open(file, "rb")
439 if not fd then
440 return nil
441 end
442 local magic = fd:read(4)
443 if magic == hex("$7FELF") then
444 return detect_elf(fd)
445 end
446 if magic == hex("MZ$90$00") then
447 return detect_pe(fd)
448 end
449 return detect_mach(magic, fd)
450end
451
452local cache_system
453local cache_processor
454
455function sysdetect.detect(input_file)
456 local dirsep = package.config:sub(1, 1)
457 local files
458
459 if input_file then
460 files = { input_file }
461 else
462 if cache_system then
463 return cache_system, cache_processor
464 end
465
466 local PATHsep
467 local interp = arg and arg[-1]
468 if dirsep == "/" then
469
470 files = {
471 "/bin/sh",
472 "/proc/self/exe",
473 }
474 PATHsep = ":"
475 else
476
477 local systemroot = os.getenv("SystemRoot")
478 files = {
479 systemroot .. "\\system32\\notepad.exe",
480 systemroot .. "\\explorer.exe",
481 }
482 if interp and not interp:lower():match("exe$") then
483 interp = interp .. ".exe"
484 end
485 PATHsep = ";"
486 end
487 if interp then
488 if interp:match(dirsep) then
489
490 table.insert(files, 1, interp)
491 else
492 for d in (os.getenv("PATH") or ""):gmatch("[^" .. PATHsep .. "]+") do
493 table.insert(files, d .. dirsep .. interp)
494 end
495 end
496 end
497 end
498 for _, f in ipairs(files) do
499 local system, processor = sysdetect.detect_file(f)
500 if system then
501 cache_system = system
502 cache_processor = processor
503 return system, processor
504 end
505 end
506end
507
508return sysdetect
diff --git a/src/luarocks/core/types/query.lua b/src/luarocks/core/types/query.lua
new file mode 100644
index 00000000..e8f318fe
--- /dev/null
+++ b/src/luarocks/core/types/query.lua
@@ -0,0 +1,13 @@
1
2
3local query = {Query = {}, }
4
5
6
7
8
9
10
11
12
13return query
diff --git a/src/luarocks/core/types/result.lua b/src/luarocks/core/types/result.lua
new file mode 100644
index 00000000..974bd23a
--- /dev/null
+++ b/src/luarocks/core/types/result.lua
@@ -0,0 +1,14 @@
1
2
3local result = {Result = {}, }
4
5
6
7
8
9
10
11
12
13
14return result
diff --git a/src/luarocks/core/types/rockspec.lua b/src/luarocks/core/types/rockspec.lua
new file mode 100644
index 00000000..5d90fbf1
--- /dev/null
+++ b/src/luarocks/core/types/rockspec.lua
@@ -0,0 +1,78 @@
1
2
3
4
5local rockspec = {Description = {}, Source = {}, Test = {}, Dependencies = {}, Hooks = {}, Deploy = {}, Rockspec = {}, }
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78return rockspec
diff --git a/src/luarocks/core/util.lua b/src/luarocks/core/util.lua
new file mode 100644
index 00000000..6a457c54
--- /dev/null
+++ b/src/luarocks/core/util.lua
@@ -0,0 +1,334 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local debug = _tl_compat and _tl_compat.debug or debug; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local math = _tl_compat and _tl_compat.math or math; local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2local util = {}
3
4
5
6
7
8
9
10local dir_sep = package.config:sub(1, 1)
11
12
13
14
15
16
17
18
19function util.popen_read(cmd, spec)
20 local tmpfile = (dir_sep == "\\") and
21 (os.getenv("TMP") .. "/luarocks-" .. tostring(math.floor(math.random() * 10000))) or
22 os.tmpname()
23 os.execute(cmd .. " > " .. tmpfile)
24 local fd = io.open(tmpfile, "rb")
25 if not fd then
26 os.remove(tmpfile)
27 return ""
28 end
29 local out = fd:read(spec or "*l")
30 fd:close()
31 os.remove(tmpfile)
32 return out or ""
33end
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52function util.show_table(t, tname, top_indent)
53 local cart
54 local autoref
55
56 local function is_empty_table(tbl) return next(tbl) == nil end
57
58 local function basic_serialize(o)
59 local so = tostring(o)
60 if type(o) == "function" then
61 local info = debug and debug.getinfo(o, "S")
62 if not info then
63 return ("%q"):format(so)
64 end
65
66 if info.what == "C" then
67 return ("%q"):format(so .. ", C function")
68 else
69
70 return ("%q"):format(so .. ", defined in (" .. info.linedefined .. "-" .. info.lastlinedefined .. ")" .. info.source)
71 end
72 elseif type(o) == "number" then
73 return so
74 else
75 return ("%q"):format(so)
76 end
77 end
78
79 local function add_to_cart(value, name, indent, saved, field)
80 indent = indent or ""
81 saved = saved or {}
82 field = field or name
83
84 cart = cart .. indent .. field
85
86 if not (type(value) == "table") then
87 cart = cart .. " = " .. basic_serialize(value) .. ";\n"
88 else
89 if saved[value] then
90 cart = cart .. " = {}; -- " .. saved[value] .. " (self reference)\n"
91 autoref = autoref .. name .. " = " .. saved[value] .. ";\n"
92 else
93 saved[value] = name
94 if is_empty_table(value) then
95 cart = cart .. " = {};\n"
96 else
97 cart = cart .. " = {\n"
98 for k, v in pairs(value) do
99 k = basic_serialize(k)
100 local fname = ("%s[%s]"):format(name, k)
101 field = ("[%s]"):format(k)
102
103 add_to_cart(v, fname, indent .. " ", saved, field)
104 end
105 cart = cart .. indent .. "};\n"
106 end
107 end
108 end
109 end
110
111 tname = tname or "__unnamed__"
112 if not (type(t) == "table") then
113 return tname .. " = " .. basic_serialize(t)
114 end
115 cart, autoref = "", ""
116 add_to_cart(t, tname, top_indent)
117 return cart .. autoref
118end
119
120
121
122
123
124
125function util.matchquote(s)
126 return (s:gsub("[?%-+*%[%].%%()$^]", "%%%1"))
127end
128
129
130
131
132
133function util.deep_merge(dst, src)
134 for k, v in pairs(src) do
135 if type(v) == "table" then
136 local dstk = dst[k]
137 if dstk == nil then
138 dst[k] = {}
139 dstk = dst[k]
140 end
141 if type(dstk) == "table" then
142 util.deep_merge(dstk, v)
143 else
144 dst[k] = v
145 end
146 else
147 dst[k] = v
148 end
149 end
150end
151
152
153
154
155
156function util.deep_merge_under(dst, src)
157 for k, v in pairs(src) do
158 if type(v) == "table" then
159 local dstk = dst[k]
160 if dstk == nil then
161 dst[k] = {}
162 dstk = dst[k]
163 end
164 if type(dstk) == "table" then
165 util.deep_merge_under(dstk, v)
166 end
167 elseif dst[k] == nil then
168 dst[k] = v
169 end
170 end
171end
172
173
174
175function util.split_string(str, delim, maxNb)
176
177 if string.find(str, delim) == nil then
178 return { str }
179 end
180 if maxNb == nil or maxNb < 1 then
181 maxNb = 0
182 end
183 local result = {}
184 local pat = "(.-)" .. delim .. "()"
185 local nb = 0
186 local lastPos
187 for part, pos in string.gmatch(str, pat) do
188 nb = nb + 1
189 result[nb] = part
190 lastPos = tonumber(pos)
191 if nb == maxNb then break end
192 end
193
194 if nb ~= maxNb then
195 result[nb + 1] = string.sub(str, lastPos)
196 end
197 return result
198end
199
200
201
202
203
204
205
206
207
208
209
210function util.cleanup_path(list, sep, lua_version, keep_first)
211
212 list = list:gsub(dir_sep, "/")
213
214 local parts = util.split_string(list, sep)
215 local final, entries = {}, {}
216 local start, stop, step
217
218 if keep_first then
219 start, stop, step = 1, #parts, 1
220 else
221 start, stop, step = #parts, 1, -1
222 end
223
224 for i = start, stop, step do
225 local part = parts[i]:gsub("//", "/")
226 if lua_version then
227 part = part:gsub("/lua/([%d.]+)/", function(part_version)
228 if part_version:sub(1, #lua_version) ~= lua_version then
229 return "/lua/" .. lua_version .. "/"
230 end
231 end)
232 end
233 if not entries[part] then
234 local at = keep_first and #final + 1 or 1
235 table.insert(final, at, part)
236 entries[part] = true
237 end
238 end
239
240 return (table.concat(final, sep):gsub("/", dir_sep))
241end
242
243
244
245
246function util.keys(tbl)
247 local ks = {}
248 for k, _ in pairs(tbl) do
249 table.insert(ks, k)
250 end
251 return ks
252end
253
254
255function util.printerr(...)
256 io.stderr:write(table.concat({ ... }, "\t"))
257 io.stderr:write("\n")
258end
259
260
261
262function util.warning(msg)
263 util.printerr("Warning: " .. msg)
264end
265
266
267local function default_sort(a, b)
268 local ta = type(a)
269 local tb = type(b)
270 if ta == "number" and tb == "number" then
271 return tonumber(a) < tonumber(b)
272 elseif ta == "number" then
273 return true
274 elseif tb == "number" then
275 return false
276 else
277 return tostring(a) < tostring(b)
278 end
279end
280
281
282
283
284
285
286
287
288
289
290
291function util.sortedpairs(tbl, sort_by)
292 local keys = util.keys(tbl)
293 local sub_orders = nil
294
295 if sort_by == nil then
296 table.sort(keys, default_sort)
297 elseif type(sort_by) == "function" then
298 table.sort(keys, sort_by)
299 else
300
301
302 sub_orders = sort_by.sub_orders
303
304 local seen_ordered_key = {}
305
306 local my_ordered_keys = {}
307
308 for _, key in ipairs(sort_by) do
309 if tbl[key] then
310 seen_ordered_key[key] = true
311 table.insert(my_ordered_keys, key)
312 end
313 end
314
315 table.sort(keys, default_sort)
316
317 for _, key in ipairs(keys) do
318 if not seen_ordered_key[key] then
319 table.insert(my_ordered_keys, key)
320 end
321 end
322
323 keys = my_ordered_keys
324 end
325
326 local i = 1
327 return function()
328 local key = keys[i]
329 i = i + 1
330 return key, tbl[key], sub_orders and sub_orders[key]
331 end
332end
333
334return util
diff --git a/src/luarocks/core/vers.lua b/src/luarocks/core/vers.lua
new file mode 100644
index 00000000..4ffb8c0d
--- /dev/null
+++ b/src/luarocks/core/vers.lua
@@ -0,0 +1,211 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local math = _tl_compat and _tl_compat.math or math; local string = _tl_compat and _tl_compat.string or string; local vers = {}
2
3
4local util = require("luarocks.core.util")
5
6
7
8
9local deltas = {
10 dev = 120000000,
11 scm = 110000000,
12 cvs = 100000000,
13 rc = -1000,
14 pre = -10000,
15 beta = -100000,
16 alpha = -1000000,
17}
18
19local version_mt = {
20
21
22
23
24
25
26
27 __eq = function(v1, v2)
28 if #v1 ~= #v2 then
29 return false
30 end
31 for i = 1, #v1 do
32 if v1[i] ~= v2[i] then
33 return false
34 end
35 end
36 if v1.revision and v2.revision then
37 return (v1.revision == v2.revision)
38 end
39 return true
40 end,
41
42
43
44
45
46
47
48 __lt = function(v1, v2)
49 for i = 1, math.max(#v1, #v2) do
50 local v1i, v2i = v1[i] or 0, v2[i] or 0
51 if v1i ~= v2i then
52 return (v1i < v2i)
53 end
54 end
55 if v1.revision and v2.revision then
56 return (v1.revision < v2.revision)
57 end
58 return false
59 end,
60
61
62
63 __le = function(v1, v2)
64 return not (v2 < v1)
65 end,
66
67
68
69 __tostring = function(v)
70 return v.string
71 end,
72}
73
74local version_cache = {}
75setmetatable(version_cache, {
76 __mode = "kv",
77})
78
79
80
81
82
83
84
85
86
87
88
89
90function vers.parse_version(vstring)
91 if not vstring then return nil end
92
93 local cached = version_cache[vstring]
94 if cached then
95 return cached
96 end
97
98 local version = {}
99 local i = 1
100
101 local function add_token(number)
102 version[i] = version[i] and version[i] + number / 100000 or number
103 i = i + 1
104 end
105
106
107 local v = vstring:match("^%s*(.*)%s*$")
108 version.string = v
109
110 local main, revision = v:match("(.*)%-(%d+)$")
111 if revision then
112 v = main
113 version.revision = tonumber(revision)
114 end
115 while #v > 0 do
116
117 local token, rest = v:match("^(%d+)[%.%-%_]*(.*)")
118 if token then
119 add_token(tonumber(token))
120 else
121
122 token, rest = v:match("^(%a+)[%.%-%_]*(.*)")
123 if not token then
124 util.warning("version number '" .. v .. "' could not be parsed.")
125 version[i] = 0
126 break
127 end
128 version[i] = deltas[token] or (token:byte() / 1000)
129 end
130 v = rest
131 end
132 setmetatable(version, version_mt)
133 version_cache[vstring] = version
134 return version
135end
136
137
138
139
140
141function vers.compare_versions(a, b)
142 if a == b then
143 return false
144 end
145 return vers.parse_version(b) < vers.parse_version(a)
146end
147
148
149
150
151
152
153
154
155
156
157
158
159
160local function partial_match(input_version, input_requested)
161
162 local version, requested
163
164 if not (type(input_version) == "table") then version = vers.parse_version(input_version)
165 else version = input_version end
166 if not (type(input_requested) == "table") then requested = vers.parse_version(input_requested)
167 else requested = input_requested end
168 if not (type(version) == "table") or not (type(requested) == "table") then return false end
169
170 for i, ri in ipairs(requested) do
171 local vi = version[i] or 0
172 if ri ~= vi then return false end
173 end
174 if requested.revision then
175 return requested.revision == version.revision
176 end
177 return true
178end
179
180
181
182
183
184
185function vers.match_constraints(version, constraints)
186 local ok = true
187 setmetatable(version, version_mt)
188 for _, constr in ipairs(constraints) do
189 local constr_version, constr_op = constr.version, constr.op
190 local cv
191 if type(constr_version) == "string" then
192 cv = vers.parse_version(constr_version)
193 constr.version = cv
194 else
195 cv = constr_version
196 end
197 setmetatable(cv, version_mt)
198 if constr_op == "==" then ok = version == cv
199 elseif constr_op == "~=" then ok = version ~= cv
200 elseif constr_op == ">" then ok = cv < version
201 elseif constr_op == "<" then ok = version < cv
202 elseif constr_op == ">=" then ok = cv <= version
203 elseif constr_op == "<=" then ok = version <= cv
204 elseif constr_op == "~>" then ok = partial_match(version, cv)
205 end
206 if not ok then break end
207 end
208 return ok
209end
210
211return vers
diff --git a/src/luarocks/deplocks.lua b/src/luarocks/deplocks.lua
new file mode 100644
index 00000000..8a21ef1b
--- /dev/null
+++ b/src/luarocks/deplocks.lua
@@ -0,0 +1,111 @@
1local deplocks = {}
2
3local fs = require("luarocks.fs")
4local dir = require("luarocks.dir")
5local util = require("luarocks.util")
6local persist = require("luarocks.persist")
7
8
9
10local deptable = {}
11local deptable_mode = "start"
12local deplock_abs_filename
13local deplock_root_rock_name
14
15function deplocks.init(root_rock_name, dirname)
16 if deptable_mode ~= "start" then
17 return
18 end
19 deptable_mode = "create"
20
21 local filename = dir.path(dirname, "luarocks.lock")
22 deplock_abs_filename = fs.absolute_name(filename)
23 deplock_root_rock_name = root_rock_name
24
25 deptable = {}
26end
27
28function deplocks.get_abs_filename(root_rock_name)
29 if root_rock_name == deplock_root_rock_name then
30 return deplock_abs_filename
31 end
32end
33
34function deplocks.load(root_rock_name, dirname)
35 if deptable_mode ~= "start" then
36 return true, nil
37 end
38 deptable_mode = "locked"
39
40 local filename = dir.path(dirname, "luarocks.lock")
41 local _, result, errcode = persist.run_file(filename, {})
42 if errcode == "load" or errcode == "run" then
43
44 return nil, nil, "Could not read existing lockfile " .. filename
45 end
46
47 if errcode == "open" then
48
49 return true, nil
50 end
51
52 deplock_abs_filename = fs.absolute_name(filename)
53 deplock_root_rock_name = root_rock_name
54
55 deptable = result
56 return true, filename
57end
58
59function deplocks.add(depskey, name, version)
60 if deptable_mode == "locked" then
61 return
62 end
63
64 local dk = deptable[depskey]
65 if not dk then
66 dk = {}
67 deptable[depskey] = dk
68 end
69
70 if type(dk) == "table" and not dk[name] then
71 dk[name] = version
72 end
73end
74
75function deplocks.get(depskey, name)
76 local dk = deptable[depskey]
77 if not dk then
78 return nil
79 end
80 if type(dk) == "table" then
81 return dk[name]
82 else
83 return dk
84 end
85end
86
87function deplocks.write_file()
88 if deptable_mode ~= "create" then
89 return true
90 end
91
92 return persist.save_as_module(deplock_abs_filename, deptable)
93end
94
95
96function deplocks.proxy(depskey)
97 return setmetatable({}, {
98 __index = function(_, k)
99 return deplocks.get(depskey, k)
100 end,
101 __newindex = function(_, k, v)
102 return deplocks.add(depskey, k, v)
103 end,
104 })
105end
106
107function deplocks.each(depskey)
108 return util.sortedpairs(deptable[depskey] or {})
109end
110
111return deplocks
diff --git a/src/luarocks/deps.lua b/src/luarocks/deps.lua
new file mode 100644
index 00000000..c960f3b7
--- /dev/null
+++ b/src/luarocks/deps.lua
@@ -0,0 +1,877 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2
3local deps = {}
4
5
6
7local cfg = require("luarocks.core.cfg")
8local manif = require("luarocks.manif")
9local path = require("luarocks.path")
10local dir = require("luarocks.dir")
11local fun = require("luarocks.fun")
12local util = require("luarocks.util")
13local vers = require("luarocks.core.vers")
14local queries = require("luarocks.queries")
15local deplocks = require("luarocks.deplocks")
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57local function prepare_get_versions(deps_mode, rocks_provided, depskey, skip_set)
58
59 return function(dep)
60 local versions, locations
61 local provided = rocks_provided[dep.name]
62 if provided then
63
64 versions, locations = { provided }, {}
65 else
66 if deps_mode == "none" then
67 deps_mode = "one"
68 end
69 versions, locations = manif.get_versions(dep, deps_mode)
70 end
71
72 if skip_set and skip_set[dep.name] then
73 for i = #versions, 1, -1 do
74 local v = versions[i]
75 if skip_set[dep.name][v] then
76 table.remove(versions, i)
77 end
78 end
79 end
80
81 local lockversion = deplocks.get(depskey, dep.name)
82
83 return versions, locations, lockversion, provided ~= nil
84 end
85end
86
87
88
89
90
91
92
93
94
95
96
97
98
99local function match_dep(depq,
100 get_versions)
101
102 local versions, locations, lockversion, provided = get_versions(depq)
103
104 local latest_version
105 local latest_vstring
106 for _, vstring in ipairs(versions) do
107 local version = vers.parse_version(vstring)
108 if vers.match_constraints(version, depq.constraints) then
109 if not latest_version or version > latest_version then
110 latest_version = version
111 latest_vstring = vstring
112 end
113 end
114 end
115
116 if lockversion and not locations[lockversion] then
117 local latest_matching_msg = ""
118 if latest_vstring and latest_vstring ~= lockversion then
119 latest_matching_msg = " (latest matching is " .. latest_vstring .. ")"
120 end
121 util.printout("Forcing " .. depq.name .. " to pinned version " .. lockversion .. latest_matching_msg)
122 return nil, nil, queries.new(depq.name, depq.namespace, lockversion)
123 end
124
125 return latest_vstring, locations[latest_vstring], depq, provided
126end
127
128local function match_all_deps(dependencies,
129 get_versions)
130
131 local matched, missing, no_upgrade = {}, {}, {}
132
133 for _, depq in ipairs(dependencies) do
134 local found, _, provided
135 found, _, depq, provided = match_dep(depq, get_versions)
136 if found then
137 if not provided then
138 matched[depq] = { name = depq.name, version = found }
139 end
140 else
141 if depq.constraints and depq.constraints[1] and depq.constraints[1].no_upgrade then
142 no_upgrade[depq.name] = depq
143 else
144 missing[depq.name] = depq
145 end
146 end
147 end
148 return matched, missing, no_upgrade
149end
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164function deps.match_deps(dependencies, rocks_provided, deps_mode, skip_set)
165
166 local get_versions = prepare_get_versions(deps_mode, rocks_provided, "dependencies", skip_set)
167 return match_all_deps(dependencies, get_versions)
168end
169
170local function rock_status(dep, get_versions)
171 local installed, _, _, provided = match_dep(dep, get_versions)
172 local installation_type = provided and "provided by VM" or "installed"
173 return installed and installed .. " " .. installation_type .. ": success" or "not installed"
174end
175
176
177
178
179
180
181
182
183
184
185function deps.report_missing_dependencies(name, version, dependencies, deps_mode, rocks_provided)
186
187 if deps_mode == "none" then
188 return
189 end
190
191 local get_versions = prepare_get_versions(deps_mode, rocks_provided, "dependencies")
192
193 local first_missing_dep = true
194
195 for _, depq in ipairs(dependencies) do
196 local found, _
197 found, _, depq = match_dep(depq, get_versions)
198 if not found then
199 if first_missing_dep then
200 util.printout(("Missing dependencies for %s %s:"):format(name, version))
201 first_missing_dep = false
202 end
203
204 util.printout((" %s (%s)"):format(tostring(depq), rock_status(depq, get_versions)))
205 end
206 end
207end
208
209function deps.fulfill_dependency(dep, deps_mode, rocks_provided, verify, depskey)
210
211 deps_mode = deps_mode or "all"
212 rocks_provided = rocks_provided or {}
213
214 local get_versions = prepare_get_versions(deps_mode, rocks_provided, depskey)
215
216 local found, where
217 found, where, dep = match_dep(dep, get_versions)
218 if found then
219 local tree_manifests = manif.load_rocks_tree_manifests(deps_mode)
220 manif.scan_dependencies(dep.name, found, tree_manifests, deplocks.proxy(depskey))
221 return true, found, where
222 end
223
224 local search = require("luarocks.search")
225
226 local url, search_err = search.find_suitable_rock(dep)
227 if not url then
228 return nil, "Could not satisfy dependency " .. tostring(dep) .. ": " .. search_err
229 end
230 util.printout("Installing " .. url)
231 local install_args = {
232 rock = url,
233 deps_mode = deps_mode,
234 namespace = dep.namespace,
235 verify = verify,
236 }
237 local ok, install_err, errcode = deps.installer(install_args)
238 if not ok then
239 return nil, "Failed installing dependency: " .. url .. " - " .. install_err, errcode
240 end
241
242 found, where = match_dep(dep, get_versions)
243 if not found then
244 return nil, "Repository inconsistency detected (previously unfinished/corrupted installation?)"
245 end
246 return true, found, where
247end
248
249local function check_supported_platforms(rockspec)
250 if rockspec.supported_platforms and next(rockspec.supported_platforms) then
251 local all_negative = true
252 local supported = false
253 for _, plat in ipairs(rockspec.supported_platforms) do
254 local neg
255 neg, plat = plat:match("^(!?)(.*)")
256 if neg == "!" then
257 if cfg.is_platform(plat) then
258 return nil, "This rockspec for " .. rockspec.package .. " does not support " .. plat .. " platforms."
259 end
260 else
261 all_negative = false
262 if cfg.is_platform(plat) then
263 supported = true
264 break
265 end
266 end
267 end
268 if supported == false and not all_negative then
269 local plats = cfg.print_platforms()
270 return nil, "This rockspec for " .. rockspec.package .. " does not support " .. plats .. " platforms."
271 end
272 end
273
274 return true
275end
276
277
278
279
280
281
282
283
284
285
286
287
288
289function deps.fulfill_dependencies(rockspec, depskey, deps_mode, verify, deplock_dir)
290 local name = rockspec.name
291 local version = rockspec.version
292 local rocks_provided = rockspec.rocks_provided
293
294 local ok, filename, err = deplocks.load(name, deplock_dir or ".")
295 if filename then
296 util.printout("Using dependencies pinned in lockfile: " .. filename)
297
298 local get_versions = prepare_get_versions("none", rocks_provided, depskey)
299 local dnsnamestr, dversionstr
300 for dnsname, dversion in deplocks.each(depskey) do
301 if type(dnsname) == "string" then
302 dnsnamestr = dnsname
303 end
304 if type(dversion) == "string" then
305 dversionstr = dversion
306 end
307 local dname, dnamespace = util.split_namespace(dnsnamestr)
308 local depq = queries.new(dname, dnamespace, dversionstr)
309
310 util.printout(("%s %s is pinned to %s (%s)"):format(
311 name, version, tostring(depq), rock_status(depq, get_versions)))
312
313 local okfullfill, errfullfill = deps.fulfill_dependency(depq, "none", rocks_provided, verify, depskey)
314 if not okfullfill then
315 return nil, errfullfill
316 end
317 end
318 util.printout()
319 return true
320 elseif err then
321 util.warning(err)
322 end
323
324 ok, err = check_supported_platforms(rockspec)
325 if not ok then
326 return nil, err
327 end
328
329 deps.report_missing_dependencies(name, version, (rockspec)[depskey].queries, deps_mode, rocks_provided)
330
331 util.printout()
332
333 local get_versions = prepare_get_versions(deps_mode, rocks_provided, depskey)
334 for _, depq in ipairs((rockspec)[depskey].queries) do
335
336 util.printout(("%s %s depends on %s (%s)"):format(
337 name, version, tostring(depq), rock_status(depq, get_versions)))
338
339 local okfulfill, found_or_err, _ = deps.fulfill_dependency(depq, deps_mode, rocks_provided, verify, depskey)
340 if okfulfill then
341 deplocks.add(depskey, depq.name, found_or_err)
342 else
343
344
345
346
347
348
349
350
351 return nil, found_or_err
352 end
353 end
354
355 return true
356end
357
358
359
360
361
362
363
364
365local function deconstruct_pattern(file, pattern)
366 local depattern = "^" .. (pattern:gsub("%.", "%%."):gsub("%*", ".*"):gsub("?", "(.*)")) .. "$"
367 return (file:match(depattern))
368end
369
370
371
372
373
374
375
376
377local function add_all_patterns(file, patterns, files)
378 for _, pattern in ipairs(patterns) do
379 table.insert(files, { #files + 1, (pattern:gsub("?", file)) })
380 end
381end
382
383local function get_external_deps_dirs(mode)
384 local patterns = cfg.external_deps_patterns
385 local subdirs = cfg.external_deps_subdirs
386 if mode == "install" then
387 patterns = cfg.runtime_external_deps_patterns
388 subdirs = cfg.runtime_external_deps_subdirs
389 end
390 local dirs = {
391 BINDIR = { subdir = subdirs.bin, testfile = "program", pattern = patterns.bin },
392 INCDIR = { subdir = subdirs.include, testfile = "header", pattern = patterns.include },
393 LIBDIR = { subdir = subdirs.lib, testfile = "library", pattern = patterns.lib },
394 }
395 if mode == "install" then
396 dirs.INCDIR = nil
397 end
398 return dirs
399end
400
401local function resolve_prefix(prefix, dirs)
402 if type(prefix) == "string" then
403 return prefix
404 elseif type(prefix) == "table" then
405 if prefix.bin then
406 dirs.BINDIR.subdir = prefix.bin
407 end
408 if prefix.include then
409 if dirs.INCDIR then
410 dirs.INCDIR.subdir = prefix.include
411 end
412 end
413 if prefix.lib then
414 dirs.LIBDIR.subdir = prefix.lib
415 end
416 return prefix.prefix
417 end
418end
419
420local function add_patterns_for_file(files, file, patterns)
421
422 if not (file:match("%.[a-z]+$") or file:match("%.[a-z]+%.")) then
423 add_all_patterns(file, patterns, files)
424 else
425 for _, pattern in ipairs(patterns) do
426 local matched = deconstruct_pattern(file, pattern)
427 if matched then
428 add_all_patterns(matched, patterns, files)
429 end
430 end
431 table.insert(files, { #files + 1, file })
432 end
433end
434
435local function check_external_dependency_at(
436 prefix,
437 name,
438 ext_files,
439 vars,
440 dirs,
441 err_files,
442 cache)
443
444 local fs = require("luarocks.fs")
445 cache = cache or {}
446
447 for dirname, dirdata in util.sortedpairs(dirs) do
448 local paths
449 local path_var_value = vars[name .. "_" .. dirname]
450 local dirdatastr = dirdata.subdir
451 if path_var_value then
452 paths = { path_var_value }
453 elseif type(dirdatastr) == "table" then
454 paths = {}
455 for i, v in ipairs(dirdatastr) do
456 paths[i] = dir.path(prefix, v)
457 end
458 else
459 paths = { dir.path(prefix, dirdatastr) }
460 end
461 local file_or_files = ext_files[dirdata.testfile]
462 if file_or_files then
463 local files = {}
464 if type(file_or_files) == "string" then
465 add_patterns_for_file(files, file_or_files, dirdata.pattern)
466 elseif type(file_or_files) == "table" then
467 for _, f in ipairs(file_or_files) do
468 add_patterns_for_file(files, f, dirdata.pattern)
469 end
470 end
471
472 local found = false
473 table.sort(files, function(a, b)
474 if (not a[2]:match("%*")) and b[2]:match("%*") then
475 return true
476 elseif a[2]:match("%*") and (not b[2]:match("%*")) then
477 return false
478 else
479 return a[1] < b[1]
480 end
481 end)
482 for _, fa in ipairs(files) do
483
484 local f = fa[2]
485
486 if f:match("%.so$") or f:match("%.dylib$") or f:match("%.dll$") then
487 f = f:gsub("%.[^.]+$", "." .. cfg.external_lib_extension)
488 end
489
490 local pattern
491 if f:match("%*") then
492 pattern = "^" .. f:gsub("([-.+])", "%%%1"):gsub("%*", ".*") .. "$"
493 f = "matching " .. f
494 end
495
496 for _, d in ipairs(paths) do
497 if pattern then
498 if not cache[d] then
499 cache[d] = fs.list_dir(d)
500 end
501 local match = string.match
502 for _, entry in ipairs(cache[d]) do
503 if match(entry, pattern) then
504 found = true
505 break
506 end
507 end
508 else
509 found = fs.is_file(dir.path(d, f))
510 end
511 if found then
512 dirdata.dir = d
513 dirdata.file = f
514 break
515 else
516 table.insert(err_files[dirdata.testfile], f .. " in " .. d)
517 end
518 end
519 if found then
520 break
521 end
522 end
523 if not found then
524 return nil, dirname, dirdata.testfile
525 end
526 else
527
528
529
530 dirdata.dir = paths[1]
531 for _, p in ipairs(paths) do
532 if fs.exists(p) then
533 dirdata.dir = p
534 break
535 end
536 end
537 end
538 end
539
540 for dirname, dirdata in pairs(dirs) do
541 vars[name .. "_" .. dirname] = dirdata.dir
542 vars[name .. "_" .. dirname .. "_FILE"] = dirdata.file
543 end
544 vars[name .. "_DIR"] = prefix
545 return true
546end
547
548local function check_external_dependency(
549 name,
550 ext_files,
551 vars,
552 mode,
553 cache)
554 local ok
555 local err_dirname
556 local err_testfile
557 local err_files = { program = {}, header = {}, library = {} }
558
559 local dirs = get_external_deps_dirs(mode)
560
561 local prefixes
562 if vars[name .. "_DIR"] then
563 prefixes = { vars[name .. "_DIR"] }
564 elseif vars.DEPS_DIR then
565 prefixes = { vars.DEPS_DIR }
566 else
567 prefixes = cfg.external_deps_dirs
568 end
569
570 for _, prefix in ipairs(prefixes) do
571 prefix = resolve_prefix(prefix, dirs)
572 if cfg.is_platform("mingw32") and name == "LUA" then
573 dirs.LIBDIR.pattern = fun.filter(util.deep_copy(dirs.LIBDIR.pattern), function(s)
574 return not s:match("%.a$")
575 end)
576 elseif cfg.is_platform("windows") and name == "LUA" then
577 dirs.LIBDIR.pattern = fun.filter(util.deep_copy(dirs.LIBDIR.pattern), function(s)
578 return not s:match("%.dll$")
579 end)
580 end
581 ok, err_dirname, err_testfile = check_external_dependency_at(prefix, name, ext_files, vars, dirs, err_files, cache)
582 if ok then
583 return true
584 end
585 end
586
587 return nil, err_dirname, err_testfile, err_files
588end
589
590function deps.autodetect_external_dependencies(build)
591
592 if not build or not (build).modules then
593 return nil
594 end
595
596 local extdeps = {}
597 local any = false
598 for _, data in pairs((build).modules) do
599 if type(data) == "table" and data.libraries then
600 local libraries
601 local librariesstr = data.libraries
602 if type(librariesstr) == "string" then
603 libraries = { librariesstr }
604 else
605 libraries = librariesstr
606 end
607 local incdirs = {}
608 local libdirs = {}
609 for _, lib in ipairs(libraries) do
610 local upper = lib:upper():gsub("%+", "P"):gsub("[^%w]", "_")
611 any = true
612 extdeps[upper] = { library = lib }
613 table.insert(incdirs, "$(" .. upper .. "_INCDIR)")
614 table.insert(libdirs, "$(" .. upper .. "_LIBDIR)")
615 end
616 if not data.incdirs then
617 data.incdirs = incdirs
618 end
619 if not data.libdirs then
620 data.libdirs = libdirs
621 end
622 end
623 end
624 return any and extdeps or nil
625end
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640function deps.check_external_deps(rockspec, mode)
641
642 if not rockspec.external_dependencies then
643 rockspec.external_dependencies = deps.autodetect_external_dependencies(rockspec.build)
644 end
645 if not rockspec.external_dependencies then
646 return true
647 end
648
649 for name, ext_files in util.sortedpairs(rockspec.external_dependencies) do
650 local ok, err_dirname, err_testfile, err_files = check_external_dependency(name, ext_files, rockspec.variables, mode)
651 if not ok then
652 local lines = { "Could not find " .. err_testfile .. " file for " .. name }
653
654 local err_paths = {}
655 for _, err_file in ipairs(err_files[err_testfile]) do
656 if not err_paths[err_file] then
657 err_paths[err_file] = true
658 table.insert(lines, " No file " .. err_file)
659 end
660 end
661
662 table.insert(lines, "You may have to install " .. name .. " in your system and/or pass " .. name .. "_DIR or " .. name .. "_" .. err_dirname .. " to the luarocks command.")
663 table.insert(lines, "Example: luarocks install " .. rockspec.name .. " " .. name .. "_DIR=/usr/local")
664
665 return nil, table.concat(lines, "\n"), "dependency"
666 end
667 end
668 return true
669end
670
671
672
673
674
675
676
677
678function deps.scan_deps(results, mdeps, name, version, deps_mode)
679 assert(not name:match("/"))
680
681 local fetch = require("luarocks.fetch")
682
683 if results[name] then
684 return
685 end
686 if not mdeps[name] then mdeps[name] = {} end
687 local mdn = mdeps[name]
688 local dependencies = mdn[version]
689 local rocks_provided
690 if not dependencies then
691 local rockspec = fetch.load_local_rockspec(path.rockspec_file(name, version), false)
692 if not rockspec then
693 return
694 end
695 dependencies = rockspec.dependencies.queries
696 rocks_provided = rockspec.rocks_provided
697 mdn[version] = dependencies
698 else
699 rocks_provided = util.get_rocks_provided()
700 end
701
702 local get_versions = prepare_get_versions(deps_mode, rocks_provided, "dependencies")
703
704 local matched = match_all_deps(dependencies, get_versions)
705 results[name] = version
706 for _, match in pairs(matched) do
707 deps.scan_deps(results, mdeps, match.name, match.version, deps_mode)
708 end
709end
710
711local function lua_h_exists(d, luaver)
712 local major, minor = luaver:match("(%d+)%.(%d+)")
713 local luanum = ("%s%02d"):format(major, tonumber(minor))
714
715 local lua_h = dir.path(d, "lua.h")
716 local fd = io.open(lua_h)
717 if fd then
718 local data = fd:read("*a")
719 fd:close()
720 if data:match("LUA_VERSION_NUM%s*" .. tostring(luanum)) then
721 return d ~= nil
722 end
723 return nil, "Lua header lua.h found at " .. d .. " does not match Lua version " .. luaver .. ". You can use `luarocks config variables.LUA_INCDIR <path>` to set the correct location.", "dependency", 2
724 end
725
726 return nil, "Failed finding Lua header lua.h (searched at " .. d .. "). You may need to install Lua development headers. You can use `luarocks config variables.LUA_INCDIR <path>` to set the correct location.", "dependency", 1
727end
728
729local function find_lua_incdir(prefix, luaver, luajitver)
730 luajitver = luajitver and luajitver:gsub("%-.*", "")
731 local shortv = luaver:gsub("%.", "")
732 local incdirs = {
733 prefix .. "/include/lua/" .. luaver,
734 prefix .. "/include/lua" .. luaver,
735 prefix .. "/include/lua-" .. luaver,
736 prefix .. "/include/lua" .. shortv,
737 prefix .. "/include",
738 prefix,
739 luajitver and (prefix .. "/include/luajit-" .. (luajitver:match("^(%d+%.%d+)") or "")),
740 }
741 local errprio = 0
742 local mainerr
743 for _, d in ipairs(incdirs) do
744 local ok, err, _, prio = lua_h_exists(d, luaver)
745 if ok then
746 return d
747 end
748 if prio > errprio then
749 mainerr = err
750 errprio = prio
751 end
752 end
753
754
755 return nil, mainerr
756end
757
758function deps.check_lua_incdir(vars)
759 if vars.LUA_INCDIR_OK == "ok" then
760 return true
761 end
762
763 local ljv = util.get_luajit_version()
764
765 if vars.LUA_INCDIR then
766 local ok, err = lua_h_exists(vars.LUA_INCDIR, cfg.lua_version)
767 if ok then
768 vars.LUA_INCDIR_OK = "ok"
769 end
770 return ok, err
771 end
772
773 if vars.LUA_DIR then
774 local d, err = find_lua_incdir(vars.LUA_DIR, cfg.lua_version, ljv)
775 if d then
776 vars.LUA_INCDIR = d
777 vars.LUA_INCDIR_OK = "ok"
778 return true
779 end
780 return nil, err
781 end
782
783 return nil, "Failed finding Lua headers; neither LUA_DIR or LUA_INCDIR are set. You may need to install them or configure LUA_INCDIR.", "dependency"
784end
785
786function deps.check_lua_libdir(vars)
787 if vars.LUA_LIBDIR_OK == "ok" then
788 return true
789 end
790
791 local fs = require("luarocks.fs")
792 local ljv = util.get_luajit_version()
793
794 if vars.LUA_LIBDIR and vars.LUALIB and fs.exists(dir.path(vars.LUA_LIBDIR, vars.LUALIB)) then
795 vars.LUA_LIBDIR_OK = "ok"
796 return true
797 end
798
799 local shortv = cfg.lua_version:gsub("%.", "")
800 local libnames = {
801 "lua" .. cfg.lua_version,
802 "lua" .. shortv,
803 "lua-" .. cfg.lua_version,
804 "lua-" .. shortv,
805 "lua",
806 }
807 if ljv then
808 table.insert(libnames, 1, "luajit-" .. cfg.lua_version)
809 table.insert(libnames, 2, "luajit")
810 end
811 local cache = {}
812 local save_LUA_INCDIR = vars.LUA_INCDIR
813 local ok, _, _, errfiles = check_external_dependency("LUA", { library = libnames }, vars, "build", cache)
814 vars.LUA_INCDIR = save_LUA_INCDIR
815 local err
816 if ok then
817 local filename = dir.path(vars.LUA_LIBDIR, vars.LUA_LIBDIR_FILE)
818 local fd = io.open(filename, "r")
819 if fd then
820 if not vars.LUA_LIBDIR_FILE:match((cfg.lua_version:gsub("%.", "%%.?"))) then
821
822 local txt = fd:read("*a")
823 ok = txt:find("Lua " .. cfg.lua_version, 1, true) or
824 txt:find("lua" .. (cfg.lua_version:gsub("%.", "")), 1, true) and
825 true
826 if not ok then
827 err = "Lua library at " .. filename .. " does not match Lua version " .. cfg.lua_version .. ". You can use `luarocks config variables.LUA_LIBDIR <path>` to set the correct location."
828 end
829 end
830
831 fd:close()
832 end
833 end
834
835 if ok then
836 vars.LUALIB = vars.LUA_LIBDIR_FILE
837 vars.LUA_LIBDIR_OK = "ok"
838 return true
839 else
840 err = err or "Failed finding the Lua library. You can use `luarocks config variables.LUA_LIBDIR <path>` to set the correct location."
841 return nil, err, "dependency", errfiles
842 end
843end
844
845function deps.get_deps_mode(args)
846 return args.deps_mode or cfg.deps_mode
847end
848
849
850
851
852
853
854
855function deps.check_dependencies(repo, deps_mode)
856 local rocks_dir = path.rocks_dir(repo or cfg.root_dir)
857 if deps_mode == "none" then deps_mode = cfg.deps_mode end
858
859 local manifest = manif.load_manifest(rocks_dir)
860 if not manifest then
861 return
862 end
863
864 for name, versions in util.sortedpairs(manifest.repository) do
865 for version, version_entries in util.sortedpairs(versions, vers.compare_versions) do
866 for _, entry in ipairs(version_entries) do
867 if entry.arch == "installed" then
868 if manifest.dependencies[name] and manifest.dependencies[name][version] then
869 deps.report_missing_dependencies(name, version, manifest.dependencies[name][version], deps_mode, util.get_rocks_provided())
870 end
871 end
872 end
873 end
874 end
875end
876
877return deps
diff --git a/src/luarocks/dir.lua b/src/luarocks/dir.lua
new file mode 100644
index 00000000..b9989bbe
--- /dev/null
+++ b/src/luarocks/dir.lua
@@ -0,0 +1,65 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local package = _tl_compat and _tl_compat.package or package; local string = _tl_compat and _tl_compat.string or string
2
3local dir = {}
4
5
6
7
8
9local core = require("luarocks.core.dir")
10
11dir.path = core.path
12dir.split_url = core.split_url
13dir.normalize = core.normalize
14
15local dir_sep = package.config:sub(1, 1)
16
17
18
19
20
21function dir.base_name(pathname)
22
23 local b
24 b = pathname:gsub("[/\\]", "/")
25 b = b:gsub("/*$", "")
26 b = b:match(".*[/\\]([^/\\]*)")
27 b = b or pathname
28
29 return b
30end
31
32
33
34
35
36
37function dir.dir_name(pathname)
38
39 local d
40 d = pathname:gsub("[/\\]", "/")
41 d = d:gsub("/*$", "")
42 d = d:match("(.*)[/]+[^/]*")
43 d = d or ""
44 d = d:gsub("/", dir_sep)
45
46 return d
47end
48
49
50
51function dir.is_basic_protocol(protocol)
52 return protocol == "http" or protocol == "https" or protocol == "ftp" or protocol == "file"
53end
54
55function dir.deduce_base_dir(url)
56
57 local known_exts = {}
58 for _, ext in ipairs({ "zip", "git", "tgz", "tar", "gz", "bz2" }) do
59 known_exts[ext] = ""
60 end
61 local base = dir.base_name(url)
62 return (base:gsub("%.([^.]*)$", known_exts):gsub("%.tar", ""))
63end
64
65return dir
diff --git a/src/luarocks/download.lua b/src/luarocks/download.lua
new file mode 100644
index 00000000..068ade96
--- /dev/null
+++ b/src/luarocks/download.lua
@@ -0,0 +1,76 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local download = {}
2
3
4local path = require("luarocks.path")
5local fetch = require("luarocks.fetch")
6local search = require("luarocks.search")
7local queries = require("luarocks.queries")
8local fs = require("luarocks.fs")
9local dir = require("luarocks.dir")
10local util = require("luarocks.util")
11
12local function get_file(filename)
13 local protocol, pathname = dir.split_url(filename)
14 if protocol == "file" then
15 local ok, err = fs.copy(pathname, fs.current_dir(), "read")
16 if ok then
17 return pathname
18 else
19 return nil, err
20 end
21 else
22
23 local ok, err = fetch.fetch_url(filename)
24 return ok, err
25 end
26end
27
28function download.download_all(arch, name, namespace, version)
29 local substring = (name == "")
30 local query = queries.new(name, namespace, version, substring, arch)
31 local search_err
32
33 local results = search.search_repos(query)
34 local has_result = false
35 local all_ok = true
36 local any_err = ""
37 for name, result in pairs(results) do
38 for version, items in pairs(result) do
39 for _, item in ipairs(items) do
40
41 if item.arch ~= "installed" then
42 has_result = true
43 local filename = path.make_url(item.repo, name, version, item.arch)
44 local ok, err = get_file(filename)
45 if not ok then
46 all_ok = false
47 any_err = any_err .. "\n" .. err
48 end
49 end
50 end
51 end
52 end
53
54 if has_result then
55 return all_ok, any_err
56 end
57
58 local rock = util.format_rock_name(name, namespace, version)
59 return nil, "Could not find a result named " .. rock .. (search_err and ": " .. search_err or ".")
60end
61
62function download.download_file(arch, name, namespace, version, check_lua_versions)
63 local query = queries.new(name, namespace, version, false, arch)
64 local search_err
65
66 local url
67 url, search_err = search.find_rock_checking_lua_versions(query, check_lua_versions)
68 if url then
69 return get_file(url)
70 end
71
72 local rock = util.format_rock_name(name, namespace, version)
73 return nil, "Could not find a result named " .. rock .. (search_err and ": " .. search_err or ".")
74end
75
76return download
diff --git a/src/luarocks/fetch.lua b/src/luarocks/fetch.lua
new file mode 100644
index 00000000..7472ee3f
--- /dev/null
+++ b/src/luarocks/fetch.lua
@@ -0,0 +1,615 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2
3local fetch = {Fetch = {}, }
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18local fs = require("luarocks.fs")
19local dir = require("luarocks.dir")
20local rockspecs = require("luarocks.rockspecs")
21local signing = require("luarocks.signing")
22local persist = require("luarocks.persist")
23local util = require("luarocks.util")
24local cfg = require("luarocks.core.cfg")
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48function fetch.fetch_caching(url, mirroring)
49 local repo_url, filename = url:match("^(.*)/([^/]+)$")
50 local name = repo_url:gsub("[/:]", "_")
51 local cache_dir = dir.path(cfg.local_cache, name)
52 local ok = fs.make_dir(cache_dir)
53
54 local cachefile = dir.path(cache_dir, filename)
55 local checkfile = cachefile .. ".check"
56
57 if (fs.file_age(checkfile) < 10 or
58 cfg.aggressive_cache and (not name:match("^manifest"))) and fs.exists(cachefile) then
59
60 return cachefile, nil, nil, true
61 end
62
63 local lock, errlock
64 if ok then
65 lock, errlock = fs.lock_access(cache_dir)
66 end
67
68 if not (ok and lock) then
69 cfg.local_cache = fs.make_temp_dir("local_cache")
70 if not cfg.local_cache then
71 return nil, "Failed creating temporary local_cache directory"
72 end
73 cache_dir = dir.path(cfg.local_cache, name)
74 ok = fs.make_dir(cache_dir)
75 if not ok then
76 return nil, "Failed creating temporary cache directory " .. cache_dir
77 end
78 lock = fs.lock_access(cache_dir)
79 end
80
81 local file, err, errcode, from_cache = fetch.fetch_url(url, cachefile, true, mirroring)
82 if not file then
83 fs.unlock_access(lock)
84 return nil, err or "Failed downloading " .. url, errcode
85 end
86
87 local fd, erropen = io.open(checkfile, "wb")
88 if erropen then
89 fs.unlock_access(lock)
90 return nil, erropen
91 end
92 fd:write("!")
93 fd:close()
94
95 fs.unlock_access(lock)
96 return file, nil, nil, from_cache
97end
98
99local function ensure_trailing_slash(url)
100 return (url:gsub("/*$", "/"))
101end
102
103local function is_url_relative_to_rocks_servers(url, servers)
104 for _, item in ipairs(servers) do
105 if type(item) == "table" then
106 for i, s in ipairs(item) do
107 local base = ensure_trailing_slash(s)
108 if string.find(url, base, 1, true) == 1 then
109 return i, url:sub(#base + 1), item
110 end
111 end
112 end
113 end
114end
115
116local function download_with_mirrors(url, filename, cache, servers)
117 local idx, rest, mirrors = is_url_relative_to_rocks_servers(url, servers)
118
119 if not idx then
120
121 return fs.download(url, filename, cache)
122 end
123
124
125 local err = "\n"
126 for i = idx, #mirrors do
127 local try_url = ensure_trailing_slash(mirrors[i]) .. rest
128 if i > idx then
129 util.warning("Failed downloading. Attempting mirror at " .. try_url)
130 end
131 local name, _, _, from_cache = fs.download(try_url, filename, cache)
132 if name then
133 return name, nil, nil, from_cache
134 else
135 err = err .. name .. "\n"
136 end
137 end
138
139 return nil, err, "network"
140end
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165function fetch.fetch_url(url, filename, cache, mirroring)
166
167 local protocol, pathname = dir.split_url(url)
168 if protocol == "file" then
169 local fullname = fs.absolute_name(pathname)
170 if not fs.exists(fullname) then
171 local hint = (not pathname:match("^/")) and
172 (" - note that given path in rockspec is not absolute: " .. url) or
173 ""
174 return nil, "Local file not found: " .. fullname .. hint
175 end
176 filename = filename or dir.base_name(pathname)
177 local dstname = fs.absolute_name(dir.path(".", filename))
178 local ok, err
179 if fullname == dstname then
180 ok = true
181 else
182 ok, err = fs.copy(fullname, dstname)
183 end
184 if ok then
185 return dstname
186 else
187 return nil, "Failed copying local file " .. fullname .. " to " .. dstname .. ": " .. err
188 end
189 elseif dir.is_basic_protocol(protocol) then
190 local name, err, err_code, from_cache
191 if mirroring ~= "no_mirror" then
192 name, err, err_code, from_cache = download_with_mirrors(url, filename, cache, cfg.rocks_servers)
193 else
194 name, err, err_code, from_cache = fs.download(url, filename, cache)
195 end
196 if not name then
197 return nil, "Failed downloading " .. url .. (err and " - " .. err or ""), err_code
198 end
199 return name, nil, nil, from_cache
200 else
201 return nil, "Unsupported protocol " .. protocol
202 end
203end
204
205
206
207
208
209
210
211
212
213
214
215
216function fetch.fetch_url_at_temp_dir(url, tmpname, filename, cache)
217 filename = filename or dir.base_name(url)
218
219 local protocol, pathname = dir.split_url(url)
220 if protocol == "file" then
221 if fs.exists(pathname) then
222 return pathname, dir.dir_name(fs.absolute_name(pathname))
223 else
224 return nil, "File not found: " .. pathname
225 end
226 else
227 local temp_dir, errmake = fs.make_temp_dir(tmpname)
228 if not temp_dir then
229 return nil, "Failed creating temporary directory " .. tmpname .. ": " .. errmake
230 end
231 util.schedule_function(fs.delete, temp_dir)
232 local ok, errchange = fs.change_dir(temp_dir)
233 if not ok then return nil, errchange end
234
235 local file, err, errcode
236
237 if cache then
238 local cachefile
239 cachefile, err, errcode = fetch.fetch_caching(url)
240
241 if cachefile then
242 file = dir.path(temp_dir, filename)
243 fs.copy(cachefile, file)
244 end
245 end
246
247 if not file then
248 file, err, errcode = fetch.fetch_url(url, filename, cache)
249 end
250
251 fs.pop_dir()
252 if not file then
253 return nil, "Error fetching file: " .. err, errcode
254 end
255
256 return file, temp_dir
257 end
258end
259
260
261
262
263
264
265
266
267
268
269
270
271
272function fetch.find_base_dir(file, temp_dir, src_url, src_dir)
273 local ok, err = fs.change_dir(temp_dir)
274 if not ok then return nil, err end
275 fs.unpack_archive(file)
276
277 if not src_dir then
278 local rockspec = {
279 source = {
280 file = file,
281 dir = src_dir,
282 url = src_url,
283 },
284 }
285 ok, err = fetch.find_rockspec_source_dir(rockspec, ".")
286 if ok then
287 src_dir = rockspec.source.dir
288 end
289 end
290
291 local inferred_dir = src_dir or dir.deduce_base_dir(src_url)
292 local found_dir = nil
293 if fs.exists(inferred_dir) then
294 found_dir = inferred_dir
295 else
296 util.printerr("Directory " .. inferred_dir .. " not found")
297 local files = fs.list_dir()
298 if files then
299 table.sort(files)
300 for _, filename in ipairs(files) do
301 if fs.is_dir(filename) then
302 util.printerr("Found " .. filename)
303 found_dir = filename
304 break
305 end
306 end
307 end
308 end
309 fs.pop_dir()
310 return inferred_dir, found_dir
311end
312
313
314local function fetch_and_verify_signature_for(url, filename, tmpdir)
315 local sig_url = signing.signature_url(url)
316 local sig_file, errfetch, errcode = fetch.fetch_url_at_temp_dir(sig_url, tmpdir)
317 if not sig_file then
318 return nil, "Could not fetch signature file for verification: " .. errfetch, errcode
319 end
320
321 local ok, err = signing.verify_signature(filename, sig_file)
322 if not ok then
323 return nil, "Failed signature verification: " .. err
324 end
325
326 return fs.absolute_name(sig_file)
327end
328
329
330
331
332
333
334
335
336
337
338function fetch.fetch_and_unpack_rock(url, dest, verify)
339
340 local name = dir.base_name(url):match("(.*)%.[^.]*%.rock")
341 local tmpname = "luarocks-rock-" .. name
342
343 local rock_file, err, errcode = fetch.fetch_url_at_temp_dir(url, tmpname, nil, true)
344 if not rock_file then
345 return nil, "Could not fetch rock file: " .. err, errcode
346 end
347
348 local sig_file
349 if verify then
350 sig_file, err = fetch_and_verify_signature_for(url, rock_file, tmpname)
351 if err then
352 return nil, err
353 end
354 end
355
356 rock_file = fs.absolute_name(rock_file)
357
358 local unpack_dir
359 if dest then
360 unpack_dir = dest
361 local ok, errmake = fs.make_dir(unpack_dir)
362 if not ok then
363 return nil, "Failed unpacking rock file: " .. errmake
364 end
365 else
366 unpack_dir, err = fs.make_temp_dir(name)
367 if not unpack_dir then
368 return nil, "Failed creating temporary dir: " .. err
369 end
370 end
371 if not dest then
372 util.schedule_function(fs.delete, unpack_dir)
373 end
374 local ok, errchange = fs.change_dir(unpack_dir)
375 if not ok then return nil, errchange end
376 ok, err = fs.unzip(rock_file)
377 if not ok then
378 return nil, "Failed unpacking rock file: " .. rock_file .. ": " .. err
379 end
380 if sig_file then
381 ok, err = fs.copy(sig_file, ".")
382 if not ok then
383 return nil, "Failed copying signature file"
384 end
385 end
386 fs.pop_dir()
387 return unpack_dir
388end
389
390
391
392
393
394
395
396
397function fetch.load_local_rockspec(rel_filename, quick)
398 local abs_filename = fs.absolute_name(rel_filename)
399
400 local basename = dir.base_name(abs_filename)
401 if basename ~= "rockspec" then
402 if not basename:match("(.*)%-[^-]*%-[0-9]*") then
403 return nil, "Expected filename in format 'name-version-revision.rockspec'."
404 end
405 end
406
407 local tbl, err = persist.load_into_table(abs_filename)
408 if not tbl and type(err) == "string" then
409 return nil, "Could not load rockspec file " .. abs_filename .. " (" .. err .. ")"
410 end
411
412 local rockspec, errrock = rockspecs.from_persisted_table(abs_filename, tbl, err, quick)
413 if not rockspec then
414 return nil, abs_filename .. ": " .. errrock
415 end
416
417 local name_version = rockspec.package:lower() .. "-" .. rockspec.version
418 if basename ~= "rockspec" and basename ~= name_version .. ".rockspec" then
419 return nil, "Inconsistency between rockspec filename (" .. basename .. ") and its contents (" .. name_version .. ".rockspec)."
420 end
421
422 return rockspec
423end
424
425
426
427
428
429
430
431
432
433
434
435function fetch.load_rockspec(url, location, verify)
436
437 local name
438 local basename = dir.base_name(url)
439 if basename == "rockspec" then
440 name = "rockspec"
441 else
442 name = basename:match("(.*)%.rockspec")
443 if not name then
444 return nil, "Filename '" .. url .. "' does not look like a rockspec."
445 end
446 end
447
448 local tmpname = "luarocks-rockspec-" .. name
449 local filename, err, errcode, ok
450 if location then
451 ok, err = fs.change_dir(location)
452 if not ok then return nil, err end
453 filename, err = fetch.fetch_url(url)
454 fs.pop_dir()
455 else
456 filename, err, errcode = fetch.fetch_url_at_temp_dir(url, tmpname, nil, true)
457 end
458 if not filename then
459 return nil, err, errcode
460 end
461
462 if verify then
463 local _, errfetch = fetch_and_verify_signature_for(url, filename, tmpname)
464 if err then
465 return nil, errfetch
466 end
467 end
468
469 return fetch.load_local_rockspec(filename)
470end
471
472
473
474
475
476
477
478
479
480
481function fetch.get_sources(rockspec, extract, dest_dir)
482
483 local url = rockspec.source.url
484 local name = rockspec.name .. "-" .. rockspec.version
485 local filename = rockspec.source.file
486 local source_file, store_dir
487 local ok, err, errcode
488 if dest_dir then
489 ok, err = fs.change_dir(dest_dir)
490 if not ok then return nil, err, "dest_dir" end
491 source_file, err, errcode = fetch.fetch_url(url, filename)
492 fs.pop_dir()
493 store_dir = dest_dir
494 else
495 source_file, store_dir, errcode = fetch.fetch_url_at_temp_dir(url, "luarocks-source-" .. name, filename)
496 end
497 if not source_file then
498 return nil, err or store_dir, errcode
499 end
500 if rockspec.source.md5 then
501 if not fs.check_md5(source_file, rockspec.source.md5) then
502 return nil, "MD5 check for " .. filename .. " has failed.", "md5"
503 end
504 end
505 if extract then
506 ok, err = fs.change_dir(store_dir)
507 if not ok then return nil, err end
508 ok, err = fs.unpack_archive(rockspec.source.file)
509 if not ok then return nil, err end
510 ok, err = fetch.find_rockspec_source_dir(rockspec, ".")
511 if not ok then return nil, err end
512 fs.pop_dir()
513 end
514 return source_file, store_dir
515end
516
517function fetch.find_rockspec_source_dir(rockspec, store_dir)
518 local ok, err = fs.change_dir(store_dir)
519 if not ok then return nil, err end
520
521 local file_count, dir_count = 0, 0
522 local found_dir
523
524 if rockspec.source.dir and fs.exists(rockspec.source.dir) then
525 ok, err = true, nil
526 elseif rockspec.source.file and rockspec.source.dir then
527 ok, err = nil, "Directory " .. rockspec.source.dir .. " not found inside archive " .. rockspec.source.file
528 elseif not rockspec.source.dir_set then
529
530 local name = dir.base_name(rockspec.source.file or rockspec.source.url or "")
531
532 if name:match("%.lua$") or name:match("%.c$") then
533 if fs.is_file(name) then
534 rockspec.source.dir = "."
535 ok, err = true, nil
536 end
537 end
538
539 if not rockspec.source.dir then
540 for file in fs.dir() do
541 file_count = file_count + 1
542 if fs.is_dir(file) then
543 dir_count = dir_count + 1
544 found_dir = file
545 end
546 end
547
548 if dir_count == 1 then
549 rockspec.source.dir = found_dir
550 ok, err = true, nil
551 else
552 ok, err = nil, "Could not determine source directory from rock contents (" .. tostring(file_count) .. " file(s), " .. tostring(dir_count) .. " dir(s))"
553 end
554 end
555 else
556 ok, err = nil, "Could not determine source directory, please set source.dir in rockspec."
557 end
558
559 fs.pop_dir()
560
561 assert(rockspec.source.dir or not ok)
562 return ok, err
563end
564
565
566
567
568
569
570
571
572
573
574function fetch.fetch_sources(rockspec, extract, dest_dir)
575
576
577
578 if rockspec.source.url:match("^git://github%.com/") or
579 rockspec.source.url:match("^git://www%.github%.com/") then
580 rockspec.source.url = rockspec.source.url:gsub("^git://", "git+https://")
581 rockspec.source.protocol = "git+https"
582 end
583
584 local protocol = rockspec.source.protocol
585 local ok, err, proto
586
587 if dir.is_basic_protocol(protocol) then
588 proto = fetch
589 else
590 ok, proto = pcall(require, "luarocks.fetch." .. protocol:gsub("[+-]", "_"))
591 if not ok then
592 return nil, "Unknown protocol " .. protocol
593 end
594 end
595
596 if cfg.only_sources_from and
597 rockspec.source.pathname and
598 #rockspec.source.pathname > 0 then
599 if #cfg.only_sources_from == 0 then
600 return nil, "Can't download " .. rockspec.source.url .. " -- download from remote servers disabled"
601 elseif rockspec.source.pathname:find(cfg.only_sources_from, 1, true) ~= 1 then
602 return nil, "Can't download " .. rockspec.source.url .. " -- only downloading from " .. cfg.only_sources_from
603 end
604 end
605
606 local source_file, store_dir = proto.get_sources(rockspec, extract, dest_dir)
607 if not source_file then return nil, store_dir end
608
609 ok, err = fetch.find_rockspec_source_dir(rockspec, store_dir)
610 if not ok then return nil, err, "source.dir", source_file, store_dir end
611
612 return source_file, store_dir
613end
614
615return fetch
diff --git a/src/luarocks/fetch/cvs.lua b/src/luarocks/fetch/cvs.lua
new file mode 100644
index 00000000..6aabfb31
--- /dev/null
+++ b/src/luarocks/fetch/cvs.lua
@@ -0,0 +1,53 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack
2
3local cvs = {}
4
5
6local fs = require("luarocks.fs")
7local dir = require("luarocks.dir")
8local util = require("luarocks.util")
9
10
11
12
13
14
15
16
17
18
19function cvs.get_sources(rockspec, extract, dest_dir)
20 local cvs_cmd = rockspec.variables.CVS
21 local ok, err_msg = fs.is_tool_available(cvs_cmd, "CVS")
22 if not ok then
23 return nil, err_msg
24 end
25
26 local name_version = rockspec.name .. "-" .. rockspec.version
27 local module = rockspec.source.module or dir.base_name(rockspec.source.url)
28 local command = { cvs_cmd, "-d" .. rockspec.source.pathname, "export", module }
29 if rockspec.source.tag then
30 table.insert(command, 4, "-r")
31 table.insert(command, 5, rockspec.source.tag)
32 end
33 local store_dir
34 if not dest_dir then
35 store_dir = fs.make_temp_dir(name_version)
36 if not store_dir then
37 return nil, "Failed creating temporary directory."
38 end
39 util.schedule_function(fs.delete, store_dir)
40 else
41 store_dir = dest_dir
42 end
43 local okchange, err = fs.change_dir(store_dir)
44 if not okchange then return nil, err end
45 if not fs.execute(_tl_table_unpack(command)) then
46 return nil, "Failed fetching files from CVS."
47 end
48 fs.pop_dir()
49 return module, store_dir
50end
51
52
53return cvs
diff --git a/src/luarocks/fetch/git.lua b/src/luarocks/fetch/git.lua
new file mode 100644
index 00000000..b32dfb1b
--- /dev/null
+++ b/src/luarocks/fetch/git.lua
@@ -0,0 +1,166 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack
2
3local git = {}
4
5
6
7local fs = require("luarocks.fs")
8local dir = require("luarocks.dir")
9local vers = require("luarocks.core.vers")
10local util = require("luarocks.util")
11
12
13
14
15local cached_git_version
16
17
18
19
20local function git_version(git_cmd)
21 if not cached_git_version then
22 local version_line = io.popen(fs.Q(git_cmd) .. ' --version'):read()
23 local version_string = version_line:match('%d-%.%d+%.?%d*')
24 cached_git_version = vers.parse_version(version_string)
25 end
26
27 return cached_git_version
28end
29
30
31
32
33
34local function git_is_at_least(git_cmd, version)
35 return git_version(git_cmd) >= vers.parse_version(version)
36end
37
38
39
40
41
42
43
44local function git_can_clone_by_tag(git_cmd)
45 return git_is_at_least(git_cmd, "1.7.10")
46end
47
48
49
50
51
52local function git_supports_shallow_submodules(git_cmd)
53 return git_is_at_least(git_cmd, "1.8.4")
54end
55
56
57
58
59local function git_supports_shallow_recommendations(git_cmd)
60 return git_is_at_least(git_cmd, "2.10.0")
61end
62
63local function git_identifier(git_cmd, ver)
64 if not (ver:match("^dev%-%d+$") or ver:match("^scm%-%d+$")) then
65 return nil
66 end
67 local pd = io.popen(fs.command_at(fs.current_dir(), fs.Q(git_cmd) .. " log --pretty=format:%ai_%h -n 1"))
68 if not pd then
69 return nil
70 end
71 local date_hash = pd:read("*l")
72 pd:close()
73 if not date_hash then
74 return nil
75 end
76 local date, time, _tz, hash = date_hash:match("([^%s]+) ([^%s]+) ([^%s]+)_([^%s]+)")
77 date = date:gsub("%-", "")
78 time = time:gsub(":", "")
79 return date .. "." .. time .. "." .. hash
80end
81
82
83
84
85
86
87
88
89function git.get_sources(rockspec, extract, dest_dir, depth)
90
91 local git_cmd = rockspec.variables.GIT
92 local name_version = rockspec.name .. "-" .. rockspec.version
93 local module = dir.base_name(rockspec.source.url)
94
95 module = module:gsub("%.git$", "")
96
97 local ok_available, err_msg = fs.is_tool_available(git_cmd, "Git")
98 if not ok_available then
99 return nil, err_msg
100 end
101
102 local store_dir
103 if not dest_dir then
104 store_dir = fs.make_temp_dir(name_version)
105 if not store_dir then
106 return nil, "Failed creating temporary directory."
107 end
108 util.schedule_function(fs.delete, store_dir)
109 else
110 store_dir = dest_dir
111 end
112 store_dir = fs.absolute_name(store_dir)
113 local ok, err = fs.change_dir(store_dir)
114 if not ok then return nil, err end
115
116 local command = { fs.Q(git_cmd), "clone", depth or "--depth=1", rockspec.source.url, module }
117 local tag_or_branch = rockspec.source.tag or rockspec.source.branch
118
119
120 if tag_or_branch == "master" then tag_or_branch = nil end
121 if tag_or_branch then
122 if git_can_clone_by_tag(git_cmd) then
123
124
125 table.insert(command, 3, "--branch=" .. tag_or_branch)
126 end
127 end
128 if not fs.execute(_tl_table_unpack(command)) then
129 return nil, "Failed cloning git repository."
130 end
131 ok, err = fs.change_dir(module)
132 if not ok then return nil, err end
133 if tag_or_branch and not git_can_clone_by_tag() then
134 if not fs.execute(fs.Q(git_cmd), "checkout", tag_or_branch) then
135 return nil, 'Failed to check out the "' .. tag_or_branch .. '" tag or branch.'
136 end
137 end
138
139
140 if rockspec:format_is_at_least("3.0") then
141 command = { fs.Q(git_cmd), "submodule", "update", "--init", "--recursive" }
142
143 if git_supports_shallow_recommendations(git_cmd) then
144 table.insert(command, 5, "--recommend-shallow")
145 elseif git_supports_shallow_submodules(git_cmd) then
146
147 table.insert(command, 5, "--depth=1")
148 end
149
150 if not fs.execute(_tl_table_unpack(command)) then
151 return nil, 'Failed to fetch submodules.'
152 end
153 end
154
155 if not rockspec.source.tag then
156 rockspec.source.identifier = git_identifier(git_cmd, rockspec.version)
157 end
158
159 fs.delete(dir.path(store_dir, module, ".git"))
160 fs.delete(dir.path(store_dir, module, ".gitignore"))
161 fs.pop_dir()
162 fs.pop_dir()
163 return module, store_dir
164end
165
166return git
diff --git a/src/luarocks/fetch/git_file.lua b/src/luarocks/fetch/git_file.lua
new file mode 100644
index 00000000..60d06323
--- /dev/null
+++ b/src/luarocks/fetch/git_file.lua
@@ -0,0 +1,22 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string
2
3local git_file = {}
4
5
6local git = require("luarocks.fetch.git")
7
8
9
10
11
12
13
14
15
16
17function git_file.get_sources(rockspec, extract, dest_dir)
18 rockspec.source.url = rockspec.source.url:gsub("^git.file://", "")
19 return git.get_sources(rockspec, extract, dest_dir)
20end
21
22return git_file
diff --git a/src/luarocks/fetch/git_http.lua b/src/luarocks/fetch/git_http.lua
new file mode 100644
index 00000000..9275209c
--- /dev/null
+++ b/src/luarocks/fetch/git_http.lua
@@ -0,0 +1,29 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string
2
3
4
5
6
7
8
9
10local git_http = {}
11
12
13local git = require("luarocks.fetch.git")
14
15
16
17
18
19
20
21
22
23
24function git_http.get_sources(rockspec, extract, dest_dir)
25 rockspec.source.url = rockspec.source.url:gsub("^git.", "")
26 return git.get_sources(rockspec, extract, dest_dir, "--")
27end
28
29return git_http
diff --git a/src/luarocks/fetch/git_https.lua b/src/luarocks/fetch/git_https.lua
new file mode 100644
index 00000000..463f2f08
--- /dev/null
+++ b/src/luarocks/fetch/git_https.lua
@@ -0,0 +1,7 @@
1
2
3
4
5
6
7return require("luarocks.fetch.git_http")
diff --git a/src/luarocks/fetch/git_ssh.lua b/src/luarocks/fetch/git_ssh.lua
new file mode 100644
index 00000000..8a95a722
--- /dev/null
+++ b/src/luarocks/fetch/git_ssh.lua
@@ -0,0 +1,35 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string
2
3
4
5
6
7
8
9
10local git_ssh = {}
11
12
13local git = require("luarocks.fetch.git")
14
15
16
17
18
19
20
21
22
23
24function git_ssh.get_sources(rockspec, extract, dest_dir)
25 rockspec.source.url = rockspec.source.url:gsub("^git.", "")
26
27
28 if rockspec.source.url:match("^ssh://[^/]+:[^%d]") then
29 rockspec.source.url = rockspec.source.url:gsub("^ssh://", "")
30 end
31
32 return git.get_sources(rockspec, extract, dest_dir, "--")
33end
34
35return git_ssh
diff --git a/src/luarocks/fetch/hg.lua b/src/luarocks/fetch/hg.lua
new file mode 100644
index 00000000..5265920f
--- /dev/null
+++ b/src/luarocks/fetch/hg.lua
@@ -0,0 +1,64 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack
2
3local hg = {}
4
5
6local fs = require("luarocks.fs")
7local dir = require("luarocks.dir")
8local util = require("luarocks.util")
9
10
11
12
13
14
15
16
17
18
19function hg.get_sources(rockspec, extract, dest_dir)
20
21 local hg_cmd = rockspec.variables.HG
22 local ok_available, err_msg = fs.is_tool_available(hg_cmd, "Mercurial")
23 if not ok_available then
24 return nil, err_msg
25 end
26
27 local name_version = rockspec.name .. "-" .. rockspec.version
28
29 local url = rockspec.source.url:gsub("^hg://", "")
30
31 local module = dir.base_name(url)
32
33 local command = { hg_cmd, "clone", url, module }
34 local tag_or_branch = rockspec.source.tag or rockspec.source.branch
35 if tag_or_branch then
36 command = { hg_cmd, "clone", "--rev", tag_or_branch, url, module }
37 end
38 local store_dir
39 if not dest_dir then
40 store_dir = fs.make_temp_dir(name_version)
41 if not store_dir then
42 return nil, "Failed creating temporary directory."
43 end
44 util.schedule_function(fs.delete, store_dir)
45 else
46 store_dir = dest_dir
47 end
48 local ok, err = fs.change_dir(store_dir)
49 if not ok then return nil, err end
50 if not fs.execute(_tl_table_unpack(command)) then
51 return nil, "Failed cloning hg repository."
52 end
53 ok, err = fs.change_dir(module)
54 if not ok then return nil, err end
55
56 fs.delete(dir.path(store_dir, module, ".hg"))
57 fs.delete(dir.path(store_dir, module, ".hgignore"))
58 fs.pop_dir()
59 fs.pop_dir()
60 return module, store_dir
61end
62
63
64return hg
diff --git a/src/luarocks/fetch/hg_http.lua b/src/luarocks/fetch/hg_http.lua
new file mode 100644
index 00000000..9f56c02d
--- /dev/null
+++ b/src/luarocks/fetch/hg_http.lua
@@ -0,0 +1,27 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string
2
3
4
5
6
7
8local hg_http = {}
9
10
11local hg = require("luarocks.fetch.hg")
12
13
14
15
16
17
18
19
20
21
22function hg_http.get_sources(rockspec, extract, dest_dir)
23 rockspec.source.url = rockspec.source.url:gsub("^hg.", "")
24 return hg.get_sources(rockspec, extract, dest_dir)
25end
26
27return hg_http
diff --git a/src/luarocks/fetch/hg_https.lua b/src/luarocks/fetch/hg_https.lua
new file mode 100644
index 00000000..1b6475ad
--- /dev/null
+++ b/src/luarocks/fetch/hg_https.lua
@@ -0,0 +1,8 @@
1
2
3
4
5
6
7
8return require("luarocks.fetch.hg_http")
diff --git a/src/luarocks/fetch/hg_ssh.lua b/src/luarocks/fetch/hg_ssh.lua
new file mode 100644
index 00000000..1b6475ad
--- /dev/null
+++ b/src/luarocks/fetch/hg_ssh.lua
@@ -0,0 +1,8 @@
1
2
3
4
5
6
7
8return require("luarocks.fetch.hg_http")
diff --git a/src/luarocks/fetch/sscm.lua b/src/luarocks/fetch/sscm.lua
new file mode 100644
index 00000000..f635c02f
--- /dev/null
+++ b/src/luarocks/fetch/sscm.lua
@@ -0,0 +1,45 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local string = _tl_compat and _tl_compat.string or string
2
3local sscm = {}
4
5
6local fs = require("luarocks.fs")
7local dir = require("luarocks.dir")
8
9
10
11
12
13
14
15
16
17
18function sscm.get_sources(rockspec, extract, dest_dir)
19
20 local sscm_cmd = rockspec.variables.SSCM
21 local module = rockspec.source.module or dir.base_name(rockspec.source.url)
22 local branch, repository = string.match(rockspec.source.pathname, "^([^/]*)/(.*)")
23 if not branch or not repository then
24 return nil, "Error retrieving branch and repository from rockspec."
25 end
26
27 local working_dir
28 local tmp = io.popen(string.format(sscm_cmd .. [[ property "/" -d -b%s -p%s]], branch, repository))
29 for line in tmp:lines() do
30
31 working_dir = string.match(line, "Working directory:[%s]*(.*)%c$")
32 if working_dir then break end
33 end
34 tmp:close()
35 if not working_dir then
36 return nil, "Error retrieving working directory from SSCM."
37 end
38 if not fs.execute(sscm_cmd, "get", "*", "-e", "-r", "-b" .. branch, "-p" .. repository, "-tmodify", "-wreplace") then
39 return nil, "Failed fetching files from SSCM."
40 end
41
42 return module, working_dir
43end
44
45return sscm
diff --git a/src/luarocks/fetch/svn.lua b/src/luarocks/fetch/svn.lua
new file mode 100644
index 00000000..cd467cef
--- /dev/null
+++ b/src/luarocks/fetch/svn.lua
@@ -0,0 +1,63 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack
2
3local svn = {}
4
5
6local fs = require("luarocks.fs")
7local dir = require("luarocks.dir")
8local util = require("luarocks.util")
9
10
11
12
13
14
15
16
17
18
19function svn.get_sources(rockspec, extract, dest_dir)
20
21 local svn_cmd = rockspec.variables.SVN
22 local ok, err_msg = fs.is_tool_available(svn_cmd, "Subversion")
23 if not ok then
24 return nil, err_msg
25 end
26
27 local name_version = rockspec.name .. "-" .. rockspec.version
28 local module = rockspec.source.module or dir.base_name(rockspec.source.url)
29 local url = rockspec.source.url:gsub("^svn://", "")
30 local command = { svn_cmd, "checkout", url, module }
31 if rockspec.source.tag then
32 table.insert(command, 5, "-r")
33 table.insert(command, 6, rockspec.source.tag)
34 end
35 local store_dir
36 if not dest_dir then
37 store_dir = fs.make_temp_dir(name_version)
38 if not store_dir then
39 return nil, "Failed creating temporary directory."
40 end
41 util.schedule_function(fs.delete, store_dir)
42 else
43 store_dir = dest_dir
44 end
45 local okchange, err = fs.change_dir(store_dir)
46 if not okchange then return nil, err end
47 if not fs.execute(_tl_table_unpack(command)) then
48 return nil, "Failed fetching files from Subversion."
49 end
50 okchange, err = fs.change_dir(module)
51 if not okchange then return nil, err end
52 for _, d in ipairs(fs.find(".")) do
53 if dir.base_name(d) == ".svn" then
54 fs.delete(dir.path(store_dir, module, d))
55 end
56 end
57 fs.pop_dir()
58 fs.pop_dir()
59 return module, store_dir
60end
61
62
63return svn
diff --git a/src/luarocks/fun.lua b/src/luarocks/fun.lua
new file mode 100644
index 00000000..86030ccc
--- /dev/null
+++ b/src/luarocks/fun.lua
@@ -0,0 +1,138 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local math = _tl_compat and _tl_compat.math or math; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack
2
3local fun = {}
4
5
6function fun.concat(xs, ys)
7 local rs = {}
8 local n = #xs
9 for i = 1, n do
10 rs[i] = xs[i]
11 end
12 for i = 1, #ys do
13 rs[i + n] = ys[i]
14 end
15
16 return rs
17end
18
19function fun.contains(xs, v)
20 for _, x in ipairs(xs) do
21 if v == x then
22 return true
23 end
24 end
25 return false
26end
27
28function fun.map(xs, f)
29 local rs = {}
30 for i = 1, #xs do
31 rs[i] = f(xs[i])
32 end
33 return rs
34end
35
36function fun.filter(xs, f)
37 local rs = {}
38 for i = 1, #xs do
39 local v = xs[i]
40 if f(v) then
41 rs[#rs + 1] = v
42 end
43 end
44 return rs
45end
46
47function fun.reverse_in(t)
48 for i = 1, math.floor(#t / 2) do
49 local m, n = i, #t - i + 1
50 local a, b = t[m], t[n]
51 t[m] = b
52 t[n] = a
53 end
54 return t
55end
56
57function fun.sort_in(t, f)
58 table.sort(t, f)
59 return t
60end
61
62function fun.flip(f)
63 return function(a, b)
64 return f(b, a)
65 end
66end
67
68function fun.find(xs, f)
69 if type(xs) == "table" then
70 for _, x in ipairs(xs) do
71 local r = f(x)
72 if r then
73 return r
74 end
75 end
76 else
77 for x in xs do
78 local r = f(x)
79 if r then
80 return r
81 end
82 end
83 end
84end
85
86
87function fun.partial(f, ...)
88 local n = select("#", ...)
89 if n == 1 then
90 local a = ...
91 return function(...)
92 return f(a, ...)
93 end
94 elseif n == 2 then
95 local a, b = ...
96 return function(...)
97 return f(a, b, ...)
98 end
99 else
100 local pargs = { n = n, ... }
101 return function(...)
102 local m = select("#", ...)
103 local fargs = { ... }
104 local args = {}
105 for i = 1, n do
106 args[i] = pargs[i]
107 end
108 for i = 1, m do
109 args[i + n] = fargs[i]
110 end
111 return f(_tl_table_unpack(args, 1, n + m))
112 end
113 end
114end
115
116function fun.memoize(fn)
117 local memory = setmetatable({}, { __mode = "k" })
118 local errors = setmetatable({}, { __mode = "k" })
119 local NIL = {}
120 return function(a)
121 if memory[a] then
122 if memory[a] == NIL then
123 return nil, errors[a]
124 end
125 return memory[a]
126 end
127 local ret1, ret2 = fn(a)
128 if ret1 then
129 memory[a] = ret1
130 else
131 memory[a] = NIL
132 errors[a] = ret2
133 end
134 return ret1, ret2
135 end
136end
137
138return fun
diff --git a/src/luarocks/loader.lua b/src/luarocks/loader.lua
new file mode 100644
index 00000000..ac7bb5fb
--- /dev/null
+++ b/src/luarocks/loader.lua
@@ -0,0 +1,333 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local debug = _tl_compat and _tl_compat.debug or debug; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2
3
4
5
6
7
8local loaders = package.loaders or package.searchers
9local require, ipairs, table, type, next, tostring, error =
10require, ipairs, table, type, next, tostring, error
11
12local loader = {}
13
14
15
16
17local is_clean = not package.loaded["luarocks.core.cfg"]
18
19
20local cfg = require("luarocks.core.cfg")
21local cfg_ok, _err = cfg.init()
22if cfg_ok then
23 cfg.init_package_paths()
24end
25
26local path = require("luarocks.core.path")
27local manif = require("luarocks.core.manif")
28local vers = require("luarocks.core.vers")
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54local temporary_global = false
55local status, luarocks_value = pcall(function()
56 return luarocks
57end)
58if status and luarocks_value then
59
60
61
62 luarocks.loader = loader
63else
64
65
66
67
68
69 local info = debug and debug.getinfo(2, "nS")
70 if info and info.what == "C" and not info.name then
71 luarocks = { loader = loader }
72 temporary_global = true
73
74
75 end
76end
77
78
79
80
81
82loader.context = {}
83
84
85
86
87
88
89function loader.add_context(name, version)
90 if temporary_global then
91
92
93 luarocks = nil
94 temporary_global = false
95 end
96
97 local tree_manifests = manif.load_rocks_tree_manifests()
98 if not tree_manifests then
99 return
100 end
101
102 manif.scan_dependencies(name, version, tree_manifests, loader.context)
103end
104
105
106
107
108
109
110
111local function sort_versions(a, b)
112 return a.version > b.version
113end
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130local function call_other_loaders(module, name, version, module_name)
131 for _, a_loader in ipairs(loaders) do
132 if a_loader ~= loader.luarocks_loader then
133 local results = { a_loader(module_name) }
134 local f = results[1]
135 if type(f) == "function" then
136 if #results == 2 then
137 return f, results[2]
138 else
139 return f
140 end
141 end
142 end
143 end
144 return "Failed loading module " .. module .. " in LuaRocks rock " .. name .. " " .. version
145end
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161local function add_providers(providers, entries, tree, module, filter_name)
162 for i, entry in ipairs(entries) do
163 local name, version = entry:match("^([^/]*)/(.*)$")
164
165 local file_name = tree.manifest.repository[name][version][1].modules[module]
166 if type(file_name) ~= "string" then
167 error("Invalid data in manifest file for module " .. tostring(module) .. " (invalid data for " .. tostring(name) .. " " .. tostring(version) .. ")")
168 end
169
170 file_name = filter_name(file_name, name, version, tree.tree, i)
171
172 if loader.context[name] == version then
173 return name, version, file_name
174 end
175
176 table.insert(providers, {
177 name = name,
178 version = vers.parse_version(version),
179 module_name = file_name,
180 tree = tree,
181 })
182 end
183end
184
185
186
187
188
189
190
191
192
193
194
195
196
197local function select_module(module, filter_name)
198
199 local tree_manifests = manif.load_rocks_tree_manifests()
200 if not tree_manifests then
201 return nil
202 end
203
204 local providers = {}
205 local initmodule
206 for _, tree in ipairs(tree_manifests) do
207 local entries = tree.manifest.modules[module]
208 if entries then
209 local n, v, f = add_providers(providers, entries, tree, module, filter_name)
210 if n then
211 return n, v, f
212 end
213 else
214 initmodule = initmodule or module .. ".init"
215 entries = tree.manifest.modules[initmodule]
216 if entries then
217 local n, v, f = add_providers(providers, entries, tree, initmodule, filter_name)
218 if n then
219 return n, v, f
220 end
221 end
222 end
223 end
224
225 if next(providers) then
226 table.sort(providers, sort_versions)
227 local first = providers[1]
228 return first.name, first.version.string, first.module_name
229 end
230end
231
232
233
234
235
236
237
238
239
240
241
242
243local function filter_module_name(file_name, name, version, _tree, i)
244 if i > 1 then
245 file_name = path.versioned_name(file_name, "", name, version)
246 end
247 return path.path_to_module(file_name)
248end
249
250
251
252
253
254
255
256
257
258local function pick_module(module)
259 return select_module(module, filter_module_name)
260end
261
262
263
264
265
266
267
268
269
270
271
272
273
274function loader.which(module, where)
275 where = where or "l"
276 if where:match("l") then
277 local rock_name, rock_version, file_name = select_module(module, path.which_i)
278 if rock_name then
279 local fd = io.open(file_name)
280 if fd then
281 fd:close()
282 return file_name, rock_name, rock_version, "l"
283 end
284 end
285 end
286 if where:match("p") then
287 local modpath = module:gsub("%.", "/")
288 for _, v in ipairs({ package.path, package.cpath }) do
289 for p in v:gmatch("([^;]+)") do
290 local file_name = p:gsub("%?", modpath)
291 local fd = io.open(file_name)
292 if fd then
293 fd:close()
294 return file_name, v, nil, "p"
295 end
296 end
297 end
298 end
299end
300
301
302
303
304
305
306
307
308
309
310
311
312
313function loader.luarocks_loader(module)
314 local name, version, module_name = pick_module(module)
315 if not name then
316 return "No LuaRocks module found for " .. module
317 else
318 loader.add_context(name, version)
319 return call_other_loaders(module, name, version, module_name)
320 end
321end
322
323table.insert(loaders, 1, loader.luarocks_loader)
324
325if is_clean then
326 for modname, _ in pairs(package.loaded) do
327 if modname:match("^luarocks%.") then
328 package.loaded[modname] = nil
329 end
330 end
331end
332
333return loader
diff --git a/src/luarocks/manif.lua b/src/luarocks/manif.lua
new file mode 100644
index 00000000..27144894
--- /dev/null
+++ b/src/luarocks/manif.lua
@@ -0,0 +1,229 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string
2
3
4
5local manif = {}
6
7
8
9
10
11
12
13
14
15local core = require("luarocks.core.manif")
16local persist = require("luarocks.persist")
17local fetch = require("luarocks.fetch")
18local dir = require("luarocks.dir")
19local fs = require("luarocks.fs")
20local cfg = require("luarocks.core.cfg")
21local path = require("luarocks.path")
22local util = require("luarocks.util")
23local queries = require("luarocks.queries")
24local type_manifest = require("luarocks.type.manifest")
25
26
27
28
29
30
31manif.cache_manifest = core.cache_manifest
32manif.load_rocks_tree_manifests = core.load_rocks_tree_manifests
33manif.scan_dependencies = core.scan_dependencies
34
35manif.rock_manifest_cache = {}
36
37local function check_manifest(repo_url, manifest, globals)
38 local ok, err = type_manifest.check(manifest, globals)
39 if not ok then
40 core.cache_manifest(repo_url, cfg.lua_version, nil)
41 return nil, "Error checking manifest: " .. err, "type"
42 end
43 return manifest
44end
45
46local postprocess_dependencies
47do
48 local postprocess_check = setmetatable({}, { __mode = "k" })
49 postprocess_dependencies = function(manifest)
50 if postprocess_check[manifest] then
51 return
52 end
53 if manifest.dependencies then
54 for _, versions in pairs(manifest.dependencies) do
55 for _, entries in pairs(versions) do
56 for k, v in ipairs(entries) do
57 entries[k] = queries.from_persisted_table(v)
58 end
59 end
60 end
61 end
62 postprocess_check[manifest] = true
63 end
64end
65
66function manif.load_rock_manifest(name, version, root)
67 assert(not name:match("/"))
68
69 local name_version = name .. "/" .. version
70 if manif.rock_manifest_cache[name_version] then
71 return manif.rock_manifest_cache[name_version].rock_manifest
72 end
73 local pathname = path.rock_manifest_file(name, version, root)
74 local rock_manifest = persist.load_into_table(pathname)
75 if not rock_manifest then
76 return nil, "rock_manifest file not found for " .. name .. " " .. version .. " - not a LuaRocks tree?"
77 end
78 manif.rock_manifest_cache[name_version] = rock_manifest
79 return rock_manifest.rock_manifest
80end
81
82
83
84
85
86
87
88
89
90
91function manif.load_manifest(repo_url, lua_version, versioned_only)
92 lua_version = lua_version or cfg.lua_version
93
94 local cached_manifest = core.get_cached_manifest(repo_url, lua_version)
95 if cached_manifest then
96 postprocess_dependencies(cached_manifest)
97 return cached_manifest
98 end
99
100 local filenames = {
101 "manifest-" .. lua_version .. ".zip",
102 "manifest-" .. lua_version,
103 not versioned_only and "manifest" or nil,
104 }
105
106 local protocol, repodir = dir.split_url(repo_url)
107 local pathname, from_cache
108 if protocol == "file" then
109 for _, filename in ipairs(filenames) do
110 pathname = dir.path(repodir, filename)
111 if fs.exists(pathname) then
112 break
113 end
114 end
115 else
116 local err, errcode
117 for _, filename in ipairs(filenames) do
118 pathname, err, errcode, from_cache = fetch.fetch_caching(dir.path(repo_url, filename), "no_mirror")
119 if pathname then
120 break
121 end
122 end
123 if not pathname then
124 return nil, err, errcode
125 end
126 end
127 if pathname:match(".*%.zip$") then
128 pathname = fs.absolute_name(pathname)
129 local nozip = pathname:match("(.*)%.zip$")
130 if not from_cache then
131 local dirname = dir.dir_name(pathname)
132 fs.change_dir(dirname)
133 fs.delete(nozip)
134 local ok, err = fs.unzip(pathname)
135 fs.pop_dir()
136 if not ok then
137 fs.delete(pathname)
138 fs.delete(pathname .. ".timestamp")
139 return nil, "Failed extracting manifest file: " .. err
140 end
141 end
142 pathname = nozip
143 end
144 local manifest, err, errcode = core.manifest_loader(pathname, repo_url, lua_version)
145 if not manifest and type(err) == "string" then
146 return nil, err, errcode
147 end
148
149 postprocess_dependencies(manifest)
150 return check_manifest(repo_url, manifest, err)
151end
152
153
154
155
156
157function manif.get_provided_item(deploy_type, file_path)
158 local item_type = deploy_type == "bin" and "command" or "module"
159 local item_name = item_type == "command" and file_path or path.path_to_module(file_path)
160 return item_type, item_name
161end
162
163local function get_providers(item_type, item_name, repo)
164 local rocks_dir = path.rocks_dir(repo or cfg.root_dir)
165 local manifest = manif.load_manifest(rocks_dir)
166 return manifest and (manifest)[item_type .. "s"][item_name]
167end
168
169
170
171
172
173
174
175
176function manif.get_current_provider(item_type, item_name, repo)
177 local providers = get_providers(item_type, item_name, repo)
178 if providers then
179 return providers[1]:match("([^/]*)/([^/]*)")
180 end
181end
182
183function manif.get_next_provider(item_type, item_name, repo)
184 local providers = get_providers(item_type, item_name, repo)
185 if providers and providers[2] then
186 return providers[2]:match("([^/]*)/([^/]*)")
187 end
188end
189
190
191
192
193
194
195
196
197
198function manif.get_versions(dep, deps_mode)
199
200 local name = dep.name
201 local namespace = dep.namespace
202
203 local version_set = {}
204 path.map_trees(deps_mode, function(tree)
205 local manifest = manif.load_manifest(path.rocks_dir(tree))
206
207 if manifest and manifest.repository[name] then
208 for version in pairs(manifest.repository[name]) do
209 if dep.namespace then
210 local ns_file = path.rock_namespace_file(name, version, tree)
211 local fd = io.open(ns_file, "r")
212 if fd then
213 local ns = fd:read("*a")
214 fd:close()
215 if ns == namespace then
216 version_set[version] = tree
217 end
218 end
219 else
220 version_set[version] = tree
221 end
222 end
223 end
224 end)
225
226 return util.keys(version_set), version_set
227end
228
229return manif
diff --git a/src/luarocks/manif/writer.lua b/src/luarocks/manif/writer.lua
new file mode 100644
index 00000000..04becbac
--- /dev/null
+++ b/src/luarocks/manif/writer.lua
@@ -0,0 +1,459 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2local writer = {}
3
4
5local cfg = require("luarocks.core.cfg")
6local search = require("luarocks.search")
7local repos = require("luarocks.repos")
8local deps = require("luarocks.deps")
9local vers = require("luarocks.core.vers")
10local fs = require("luarocks.fs")
11local util = require("luarocks.util")
12local dir = require("luarocks.dir")
13local fetch = require("luarocks.fetch")
14local path = require("luarocks.path")
15local persist = require("luarocks.persist")
16local manif = require("luarocks.manif")
17local queries = require("luarocks.queries")
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37local function store_package_items(storage, name, version, items)
38 assert(not name:match("/"))
39
40 local package_identifier = name .. "/" .. version
41
42 for item_name, _ in pairs(items) do
43 if not storage[item_name] then
44 storage[item_name] = {}
45 end
46
47 table.insert(storage[item_name], package_identifier)
48 end
49end
50
51
52
53
54
55
56
57
58local function remove_package_items(storage, name, version, items)
59 assert(not name:match("/"))
60
61 local package_identifier = name .. "/" .. version
62
63 for item_name, _path in pairs(items) do
64 local key = item_name
65 local all_identifiers = storage[key]
66 if not all_identifiers then
67 key = key .. ".init"
68 all_identifiers = storage[key]
69 end
70
71 if all_identifiers then
72 for i, identifier in ipairs(all_identifiers) do
73 if identifier == package_identifier then
74 table.remove(all_identifiers, i)
75 break
76 end
77 end
78
79 if #all_identifiers == 0 then
80 storage[key] = nil
81 end
82 else
83 util.warning("Cannot find entry for " .. item_name .. " in manifest -- corrupted manifest?")
84 end
85 end
86end
87
88
89
90
91
92
93
94
95
96local function update_dependencies(manifest, deps_mode)
97
98 if not manifest.dependencies then manifest.dependencies = {} end
99 local mdeps = manifest.dependencies
100
101 for pkg, versions in pairs(manifest.repository) do
102 for version, repositories in pairs(versions) do
103 for _, repo in ipairs(repositories) do
104 if repo.arch == "installed" then
105 local rd = {}
106 repo.dependencies = rd
107 deps.scan_deps(rd, mdeps, pkg, version, deps_mode)
108 rd[pkg] = nil
109 end
110 end
111 end
112 end
113end
114
115
116
117
118
119
120
121
122
123
124local function sort_pkgs(a, b)
125 local na, va = a:match("(.*)/(.*)$")
126 local nb, vb = b:match("(.*)/(.*)$")
127
128 return (na == nb) and vers.compare_versions(va, vb) or na < nb
129end
130
131
132
133
134local function sort_package_matching_table(tbl)
135
136 if next(tbl) then
137 for item, pkgs in pairs(tbl) do
138 if #pkgs > 1 then
139 table.sort(pkgs, sort_pkgs)
140
141 local prev = nil
142 local i = 1
143 while pkgs[i] do
144 local curr = pkgs[i]
145 if curr == prev then
146 table.remove(pkgs, i)
147 else
148 prev = curr
149 i = i + 1
150 end
151 end
152 end
153 end
154 end
155end
156
157
158
159
160
161
162
163local function filter_by_lua_version(manifest, lua_version_str, repodir, cache)
164
165 cache = cache or {}
166 local lua_version = vers.parse_version(lua_version_str)
167 for pkg, versions in pairs(manifest.repository) do
168 local to_remove = {}
169 for version, repositories in pairs(versions) do
170 for _, repo in ipairs(repositories) do
171 if repo.arch == "rockspec" then
172 local pathname = dir.path(repodir, pkg .. "-" .. version .. ".rockspec")
173 local rockspec = cache[pathname]
174 local err
175 if not rockspec then
176 rockspec, err = fetch.load_local_rockspec(pathname, true)
177 end
178 if rockspec then
179 cache[pathname] = rockspec
180 for _, dep in ipairs(rockspec.dependencies.queries) do
181 if dep.name == "lua" then
182 if not vers.match_constraints(lua_version, dep.constraints) then
183 table.insert(to_remove, version)
184 end
185 break
186 end
187 end
188 else
189 util.printerr("Error loading rockspec for " .. pkg .. " " .. version .. ": " .. err)
190 end
191 end
192 end
193 end
194 if next(to_remove) then
195 for _, incompat in ipairs(to_remove) do
196 versions[incompat] = nil
197 end
198 if not next(versions) then
199 manifest.repository[pkg] = nil
200 end
201 end
202 end
203end
204
205
206
207
208
209
210local function store_results(results, manifest)
211
212 for name, versions in pairs(results) do
213 local pkgtable = manifest.repository[name] or {}
214 for version, entries in pairs(versions) do
215 local versiontable = {}
216 for _, entry in ipairs(entries) do
217 local entrytable = {}
218 entrytable.arch = entry.arch
219 if entry.arch == "installed" then
220 local rock_manifest, err = manif.load_rock_manifest(name, version)
221 if not rock_manifest then return nil, err end
222
223 entrytable.modules = repos.package_modules(name, version)
224 store_package_items(manifest.modules, name, version, entrytable.modules)
225 entrytable.commands = repos.package_commands(name, version)
226 store_package_items(manifest.commands, name, version, entrytable.commands)
227 end
228 table.insert(versiontable, entrytable)
229 end
230 pkgtable[version] = versiontable
231 end
232 manifest.repository[name] = pkgtable
233 end
234 sort_package_matching_table(manifest.modules)
235 sort_package_matching_table(manifest.commands)
236 return true
237end
238
239
240
241
242
243
244
245local function save_table(where, name, tbl)
246 assert(not name:match("/"))
247
248 local filename = dir.path(where, name)
249 local ok, err = persist.save_from_table(filename .. ".tmp", tbl)
250 if ok then
251 ok, err = fs.replace_file(filename, filename .. ".tmp")
252 end
253 return ok, err
254end
255
256function writer.make_rock_manifest(name, version)
257 local install_dir = path.install_dir(name, version)
258 local tree = {}
259 for _, file in ipairs(fs.find(install_dir)) do
260 local full_path = dir.path(install_dir, file)
261 local walk = tree
262 local last
263 local last_name
264 local next
265 for filename in file:gmatch("[^\\/]+") do
266 next = walk[filename]
267 if not next then
268 next = {}
269 walk[filename] = next
270 end
271 last = walk
272 last_name = filename
273 assert(type(next) == "table")
274 walk = next
275 end
276 if fs.is_file(full_path) then
277
278 local sum, err = fs.get_md5(full_path)
279 if not sum then
280 return nil, "Failed producing checksum: " .. tostring(err)
281 end
282 last[last_name] = sum
283 end
284 end
285 local rock_manifest = { rock_manifest = tree }
286 manif.rock_manifest_cache[name .. "/" .. version] = rock_manifest
287 save_table(install_dir, "rock_manifest", rock_manifest)
288 return true
289end
290
291
292
293
294
295
296
297function writer.make_namespace_file(name, version, namespace)
298 assert(not name:match("/"))
299 if not namespace then
300 return true
301 end
302 local fd, err = io.open(path.rock_namespace_file(name, version), "w")
303 if not fd then
304 return nil, err
305 end
306 local ok, err = fd:write(namespace)
307 if not ok then
308 return nil, err
309 end
310 fd:close()
311 return true
312end
313
314
315
316
317
318
319
320
321
322
323
324function writer.make_manifest(repo, deps_mode, remote)
325
326 if deps_mode == "none" then deps_mode = cfg.deps_mode end
327
328 if not fs.is_dir(repo) then
329 return nil, "Cannot access repository at " .. repo
330 end
331
332 local query = queries.all("any")
333 local results = search.disk_search(repo, query)
334 local manifest = { repository = {}, modules = {}, commands = {} }
335
336 manif.cache_manifest(repo, nil, manifest)
337
338 local ok, err = store_results(results, manifest)
339 if not ok then return nil, err end
340
341 if remote then
342 local cache = {}
343 for luaver in util.lua_versions() do
344 local vmanifest = { repository = {}, modules = {}, commands = {} }
345 ok, err = store_results(results, vmanifest)
346 filter_by_lua_version(vmanifest, luaver, repo, cache)
347 if not cfg.no_manifest then
348 save_table(repo, "manifest-" .. luaver, vmanifest)
349 end
350 end
351 else
352 update_dependencies(manifest, deps_mode)
353 end
354
355 if cfg.no_manifest then
356
357 return true
358 end
359 return save_table(repo, "manifest", manifest)
360end
361
362
363
364
365
366
367
368
369
370
371
372
373function writer.add_to_manifest(name, version, repo, deps_mode)
374 assert(not name:match("/"))
375 local rocks_dir = path.rocks_dir(repo or cfg.root_dir)
376
377 if deps_mode == "none" then deps_mode = cfg.deps_mode end
378
379 local manifest, err = manif.load_manifest(rocks_dir)
380 if not manifest then
381 util.printerr("No existing manifest. Attempting to rebuild...")
382
383
384
385 return writer.make_manifest(rocks_dir, deps_mode)
386 end
387
388 local results = { [name] = { [version] = { { arch = "installed", repo = rocks_dir } } } }
389
390 local ok
391 ok, err = store_results(results, manifest)
392 if not ok then return nil, err end
393
394 update_dependencies(manifest, deps_mode)
395
396 if cfg.no_manifest then
397 return true
398 end
399 return save_table(rocks_dir, "manifest", manifest)
400end
401
402
403
404
405
406
407
408
409
410
411
412
413function writer.remove_from_manifest(name, version, repo, deps_mode)
414 assert(not name:match("/"))
415 local rocks_dir = path.rocks_dir(repo or cfg.root_dir)
416
417 if deps_mode == "none" then deps_mode = cfg.deps_mode end
418
419 local manifest, err = manif.load_manifest(rocks_dir)
420 if not manifest then
421 util.printerr("No existing manifest. Attempting to rebuild...")
422
423
424 return writer.make_manifest(rocks_dir, deps_mode)
425 end
426
427 local package_entry = manifest.repository[name]
428 if package_entry == nil or package_entry[version] == nil then
429
430 return true
431 end
432
433 local version_entry = package_entry[version][1]
434 if not version_entry then
435
436 return writer.make_manifest(rocks_dir, deps_mode)
437 end
438
439 remove_package_items(manifest.modules, name, version, version_entry.modules)
440 remove_package_items(manifest.commands, name, version, version_entry.commands)
441
442 package_entry[version] = nil
443 manifest.dependencies[name][version] = nil
444
445 if not next(package_entry) then
446
447 manifest.repository[name] = nil
448 manifest.dependencies[name] = nil
449 end
450
451 update_dependencies(manifest, deps_mode)
452
453 if cfg.no_manifest then
454 return true
455 end
456 return save_table(rocks_dir, "manifest", manifest)
457end
458
459return writer
diff --git a/src/luarocks/pack.lua b/src/luarocks/pack.lua
new file mode 100644
index 00000000..8a4df1a0
--- /dev/null
+++ b/src/luarocks/pack.lua
@@ -0,0 +1,188 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack
2
3local pack = {}
4
5
6local queries = require("luarocks.queries")
7local path = require("luarocks.path")
8local repos = require("luarocks.repos")
9local fetch = require("luarocks.fetch")
10local fs = require("luarocks.fs")
11local cfg = require("luarocks.core.cfg")
12local util = require("luarocks.util")
13local dir = require("luarocks.dir")
14local manif = require("luarocks.manif")
15local search = require("luarocks.search")
16local signing = require("luarocks.signing")
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31function pack.pack_source_rock(rockspec_file)
32
33 local rockspec, errload = fetch.load_rockspec(rockspec_file)
34 if errload then
35 return nil, "Error loading rockspec: " .. errload
36 end
37 rockspec_file = rockspec.local_abs_filename
38
39 local name_version = rockspec.name .. "-" .. rockspec.version
40 local rock_file = fs.absolute_name(name_version .. ".src.rock")
41
42 local temp_dir, err = fs.make_temp_dir("pack-" .. name_version)
43 if not temp_dir then
44 return nil, "Failed creating temporary directory: " .. err
45 end
46 util.schedule_function(fs.delete, temp_dir)
47
48 local source_file, source_dir = fetch.fetch_sources(rockspec, true, temp_dir)
49 if not source_file then
50 return nil, source_dir
51 end
52 local ok, errchange = fs.change_dir(source_dir)
53 if not ok then return nil, errchange end
54
55 fs.delete(rock_file)
56 fs.copy(rockspec_file, source_dir, "read")
57 ok, err = fs.zip(rock_file, dir.base_name(rockspec_file), dir.base_name(source_file))
58 if not ok then
59 return nil, "Failed packing " .. rock_file .. " - " .. err
60 end
61 fs.pop_dir()
62
63 return rock_file
64end
65
66local function copy_back_files(name, version, file_tree, deploy_dir, pack_dir, perms)
67 local ok, err = fs.make_dir(pack_dir)
68 if not ok then return nil, err end
69 for file, sub in pairs(file_tree) do
70 local source = dir.path(deploy_dir, file)
71 local target = dir.path(pack_dir, file)
72 if type(sub) == "table" then
73 ok, err = copy_back_files(name, version, sub, source, target)
74 if not ok then return nil, err end
75 else
76 local versioned = path.versioned_name(source, deploy_dir, name, version)
77 if fs.exists(versioned) then
78 fs.copy(versioned, target, perms)
79 else
80 fs.copy(source, target, perms)
81 end
82 end
83 end
84 return true
85end
86
87
88
89
90
91
92function pack.pack_installed_rock(query, tree)
93
94 local name, version, repo, repo_url = search.pick_installed_rock(query, tree)
95 if not name then
96 return nil, version
97 end
98
99 local root = path.root_from_rocks_dir(repo_url)
100 local prefix = path.install_dir(name, version, root)
101 if not fs.exists(prefix) then
102 return nil, "'" .. name .. " " .. version .. "' does not seem to be an installed rock."
103 end
104
105 local rock_manifest, err = manif.load_rock_manifest(name, version, root)
106 if not rock_manifest then return nil, err end
107
108 local name_version = name .. "-" .. version
109 local rock_file = fs.absolute_name(name_version .. "." .. cfg.arch .. ".rock")
110
111 local temp_dir = fs.make_temp_dir("pack")
112 fs.copy_contents(prefix, temp_dir)
113
114 local is_binary = false
115 if rock_manifest.lib then
116 local ok, err = copy_back_files(name, version, (rock_manifest.lib), path.deploy_lib_dir(repo), dir.path(temp_dir, "lib"), "exec")
117 if not ok then return nil, "Failed copying back files: " .. err end
118 is_binary = true
119 end
120 if rock_manifest.lua then
121 local ok, err = copy_back_files(name, version, (rock_manifest.lua), path.deploy_lua_dir(repo), dir.path(temp_dir, "lua"), "read")
122 if not ok then return nil, "Failed copying back files: " .. err end
123 end
124
125 local ok, err = fs.change_dir(temp_dir)
126 if not ok then return nil, err end
127 if not is_binary and not repos.has_binaries(name, version) then
128 rock_file = rock_file:gsub("%." .. cfg.arch:gsub("%-", "%%-") .. "%.", ".all.")
129 end
130 fs.delete(rock_file)
131 ok, err = fs.zip(rock_file, _tl_table_unpack(fs.list_dir()))
132 if not ok then
133 return nil, "Failed packing " .. rock_file .. " - " .. err
134 end
135 fs.pop_dir()
136 fs.delete(temp_dir)
137 return rock_file
138end
139
140function pack.report_and_sign_local_file(file, err, sign)
141 if err then
142 return nil, err
143 end
144 local sigfile
145 if sign then
146 sigfile, err = signing.sign_file(file)
147 util.printout()
148 end
149 util.printout("Packed: " .. file)
150 if sigfile then
151 util.printout("Signature stored in: " .. sigfile)
152 end
153 if err then
154 return nil, err
155 end
156 return true
157end
158
159function pack.pack_binary_rock(name, namespace, version, sign, cmd)
160
161
162
163
164
165
166
167
168 local temp_dir, err = fs.make_temp_dir("luarocks-build-pack-" .. dir.base_name(name))
169 if not temp_dir then
170 return nil, "Failed creating temporary directory: " .. err
171 end
172 util.schedule_function(fs.delete, temp_dir)
173
174 path.use_tree(temp_dir)
175 local ok, err = cmd()
176 if not ok then
177 return nil, err
178 end
179 local rname, rversion = path.parse_name(name)
180 if not rname then
181 rname, rversion = name, version
182 end
183 local query = queries.new(rname, namespace, rversion)
184 local file, err = pack.pack_installed_rock(query, temp_dir)
185 return pack.report_and_sign_local_file(file, err, sign)
186end
187
188return pack
diff --git a/src/luarocks/path.lua b/src/luarocks/path.lua
new file mode 100644
index 00000000..65c1a7d2
--- /dev/null
+++ b/src/luarocks/path.lua
@@ -0,0 +1,254 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local io = _tl_compat and _tl_compat.io or io; local package = _tl_compat and _tl_compat.package or package; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2
3
4
5
6local cfg = require("luarocks.core.cfg")
7local core = require("luarocks.core.path")
8local dir = require("luarocks.dir")
9local util = require("luarocks.core.util")
10
11
12
13local path = {}
14
15
16
17
18
19
20
21
22
23path.rocks_dir = core.rocks_dir
24path.versioned_name = core.versioned_name
25path.path_to_module = core.path_to_module
26path.deploy_lua_dir = core.deploy_lua_dir
27path.deploy_lib_dir = core.deploy_lib_dir
28path.map_trees = core.map_trees
29path.rocks_tree_to_string = core.rocks_tree_to_string
30
31function path.root_dir(tree)
32 if type(tree) == "string" then
33 return tree
34 else
35 return tree.root
36 end
37end
38
39
40
41
42function path.rockspec_name_from_rock(rock_name)
43 local base_name = dir.base_name(rock_name)
44 return base_name:match("(.*)%.[^.]*.rock") .. ".rockspec"
45end
46
47function path.root_from_rocks_dir(rocks_dir)
48 return rocks_dir:match("(.*)" .. util.matchquote(cfg.rocks_subdir) .. ".*$")
49end
50
51function path.deploy_bin_dir(tree)
52 return dir.path(path.root_dir(tree), "bin")
53end
54
55function path.manifest_file(tree)
56 return dir.path(path.rocks_dir(tree), "manifest")
57end
58
59
60
61
62
63
64function path.versions_dir(name, tree)
65 assert(not name:match("/"))
66 return dir.path(path.rocks_dir(tree), name)
67end
68
69
70
71
72
73
74
75function path.install_dir(name, version, tree)
76 assert(not name:match("/"))
77 return dir.path(path.rocks_dir(tree), name, version)
78end
79
80
81
82
83
84
85
86function path.rockspec_file(name, version, tree)
87 assert(not name:match("/"))
88 return dir.path(path.rocks_dir(tree), name, version, name .. "-" .. version .. ".rockspec")
89end
90
91
92
93
94
95
96
97function path.rock_manifest_file(name, version, tree)
98 assert(not name:match("/"))
99 return dir.path(path.rocks_dir(tree), name, version, "rock_manifest")
100end
101
102
103
104
105
106
107
108function path.rock_namespace_file(name, version, tree)
109 assert(not name:match("/"))
110 return dir.path(path.rocks_dir(tree), name, version, "rock_namespace")
111end
112
113
114
115
116
117
118
119function path.lib_dir(name, version, tree)
120 assert(not name:match("/"))
121 return dir.path(path.rocks_dir(tree), name, version, "lib")
122end
123
124
125
126
127
128
129
130function path.lua_dir(name, version, tree)
131 assert(not name:match("/"))
132 return dir.path(path.rocks_dir(tree), name, version, "lua")
133end
134
135
136
137
138
139
140
141function path.doc_dir(name, version, tree)
142 assert(not name:match("/"))
143 return dir.path(path.rocks_dir(tree), name, version, "doc")
144end
145
146
147
148
149
150
151
152function path.conf_dir(name, version, tree)
153 assert(not name:match("/"))
154 return dir.path(path.rocks_dir(tree), name, version, "conf")
155end
156
157
158
159
160
161
162
163
164function path.bin_dir(name, version, tree)
165 assert(not name:match("/"))
166 return dir.path(path.rocks_dir(tree), name, version, "bin")
167end
168
169
170
171
172
173
174function path.parse_name(file_name)
175 if file_name:match("%.rock$") then
176 return dir.base_name(file_name):match("(.*)-([^-]+-%d+)%.([^.]+)%.rock$")
177 else
178 return dir.base_name(file_name):match("(.*)-([^-]+-%d+)%.(rockspec)")
179 end
180end
181
182
183
184
185
186
187
188function path.make_url(pathname, name, version, arch)
189 assert(not name:match("/"))
190 local filename = name .. "-" .. version
191 if arch == "installed" then
192 filename = dir.path(name, version, filename .. ".rockspec")
193 elseif arch == "rockspec" then
194 filename = filename .. ".rockspec"
195 else
196 filename = filename .. "." .. arch .. ".rock"
197 end
198 return dir.path(pathname, filename)
199end
200
201
202
203
204
205function path.module_to_path(mod)
206 return (mod:gsub("[^.]*$", ""):gsub("%.", "/"))
207end
208
209function path.use_tree(tree)
210 cfg.root_dir = tree
211 cfg.rocks_dir = path.rocks_dir(tree)
212 cfg.deploy_bin_dir = path.deploy_bin_dir(tree)
213 cfg.deploy_lua_dir = path.deploy_lua_dir(tree)
214 cfg.deploy_lib_dir = path.deploy_lib_dir(tree)
215end
216
217function path.add_to_package_paths(tree)
218 package.path = dir.path(path.deploy_lua_dir(tree), "?.lua") .. ";" ..
219 dir.path(path.deploy_lua_dir(tree), "?/init.lua") .. ";" ..
220 package.path
221 package.cpath = dir.path(path.deploy_lib_dir(tree), "?." .. cfg.lib_extension) .. ";" ..
222 package.cpath
223end
224
225
226
227
228
229
230function path.read_namespace(name, version, tree)
231 assert(not name:match("/"))
232
233 local namespace
234 local fd = io.open(path.rock_namespace_file(name, version, tree), "r")
235 if fd then
236 namespace = fd:read("*a")
237 fd:close()
238 end
239 return namespace
240end
241
242function path.package_paths(deps_mode)
243 local lpaths = {}
244 local lcpaths = {}
245 path.map_trees(deps_mode, function(tree)
246 local root = path.root_dir(tree)
247 table.insert(lpaths, dir.path(root, cfg.lua_modules_path, "?.lua"))
248 table.insert(lpaths, dir.path(root, cfg.lua_modules_path, "?/init.lua"))
249 table.insert(lcpaths, dir.path(root, cfg.lib_modules_path, "?." .. cfg.lib_extension))
250 end)
251 return table.concat(lpaths, ";"), table.concat(lcpaths, ";")
252end
253
254return path
diff --git a/src/luarocks/persist.lua b/src/luarocks/persist.lua
new file mode 100644
index 00000000..34ea2a52
--- /dev/null
+++ b/src/luarocks/persist.lua
@@ -0,0 +1,275 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2
3
4local persist = {}
5
6
7
8
9
10
11
12
13
14local core = require("luarocks.core.persist")
15local util = require("luarocks.util")
16local dir = require("luarocks.dir")
17local fs = require("luarocks.fs")
18local cfg = require("luarocks.core.cfg")
19
20
21
22
23
24
25
26
27
28persist.run_file = core.run_file
29persist.load_into_table = core.load_into_table
30
31local write_table
32
33
34
35
36
37
38
39
40
41function persist.write_value(out, v, level, sub_order)
42 if type(v) == "table" then
43 level = level or 0
44 write_table(out, v, level + 1, sub_order)
45 elseif type(v) == "string" then
46 if v:match("[\r\n]") then
47 local open, close = "[[", "]]"
48 local equals = 0
49 local v_with_bracket = v .. "]"
50 while v_with_bracket:find(close, 1, true) do
51 equals = equals + 1
52 local eqs = ("="):rep(equals)
53 open, close = "[" .. eqs .. "[", "]" .. eqs .. "]"
54 end
55 out:write(open .. "\n" .. v .. close)
56 else
57 out:write(("%q"):format(v))
58 end
59 else
60 out:write(tostring(v))
61 end
62end
63
64local is_valid_plain_key
65do
66 local keywords = {
67 ["and"] = true,
68 ["break"] = true,
69 ["do"] = true,
70 ["else"] = true,
71 ["elseif"] = true,
72 ["end"] = true,
73 ["false"] = true,
74 ["for"] = true,
75 ["function"] = true,
76 ["goto"] = true,
77 ["if"] = true,
78 ["in"] = true,
79 ["local"] = true,
80 ["nil"] = true,
81 ["not"] = true,
82 ["or"] = true,
83 ["repeat"] = true,
84 ["return"] = true,
85 ["then"] = true,
86 ["true"] = true,
87 ["until"] = true,
88 ["while"] = true,
89 }
90 function is_valid_plain_key(k)
91 return k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") and
92 not keywords[k]
93 end
94end
95
96local function write_table_key_assignment(out, k, level)
97 if type(k) == "string" and is_valid_plain_key(k) then
98 out:write(k)
99 else
100 out:write("[")
101 persist.write_value(out, k, level)
102 out:write("]")
103 end
104
105 out:write(" = ")
106end
107
108
109
110
111
112
113
114
115write_table = function(out, tbl, level, sort_by)
116 out:write("{")
117 local sep = "\n"
118 local indentation = " "
119 local indent = true
120 local i = 1
121 for k, v, sub_order in util.sortedpairs(tbl, sort_by) do
122 out:write(sep)
123 if indent then
124 for _ = 1, level do out:write(indentation) end
125 end
126
127 if type(k) == "number" then
128 i = i + 1
129 else
130 write_table_key_assignment(out, k, level)
131 end
132
133 persist.write_value(out, v, level, sub_order)
134 if type(v) == "number" then
135 sep = ", "
136 indent = false
137 else
138 sep = ",\n"
139 indent = true
140 end
141 end
142 if sep ~= "\n" then
143 out:write("\n")
144 for _ = 1, level - 1 do out:write(indentation) end
145 end
146 out:write("}")
147end
148
149
150
151
152
153
154local function write_table_as_assignments(out, tbl, sort_by)
155 for k, v, sub_order in util.sortedpairs(tbl, sort_by) do
156 if not (type(k) == "string" and is_valid_plain_key(k)) then
157 return nil, "cannot store '" .. tostring(k) .. "' as a plain key."
158 end
159 out:write(k .. " = ")
160 persist.write_value(out, v, 0, sub_order)
161 out:write("\n")
162 end
163 return true
164end
165
166
167
168
169local function write_table_as_table(out, tbl)
170 out:write("return {\n")
171 for k, v, sub_order in util.sortedpairs(tbl) do
172 out:write(" ")
173 write_table_key_assignment(out, k, 1)
174 persist.write_value(out, v, 1, sub_order)
175 out:write(",\n")
176 end
177 out:write("}\n")
178end
179
180
181
182
183
184
185
186
187function persist.save_from_table_to_string(tbl, sort_by)
188 local out = { buffer = {} }
189 function out:write(data) table.insert(self.buffer, data) end
190 local ok, err = write_table_as_assignments(out, tbl, sort_by)
191 if not ok then
192 return nil, err
193 end
194 return table.concat(out.buffer)
195end
196
197
198
199
200
201
202
203
204
205
206function persist.save_from_table(filename, tbl, sort_by)
207 local prefix = dir.dir_name(filename)
208 fs.make_dir(prefix)
209 local out = io.open(filename, "w")
210 if not out then
211 return nil, "Cannot create file at " .. filename
212 end
213 local ok, err = write_table_as_assignments(out, tbl, sort_by)
214 out:close()
215 if not ok then
216 return nil, err
217 end
218 return true
219end
220
221
222
223
224
225
226
227
228
229function persist.save_as_module(filename, tbl)
230 local out = io.open(filename, "w")
231 if not out then
232 return nil, "Cannot create file at " .. filename
233 end
234 write_table_as_table(out, tbl)
235 out:close()
236 return true
237end
238
239function persist.load_config_file_if_basic(filename, config)
240 local env = {
241 home = config.home,
242 }
243 local result, _, errcode = persist.load_into_table(filename, env)
244 if errcode == "load" or errcode == "run" then
245
246 return nil, "Could not read existing config file " .. filename
247 end
248
249 local tbl
250 if errcode == "open" then
251
252 tbl = {}
253 else
254 tbl = result
255 tbl.home = nil
256 end
257
258 return tbl
259end
260
261function persist.save_default_lua_version(prefix, lua_version)
262 local ok, err_makedir = fs.make_dir(prefix)
263 if not ok then
264 return nil, err_makedir
265 end
266 local fd, err_open = io.open(dir.path(prefix, "default-lua-version.lua"), "w")
267 if not fd then
268 return nil, err_open
269 end
270 fd:write('return "' .. lua_version .. '"\n')
271 fd:close()
272 return true
273end
274
275return persist
diff --git a/src/luarocks/queries.lua b/src/luarocks/queries.lua
new file mode 100644
index 00000000..34b6d687
--- /dev/null
+++ b/src/luarocks/queries.lua
@@ -0,0 +1,211 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2local queries = {}
3
4
5local vers = require("luarocks.core.vers")
6local util = require("luarocks.util")
7local cfg = require("luarocks.core.cfg")
8
9local query = require("luarocks.core.types.query")
10
11
12
13
14
15local query_mt = {}
16
17query_mt.__index = query.Query
18
19
20query.Query.arch = {
21 src = true,
22 all = true,
23 rockspec = true,
24 installed = true,
25
26}
27
28
29query.Query.substring = false
30
31
32
33local function arch_to_table(input)
34 if type(input) == "table" then
35 return input
36 elseif type(input) == "string" then
37 local arch = {}
38 for a in input:gmatch("[%w_-]+") do
39 arch[a] = true
40 end
41 return arch
42 end
43end
44
45
46
47
48
49
50
51
52
53
54function queries.new(name, namespace, version, substring, arch, operator)
55
56 operator = operator or "=="
57
58 local self = {
59 name = name,
60 namespace = namespace,
61 constraints = {},
62 substring = substring,
63 arch = arch_to_table(arch),
64 }
65 if version then
66 table.insert(self.constraints, { op = operator, version = vers.parse_version(version) })
67 end
68
69 query.Query.arch[cfg.arch] = true
70 return setmetatable(self, query_mt)
71end
72
73
74
75function queries.all(arch)
76
77 return queries.new("", nil, nil, true, arch)
78end
79
80do
81 local parse_constraints
82 do
83 local parse_constraint
84 do
85 local operators = {
86 ["=="] = "==",
87 ["~="] = "~=",
88 [">"] = ">",
89 ["<"] = "<",
90 [">="] = ">=",
91 ["<="] = "<=",
92 ["~>"] = "~>",
93
94 [""] = "==",
95 ["="] = "==",
96 ["!="] = "~=",
97 }
98
99
100
101
102
103
104
105
106
107 parse_constraint = function(input)
108
109 local no_upgrade, op, versionstr, rest = input:match("^(@?)([<>=~!]*)%s*([%w%.%_%-]+)[%s,]*(.*)")
110 local _op = operators[op]
111 local version = vers.parse_version(versionstr)
112 if not _op then
113 return nil, "Encountered bad constraint operator: '" .. tostring(op) .. "' in '" .. input .. "'"
114 end
115 if not version then
116 return nil, "Could not parse version from constraint: '" .. input .. "'"
117 end
118 return { op = _op, version = version, no_upgrade = no_upgrade == "@" and true or nil }, rest
119 end
120 end
121
122
123
124
125
126
127
128
129
130 parse_constraints = function(input)
131
132 local constraints, oinput = {}, input
133 local constraint
134 while #input > 0 do
135 constraint, input = parse_constraint(input)
136 if constraint then
137 table.insert(constraints, constraint)
138 else
139 return nil, "Failed to parse constraint '" .. tostring(oinput) .. "' with error: " .. input
140 end
141 end
142 return constraints
143 end
144 end
145
146
147
148
149
150 function queries.from_dep_string(depstr)
151
152 local ns_name, rest = depstr:match("^%s*([a-zA-Z0-9%.%-%_]*/?[a-zA-Z0-9][a-zA-Z0-9%.%-%_]*)%s*([^/]*)")
153 if not ns_name then
154 return nil, "failed to extract dependency name from '" .. depstr .. "'"
155 end
156
157 ns_name = ns_name:lower()
158
159 local constraints, err = parse_constraints(rest)
160 if not constraints then
161 return nil, err
162 end
163
164 local name, namespace = util.split_namespace(ns_name)
165
166 local self = {
167 name = name,
168 namespace = namespace,
169 constraints = constraints,
170 }
171
172 query.Query.arch[cfg.arch] = true
173 return setmetatable(self, query_mt)
174 end
175end
176
177function queries.from_persisted_table(tbl)
178 query.Query.arch[cfg.arch] = true
179 return setmetatable(tbl, query_mt)
180end
181
182
183
184
185
186function query_mt.__tostring(self)
187 local out = {}
188 if self.namespace then
189 table.insert(out, self.namespace)
190 table.insert(out, "/")
191 end
192 table.insert(out, self.name)
193
194 if #self.constraints > 0 then
195 local pretty = {}
196 for _, c in ipairs(self.constraints) do
197 local v = tostring(c.version)
198 if c.op == "==" then
199 table.insert(pretty, v)
200 else
201 table.insert(pretty, c.op .. " " .. v)
202 end
203 end
204 table.insert(out, " ")
205 table.insert(out, table.concat(pretty, ", "))
206 end
207
208 return table.concat(out)
209end
210
211return queries
diff --git a/src/luarocks/remove.lua b/src/luarocks/remove.lua
new file mode 100644
index 00000000..72d3a1d8
--- /dev/null
+++ b/src/luarocks/remove.lua
@@ -0,0 +1,140 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local table = _tl_compat and _tl_compat.table or table
2local remove = {}
3
4
5local search = require("luarocks.search")
6local deps = require("luarocks.deps")
7local fetch = require("luarocks.fetch")
8local repos = require("luarocks.repos")
9local repo_writer = require("luarocks.repo_writer")
10local path = require("luarocks.path")
11local util = require("luarocks.util")
12local cfg = require("luarocks.core.cfg")
13local manif = require("luarocks.manif")
14local queries = require("luarocks.queries")
15
16
17
18
19
20
21
22
23
24local function check_dependents(name, versions, deps_mode)
25 local dependents = {}
26
27 local skip_set = {}
28 skip_set[name] = {}
29 for version, _ in pairs(versions) do
30 skip_set[name][version] = true
31 end
32
33 local local_rocks = {}
34 local query_all = queries.all()
35 search.local_manifest_search(local_rocks, cfg.rocks_dir, query_all)
36 local_rocks[name] = nil
37 for rock_name, rock_versions in pairs(local_rocks) do
38 for rock_version, _ in pairs(rock_versions) do
39 local rockspec, err = fetch.load_rockspec(path.rockspec_file(rock_name, rock_version))
40 if rockspec then
41 local _, missing = deps.match_deps(rockspec.dependencies.queries, rockspec.rocks_provided, deps_mode, skip_set)
42 if missing[name] then
43 table.insert(dependents, { name = rock_name, version = rock_version })
44 end
45 end
46 end
47 end
48
49 return dependents
50end
51
52
53
54
55
56
57
58
59local function delete_versions(name, versions, deps_mode)
60
61 for version, _ in pairs(versions) do
62 util.printout("Removing " .. name .. " " .. version .. "...")
63 local ok, err = repo_writer.delete_version(name, version, deps_mode)
64 if not ok then return nil, err end
65 end
66
67 return true
68end
69
70function remove.remove_search_results(results, name, deps_mode, force, fast)
71 local versions = results[name]
72
73 local version = next(versions)
74 local second = next(versions, version)
75
76 local dependents = {}
77 if not fast then
78 util.printout("Checking stability of dependencies in the absence of")
79 util.printout(name .. " " .. table.concat((util.keys(versions)), ", ") .. "...")
80 util.printout()
81 dependents = check_dependents(name, versions, deps_mode)
82 end
83
84 if #dependents > 0 then
85 if force or fast then
86 util.printerr("The following packages may be broken by this forced removal:")
87 for _, dependent in ipairs(dependents) do
88 util.printerr(dependent.name .. " " .. dependent.version)
89 end
90 util.printerr()
91 else
92 if not second then
93 util.printerr("Will not remove " .. name .. " " .. version .. ".")
94 util.printerr("Removing it would break dependencies for: ")
95 else
96 util.printerr("Will not remove installed versions of " .. name .. ".")
97 util.printerr("Removing them would break dependencies for: ")
98 end
99 for _, dependent in ipairs(dependents) do
100 util.printerr(dependent.name .. " " .. dependent.version)
101 end
102 util.printerr()
103 util.printerr("Use --force to force removal (warning: this may break modules).")
104 return nil, "Failed removing."
105 end
106 end
107
108 local ok, err = delete_versions(name, versions, deps_mode)
109 if not ok then return nil, err end
110
111 util.printout("Removal successful.")
112 return true
113end
114
115function remove.remove_other_versions(name, version, force, fast)
116 local results = {}
117 local query = queries.new(name, nil, version, false, nil, "~=")
118 search.local_manifest_search(results, cfg.rocks_dir, query)
119 local warn
120 if results[name] then
121 local ok, err = remove.remove_search_results(results, name, cfg.deps_mode, force, fast)
122 if not ok then
123 warn = err
124 end
125 end
126
127 if not fast then
128
129
130 local rock_manifest, load_err = manif.load_rock_manifest(name, version)
131 local ok, err = repos.check_everything_is_installed(name, version, rock_manifest, cfg.root_dir, false)
132 if not ok then
133 return nil, err
134 end
135 end
136
137 return true, nil, warn
138end
139
140return remove
diff --git a/src/luarocks/repo_writer.lua b/src/luarocks/repo_writer.lua
new file mode 100644
index 00000000..0ccb3e91
--- /dev/null
+++ b/src/luarocks/repo_writer.lua
@@ -0,0 +1,52 @@
1local repo_writer = {}
2
3
4local fs = require("luarocks.fs")
5local path = require("luarocks.path")
6local repos = require("luarocks.repos")
7local writer = require("luarocks.manif.writer")
8
9function repo_writer.deploy_files(name, version, wrap_bin_scripts, deps_mode, namespace)
10 local ok, err
11
12 if not fs.exists(path.rock_manifest_file(name, version)) then
13 ok, err = writer.make_rock_manifest(name, version)
14 if err then
15 return nil, err
16 end
17 end
18
19 if namespace then
20 ok, err = writer.make_namespace_file(name, version, namespace)
21 if not ok then
22 return nil, err
23 end
24 end
25
26 ok, err = repos.deploy_local_files(name, version, wrap_bin_scripts, deps_mode)
27 if not ok then
28 return nil, err
29 end
30
31 ok, err = writer.add_to_manifest(name, version, nil, deps_mode)
32 return ok, err
33end
34
35function repo_writer.delete_version(name, version, deps_mode, quick)
36 local ok, err, op = repos.delete_local_version(name, version, deps_mode, quick)
37
38 if op == "remove" then
39 local rok, rerr = writer.remove_from_manifest(name, version, nil, deps_mode)
40 if ok and not rok then
41 ok, err = rok, rerr
42 end
43 end
44
45 return ok, err
46end
47
48function repo_writer.refresh_manifest(rocks_dir)
49 return writer.make_manifest(rocks_dir, "one")
50end
51
52return repo_writer
diff --git a/src/luarocks/repos.lua b/src/luarocks/repos.lua
new file mode 100644
index 00000000..84c01815
--- /dev/null
+++ b/src/luarocks/repos.lua
@@ -0,0 +1,690 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local os = _tl_compat and _tl_compat.os or os; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2
3local repos = {Op = {}, Paths = {}, }
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19local fs = require("luarocks.fs")
20local path = require("luarocks.path")
21local cfg = require("luarocks.core.cfg")
22local util = require("luarocks.util")
23local dir = require("luarocks.dir")
24local manif = require("luarocks.manif")
25local vers = require("luarocks.core.vers")
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58local function get_installed_versions(name)
59 assert(not name:match("/"))
60
61 local dirs = fs.list_dir(path.versions_dir(name))
62 return (dirs and #dirs > 0) and dirs or nil
63end
64
65
66
67
68
69
70
71function repos.is_installed(name, version)
72 assert(not name:match("/"))
73
74 return fs.is_dir(path.install_dir(name, version))
75end
76
77function repos.recurse_rock_manifest_entry(entry, action)
78 if entry == nil then
79 return true
80 end
81
82 local function do_recurse_rock_manifest_entry(tree, parent_path)
83
84 for file, sub in pairs(tree) do
85 local sub_path = (parent_path and (parent_path .. "/") or "") .. file
86 local ok, err
87
88 if type(sub) == "table" then
89 ok, err = do_recurse_rock_manifest_entry(sub, sub_path)
90 else
91 ok, err = action(sub_path)
92 end
93
94 if err then return nil, err end
95 end
96 return true
97 end
98 return do_recurse_rock_manifest_entry(entry)
99end
100
101local function store_package_data(result, rock_manifest, deploy_type)
102 if rock_manifest[deploy_type] then
103 repos.recurse_rock_manifest_entry(rock_manifest[deploy_type], function(file_path)
104 local _, item_name = manif.get_provided_item(deploy_type, file_path)
105 result[item_name] = file_path
106 return true
107 end)
108 end
109end
110
111
112
113
114
115
116
117
118
119
120function repos.package_modules(name, version)
121 assert(not name:match("/"))
122
123 local result = {}
124 local rock_manifest = manif.load_rock_manifest(name, version)
125 if not rock_manifest then return result end
126 store_package_data(result, rock_manifest, "lib")
127 store_package_data(result, rock_manifest, "lua")
128 return result
129end
130
131
132
133
134
135
136
137
138
139
140function repos.package_commands(name, version)
141 assert(not name:match("/"))
142
143 local result = {}
144 local rock_manifest = manif.load_rock_manifest(name, version)
145 if not rock_manifest then return result end
146 store_package_data(result, rock_manifest, "bin")
147 return result
148end
149
150
151
152
153
154
155
156function repos.has_binaries(name, version)
157 assert(not name:match("/"))
158
159 local entries = manif.load_rock_manifest(name, version)
160 if not entries then
161 return false
162 end
163 local bin = entries["bin"]
164 if type(bin) == "table" then
165 for bin_name, md5 in pairs(bin) do
166
167 if fs.is_actual_binary(dir.path(cfg.deploy_bin_dir, bin_name)) then
168 return true
169 end
170 end
171 end
172 return false
173end
174
175function repos.run_hook(rockspec, hook_name)
176
177 local hooks = rockspec.hooks
178 if not hooks then
179 return true
180 end
181
182 if cfg.hooks_enabled == false then
183 return nil, "This rockspec contains hooks, which are blocked by the 'hooks_enabled' setting in your LuaRocks configuration."
184 end
185
186 if not hooks.substituted_variables then
187 util.variable_substitutions(hooks, rockspec.variables)
188 hooks.substituted_variables = true
189 end
190 local hook = (hooks)[hook_name]
191 if hook then
192 util.printout(hook)
193 if not fs.execute(hook) then
194 return nil, "Failed running " .. hook_name .. " hook."
195 end
196 end
197 return true
198end
199
200function repos.should_wrap_bin_scripts(rockspec)
201
202 if cfg.wrap_bin_scripts then
203 return cfg.wrap_bin_scripts
204 end
205 if rockspec.deploy and rockspec.deploy.wrap_bin_scripts == false then
206 return false
207 end
208 return true
209end
210
211local function find_suffixed(file, suffix)
212 local filenames = { file }
213 if suffix and suffix ~= "" then
214 table.insert(filenames, 1, file .. suffix)
215 end
216
217 for _, filename in ipairs(filenames) do
218 if fs.exists(filename) then
219 return filename
220 end
221 end
222
223 return nil, table.concat(filenames, ", ") .. " not found"
224end
225
226local function check_suffix(filename, suffix)
227 local suffixed_filename, err = find_suffixed(filename, suffix)
228 if not suffixed_filename then
229 return ""
230 end
231 return suffixed_filename:sub(#filename + 1)
232end
233
234
235
236
237
238
239
240local function get_deploy_paths(name, version, deploy_type, file_path, repo)
241
242 repo = repo or cfg.root_dir
243 local deploy_dir = (path)["deploy_" .. deploy_type .. "_dir"](repo)
244 local non_versioned = dir.path(deploy_dir, file_path)
245 local versioned = path.versioned_name(non_versioned, deploy_dir, name, version)
246 return { nv = non_versioned, v = versioned }
247end
248
249local function check_spot_if_available(name, version, deploy_type, file_path)
250 local item_type, item_name = manif.get_provided_item(deploy_type, file_path)
251 local cur_name, cur_version = manif.get_current_provider(item_type, item_name)
252
253
254
255
256 if not cur_name and deploy_type == "lua" and item_name:match("%.init$") then
257 cur_name, cur_version = manif.get_current_provider(item_type, (item_name:gsub("%.init$", "")))
258 end
259
260 if (not cur_name) or
261 (name < cur_name) or
262 (name == cur_name and (version == cur_version or
263 vers.compare_versions(version, cur_version))) then
264 return "nv", cur_name, cur_version, item_name
265 else
266
267 return "v", cur_name, cur_version, item_name
268 end
269end
270
271local function backup_existing(should_backup, target)
272 if not should_backup then
273 fs.delete(target)
274 return
275 end
276 if fs.exists(target) then
277 local backup = target
278 repeat
279 backup = backup .. "~"
280 until not fs.exists(backup)
281
282 util.warning(target .. " is not tracked by this installation of LuaRocks. Moving it to " .. backup)
283 local move_ok, move_err = os.rename(target, backup)
284 if not move_ok then
285 return nil, move_err
286 end
287 return backup
288 end
289end
290
291local function prepare_op_install()
292 local mkdirs = {}
293 local rmdirs = {}
294
295 local function memoize_mkdir(d)
296 if mkdirs[d] then
297 return true
298 end
299 local ok, err = fs.make_dir(d)
300 if not ok then
301 return nil, err
302 end
303 mkdirs[d] = true
304 return true
305 end
306
307 local function op_install(op)
308 local ok, err = memoize_mkdir(dir.dir_name(op.dst))
309 if not ok then
310 return nil, err
311 end
312
313 local backup, err = backup_existing(op.backup, op.dst)
314 if err then
315 return nil, err
316 end
317 if backup then
318 op.backup_file = backup
319 end
320
321 ok, err = op.fn(op.src, op.dst, op.backup)
322 if not ok then
323 return nil, err
324 end
325
326 rmdirs[dir.dir_name(op.src)] = true
327 return true
328 end
329
330 local function done_op_install()
331 for d, _ in pairs(rmdirs) do
332 fs.remove_dir_tree_if_empty(d)
333 end
334 end
335
336 return op_install, done_op_install
337end
338
339local function rollback_install(op)
340 fs.delete(op.dst)
341 if op.backup_file then
342 os.rename(op.backup_file, op.dst)
343 end
344 fs.remove_dir_tree_if_empty(dir.dir_name(op.dst))
345 return true
346end
347
348local function op_rename(op)
349 if op.suffix then
350 local suffix = check_suffix(op.src, op.suffix)
351 op.src = op.src .. suffix
352 op.dst = op.dst .. suffix
353 end
354
355 if fs.exists(op.src) then
356 fs.make_dir(dir.dir_name(op.dst))
357 fs.delete(op.dst)
358 local ok, err = os.rename(op.src, op.dst)
359 fs.remove_dir_tree_if_empty(dir.dir_name(op.src))
360 return ok, err
361 else
362 return true
363 end
364end
365
366local function rollback_rename(op)
367 return op_rename({ src = op.dst, dst = op.src })
368end
369
370local function prepare_op_delete()
371 local deletes = {}
372 local rmdirs = {}
373
374 local function done_op_delete()
375 for _, f in ipairs(deletes) do
376 os.remove(f)
377 end
378
379 for d, _ in pairs(rmdirs) do
380 fs.remove_dir_tree_if_empty(d)
381 end
382 end
383
384 local function op_delete(op)
385 if op.suffix then
386 local suffix = check_suffix(op.name, op.suffix)
387 op.name = op.name .. suffix
388 end
389
390 table.insert(deletes, op.name)
391
392 rmdirs[dir.dir_name(op.name)] = true
393 end
394
395 return op_delete, done_op_delete
396end
397
398local function rollback_ops(ops, op_fn, n)
399 for i = 1, n do
400 op_fn(ops[i])
401 end
402end
403
404
405function repos.check_everything_is_installed(name, version, rock_manifest, repo, accept_versioned)
406 local missing = {}
407 local suffix = cfg.wrapper_suffix or ""
408 for _, category in ipairs({ "bin", "lua", "lib" }) do
409 if rock_manifest[category] then
410 repos.recurse_rock_manifest_entry(rock_manifest[category], function(file_path)
411 local paths = get_deploy_paths(name, version, category, file_path, repo)
412 if category == "bin" then
413 if (fs.exists(paths.nv) or fs.exists(paths.nv .. suffix)) or
414 (accept_versioned and (fs.exists(paths.v) or fs.exists(paths.v .. suffix))) then
415 return
416 end
417 else
418 if fs.exists(paths.nv) or (accept_versioned and fs.exists(paths.v)) then
419 return
420 end
421 end
422 table.insert(missing, paths.nv)
423 end)
424 end
425 end
426 if #missing > 0 then
427 return nil, "failed deploying files. " ..
428 "The following files were not installed:\n" ..
429 table.concat(missing, "\n")
430 end
431 return true
432end
433
434
435
436
437
438
439
440
441function repos.deploy_local_files(name, version, wrap_bin_scripts, deps_mode)
442 assert(not name:match("/"))
443
444 local rock_manifest, load_err = manif.load_rock_manifest(name, version)
445 if not rock_manifest then return nil, load_err end
446
447 local repo = cfg.root_dir
448 local renames = {}
449 local installs = {}
450
451 local function install_binary(source, target)
452 if wrap_bin_scripts and fs.is_lua(source) then
453 return fs.wrap_script(source, target, deps_mode, name, version)
454 else
455 return fs.copy_binary(source, target)
456 end
457 end
458
459 local function move_lua(source, target)
460 return fs.move(source, target, "read")
461 end
462
463 local function move_lib(source, target)
464 return fs.move(source, target, "exec")
465 end
466
467 if rock_manifest.bin then
468 local source_dir = path.bin_dir(name, version)
469 repos.recurse_rock_manifest_entry(rock_manifest.bin, function(file_path)
470 local source = dir.path(source_dir, file_path)
471 local paths = get_deploy_paths(name, version, "bin", file_path, repo)
472 local mode, cur_name, cur_version = check_spot_if_available(name, version, "bin", file_path)
473
474 if mode == "nv" and cur_name then
475 local cur_paths = get_deploy_paths(cur_name, cur_version, "bin", file_path, repo)
476 table.insert(renames, { src = cur_paths.nv, dst = cur_paths.v, suffix = cfg.wrapper_suffix })
477 end
478 local target = mode == "nv" and paths.nv or paths.v
479 local backup = name ~= cur_name or version ~= cur_version
480 if wrap_bin_scripts and fs.is_lua(source) then
481 target = target .. (cfg.wrapper_suffix or "")
482 end
483 table.insert(installs, { fn = install_binary, src = source, dst = target, backup = backup })
484 end)
485 end
486
487 if rock_manifest.lua then
488 local source_dir = path.lua_dir(name, version)
489 repos.recurse_rock_manifest_entry(rock_manifest.lua, function(file_path)
490 local source = dir.path(source_dir, file_path)
491 local paths = get_deploy_paths(name, version, "lua", file_path, repo)
492 local mode, cur_name, cur_version = check_spot_if_available(name, version, "lua", file_path)
493
494 if mode == "nv" and cur_name then
495 local cur_paths = get_deploy_paths(cur_name, cur_version, "lua", file_path, repo)
496 table.insert(renames, { src = cur_paths.nv, dst = cur_paths.v })
497 cur_paths = get_deploy_paths(cur_name, cur_version, "lib", file_path:gsub("%.lua$", "." .. cfg.lib_extension), repo)
498 table.insert(renames, { src = cur_paths.nv, dst = cur_paths.v })
499 end
500 local target = mode == "nv" and paths.nv or paths.v
501 local backup = name ~= cur_name or version ~= cur_version
502 table.insert(installs, { fn = move_lua, src = source, dst = target, backup = backup })
503 end)
504 end
505
506 if rock_manifest.lib then
507 local source_dir = path.lib_dir(name, version)
508 repos.recurse_rock_manifest_entry(rock_manifest.lib, function(file_path)
509 local source = dir.path(source_dir, file_path)
510 local paths = get_deploy_paths(name, version, "lib", file_path, repo)
511 local mode, cur_name, cur_version = check_spot_if_available(name, version, "lib", file_path)
512
513 if mode == "nv" and cur_name then
514 local cur_paths = get_deploy_paths(cur_name, cur_version, "lua", file_path:gsub("%.[^.]+$", ".lua"), repo)
515 table.insert(renames, { src = cur_paths.nv, dst = cur_paths.v })
516 cur_paths = get_deploy_paths(cur_name, cur_version, "lib", file_path, repo)
517 table.insert(renames, { src = cur_paths.nv, dst = cur_paths.v })
518 end
519 local target = mode == "nv" and paths.nv or paths.v
520 local backup = name ~= cur_name or version ~= cur_version
521 table.insert(installs, { fn = move_lib, src = source, dst = target, backup = backup })
522 end)
523 end
524
525 for i, op in ipairs(renames) do
526 local ok, err = op_rename(op)
527 if not ok then
528 rollback_ops(renames, rollback_rename, i - 1)
529 return nil, err
530 end
531 end
532 local op_install, done_op_install = prepare_op_install()
533 for i, op in ipairs(installs) do
534 local ok, err = op_install(op)
535 if not ok then
536 rollback_ops(installs, rollback_install, i - 1)
537 rollback_ops(renames, rollback_rename, #renames)
538 return nil, err
539 end
540 end
541 done_op_install()
542
543 local ok, err = repos.check_everything_is_installed(name, version, rock_manifest, repo, true)
544 if not ok then
545 return nil, err
546 end
547
548 return true
549end
550
551local function add_to_double_checks(double_checks, name, version)
552 double_checks[name] = double_checks[name] or {}
553 double_checks[name][version] = true
554end
555
556local function double_check_all(double_checks, repo)
557 local errs = {}
558 for next_name, versions in pairs(double_checks) do
559 for next_version in pairs(versions) do
560 local rock_manifest, load_err = manif.load_rock_manifest(next_name, next_version)
561 local ok, err = repos.check_everything_is_installed(next_name, next_version, rock_manifest, repo, true)
562 if not ok then
563 table.insert(errs, err)
564 end
565 end
566 end
567 if next(errs) then
568 return nil, table.concat(errs, "\n")
569 end
570 return true
571end
572
573
574
575
576
577
578
579
580
581
582
583function repos.delete_local_version(name, version, deps_mode, quick)
584 assert(not name:match("/"))
585
586 local rock_manifest, load_err = manif.load_rock_manifest(name, version)
587 if not rock_manifest then
588 if not quick then
589 return nil, "rock_manifest file not found for " .. name .. " " .. version .. " - removed entry from the manifest", "remove"
590 end
591 return nil, load_err, "fail"
592 end
593
594 local repo = cfg.root_dir
595 local renames = {}
596 local deletes = {}
597
598 local double_checks = {}
599
600 if rock_manifest.bin then
601 repos.recurse_rock_manifest_entry(rock_manifest.bin, function(file_path)
602 local paths = get_deploy_paths(name, version, "bin", file_path, repo)
603 local mode, cur_name, cur_version, item_name = check_spot_if_available(name, version, "bin", file_path)
604 if mode == "v" then
605 table.insert(deletes, { name = paths.v, suffix = cfg.wrapper_suffix })
606 else
607 table.insert(deletes, { name = paths.nv, suffix = cfg.wrapper_suffix })
608
609 local next_name, next_version = manif.get_next_provider("command", item_name)
610 if next_name then
611 add_to_double_checks(double_checks, next_name, next_version)
612 local next_paths = get_deploy_paths(next_name, next_version, "bin", file_path, repo)
613 table.insert(renames, { src = next_paths.v, dst = next_paths.nv, suffix = cfg.wrapper_suffix })
614 end
615 end
616 end)
617 end
618
619 if rock_manifest.lua then
620 repos.recurse_rock_manifest_entry(rock_manifest.lua, function(file_path)
621 local paths = get_deploy_paths(name, version, "lua", file_path, repo)
622 local mode, cur_name, cur_version, item_name = check_spot_if_available(name, version, "lua", file_path)
623 if mode == "v" then
624 table.insert(deletes, { name = paths.v })
625 else
626 table.insert(deletes, { name = paths.nv })
627
628 local next_name, next_version = manif.get_next_provider("module", item_name)
629 if next_name then
630 add_to_double_checks(double_checks, next_name, next_version)
631 local next_lua_paths = get_deploy_paths(next_name, next_version, "lua", file_path, repo)
632 table.insert(renames, { src = next_lua_paths.v, dst = next_lua_paths.nv })
633 local next_lib_paths = get_deploy_paths(next_name, next_version, "lib", file_path:gsub("%.[^.]+$", ".lua"), repo)
634 table.insert(renames, { src = next_lib_paths.v, dst = next_lib_paths.nv })
635 end
636 end
637 end)
638 end
639
640 if rock_manifest.lib then
641 repos.recurse_rock_manifest_entry(rock_manifest.lib, function(file_path)
642 local paths = get_deploy_paths(name, version, "lib", file_path, repo)
643 local mode, cur_name, cur_version, item_name = check_spot_if_available(name, version, "lib", file_path)
644 if mode == "v" then
645 table.insert(deletes, { name = paths.v })
646 else
647 table.insert(deletes, { name = paths.nv })
648
649 local next_name, next_version = manif.get_next_provider("module", item_name)
650 if next_name then
651 add_to_double_checks(double_checks, next_name, next_version)
652 local next_lua_paths = get_deploy_paths(next_name, next_version, "lua", file_path:gsub("%.[^.]+$", ".lua"), repo)
653 table.insert(renames, { src = next_lua_paths.v, dst = next_lua_paths.nv })
654 local next_lib_paths = get_deploy_paths(next_name, next_version, "lib", file_path, repo)
655 table.insert(renames, { src = next_lib_paths.v, dst = next_lib_paths.nv })
656 end
657 end
658 end)
659 end
660
661 local op_delete, done_op_delete = prepare_op_delete()
662 for _, op in ipairs(deletes) do
663 op_delete(op)
664 end
665 done_op_delete()
666
667 if not quick then
668 for _, op in ipairs(renames) do
669 op_rename(op)
670 end
671
672 local ok, err = double_check_all(double_checks, repo)
673 if not ok then
674 return nil, err, "fail"
675 end
676 end
677
678 fs.delete(path.install_dir(name, version))
679 if not get_installed_versions(name) then
680 fs.delete(dir.path(cfg.rocks_dir, name))
681 end
682
683 if quick then
684 return true, nil, "ok"
685 end
686
687 return true, nil, "remove"
688end
689
690return repos
diff --git a/src/luarocks/require.lua b/src/luarocks/require.lua
new file mode 100644
index 00000000..5b7ca3c3
--- /dev/null
+++ b/src/luarocks/require.lua
@@ -0,0 +1,2 @@
1
2return require("luarocks.loader")
diff --git a/src/luarocks/results.lua b/src/luarocks/results.lua
new file mode 100644
index 00000000..fec1e463
--- /dev/null
+++ b/src/luarocks/results.lua
@@ -0,0 +1,60 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local string = _tl_compat and _tl_compat.string or string; local results = {}
2
3
4local vers = require("luarocks.core.vers")
5local util = require("luarocks.util")
6
7
8local result = require("luarocks.core.types.result")
9
10
11local result_mt = {}
12
13result_mt.__index = result.Result
14
15function results.new(name, version, repo, arch, namespace)
16
17 assert(not name:match("/"))
18
19
20 if not namespace then
21 name, namespace = util.split_namespace(name)
22 end
23
24 local self = {
25 name = name,
26 version = version,
27 namespace = namespace,
28 arch = arch,
29 repo = repo,
30 }
31
32 return setmetatable(self, result_mt)
33end
34
35
36
37
38
39
40
41
42local function match_name(query, name)
43 if query.substring then
44 return name:find(query.name, 0, true) and true or false
45 else
46 return name == query.name
47 end
48end
49
50
51
52
53function result.Result:satisfies(query)
54 return match_name(query, self.name) and
55 (query.arch[self.arch] or query.arch["any"]) and
56 ((not query.namespace) or (query.namespace == self.namespace)) and
57 (vers.match_constraints(vers.parse_version(self.version), query.constraints))
58end
59
60return results
diff --git a/src/luarocks/rockspecs.lua b/src/luarocks/rockspecs.lua
new file mode 100644
index 00000000..c41469fd
--- /dev/null
+++ b/src/luarocks/rockspecs.lua
@@ -0,0 +1,184 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2local rockspecs = {}
3
4
5local cfg = require("luarocks.core.cfg")
6local dir = require("luarocks.dir")
7local path = require("luarocks.path")
8local queries = require("luarocks.queries")
9local type_rockspec = require("luarocks.type.rockspec")
10local util = require("luarocks.util")
11local vers = require("luarocks.core.vers")
12
13
14
15
16
17
18local vendored_build_type_set = {
19 ["builtin"] = true,
20 ["cmake"] = true,
21 ["command"] = true,
22 ["make"] = true,
23 ["module"] = true,
24 ["none"] = true,
25}
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40local function platform_overrides(tbl)
41
42 if not tbl then return end
43
44 local tblp = tbl.platforms
45
46 if type(tblp) == "table" then
47 for platform in cfg.each_platform() do
48 local platform_tbl = tblp[platform]
49 if type(platform_tbl) == "table" then
50 util.deep_merge(tbl, platform_tbl)
51 end
52 end
53 end
54 tbl.platforms = nil
55end
56
57local function convert_dependencies(dependencies)
58 local qs = {}
59 for i = 1, #dependencies do
60 local parsed, err = queries.from_dep_string(dependencies[i])
61 if not parsed then
62 return nil, "Parse error processing dependency '" .. dependencies[i] .. "': " .. tostring(err)
63 end
64 qs[i] = parsed
65 end
66 dependencies.queries = qs
67 return true
68end
69
70
71
72
73
74local function configure_paths(rockspec)
75 local vars = {}
76 for k, v in pairs(cfg.variables) do
77 vars[k] = v
78 end
79 local name, version = rockspec.name, rockspec.version
80 vars.PREFIX = path.install_dir(name, version)
81 vars.LUADIR = path.lua_dir(name, version)
82 vars.LIBDIR = path.lib_dir(name, version)
83 vars.CONFDIR = path.conf_dir(name, version)
84 vars.BINDIR = path.bin_dir(name, version)
85 vars.DOCDIR = path.doc_dir(name, version)
86 rockspec.variables = vars
87end
88
89function rockspecs.from_persisted_table(filename, rockspec, globals, quick)
90
91 if rockspec.rockspec_format then
92 if vers.compare_versions(rockspec.rockspec_format, type_rockspec.rockspec_format) then
93 return nil, "Rockspec format " .. rockspec.rockspec_format .. " is not supported, please upgrade LuaRocks."
94 end
95 end
96
97 if not quick then
98 local ok, err = type_rockspec.check(rockspec, globals or {})
99 if not ok then
100 return nil, err
101 end
102 end
103
104
105
106
107
108 do
109 local parsed_format = vers.parse_version(rockspec.rockspec_format or "1.0")
110 rockspec.format_is_at_least = function(self, version)
111 return parsed_format >= vers.parse_version(version)
112 end
113 end
114
115 platform_overrides(rockspec.build)
116 platform_overrides(rockspec.dependencies)
117 platform_overrides(rockspec.build_dependencies)
118 platform_overrides(rockspec.test_dependencies)
119 platform_overrides(rockspec.external_dependencies)
120 platform_overrides(rockspec.source)
121 platform_overrides(rockspec.hooks)
122 platform_overrides(rockspec.test)
123
124 rockspec.name = rockspec.package:lower()
125
126 local protocol, pathname = dir.split_url(rockspec.source.url)
127 if dir.is_basic_protocol(protocol) then
128 rockspec.source.file = rockspec.source.file or dir.base_name(rockspec.source.url)
129 end
130 rockspec.source.protocol, rockspec.source.pathname = protocol, pathname
131
132
133 if rockspec.source.cvs_module then rockspec.source.module = rockspec.source.cvs_module end
134 if rockspec.source.cvs_tag then rockspec.source.tag = rockspec.source.cvs_tag end
135
136 rockspec.local_abs_filename = filename
137 rockspec.source.dir_set = rockspec.source.dir ~= nil
138 rockspec.source.dir = rockspec.source.dir or rockspec.source.module
139
140 rockspec.rocks_provided = util.get_rocks_provided(rockspec)
141
142 rockspec.dependencies = rockspec.dependencies or {}
143 rockspec.build_dependencies = rockspec.build_dependencies or {}
144 rockspec.test_dependencies = rockspec.test_dependencies or {}
145 for _, d in ipairs({ rockspec.dependencies, rockspec.build_dependencies, rockspec.test_dependencies }) do
146 local _, err = convert_dependencies(d)
147 if err then
148 return nil, err
149 end
150 end
151
152 if rockspec.build and
153 rockspec.build.type and
154 not vendored_build_type_set[rockspec.build.type] then
155 local build_pkg_name = "luarocks-build-" .. rockspec.build.type
156 if not rockspec.build_dependencies then
157 rockspec.build_dependencies = {}
158 end
159
160 local found = false
161 for _, dep in ipairs(rockspec.build_dependencies.queries) do
162 if dep.name == build_pkg_name then
163 found = true
164 break
165 end
166 end
167
168 if not found then
169 local query, errfromdep = queries.from_dep_string(build_pkg_name)
170 if errfromdep then
171 return nil, "Invalid dependency in rockspec: " .. errfromdep
172 end
173 table.insert(rockspec.build_dependencies.queries, query)
174 end
175 end
176
177 if not quick then
178 configure_paths(rockspec)
179 end
180
181 return rockspec
182end
183
184return rockspecs
diff --git a/src/luarocks/search.lua b/src/luarocks/search.lua
new file mode 100644
index 00000000..eca17a8f
--- /dev/null
+++ b/src/luarocks/search.lua
@@ -0,0 +1,385 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local search = {}
2
3local dir = require("luarocks.dir")
4local path = require("luarocks.path")
5local manif = require("luarocks.manif")
6local vers = require("luarocks.core.vers")
7local cfg = require("luarocks.core.cfg")
8local util = require("luarocks.util")
9local queries = require("luarocks.queries")
10local results = require("luarocks.results")
11
12
13
14
15
16
17
18
19
20
21
22
23function search.store_result(result_tree, result)
24
25 local name = result.name
26 local version = result.version
27
28 if not result_tree[name] then result_tree[name] = {} end
29 if not result_tree[name][version] then result_tree[name][version] = {} end
30 table.insert(result_tree[name][version], {
31 arch = result.arch,
32 repo = result.repo,
33 namespace = result.namespace,
34 })
35end
36
37
38
39
40
41
42
43
44
45
46local function store_if_match(result_tree, result, query)
47
48 if result:satisfies(query) then
49 search.store_result(result_tree, result)
50 end
51end
52
53
54
55
56
57
58
59
60
61
62function search.disk_search(repo, query, result_tree)
63
64 local fs = require("luarocks.fs")
65
66 if not result_tree then
67 result_tree = {}
68 end
69
70 for name in fs.dir(repo) do
71 local pathname = dir.path(repo, name)
72 local rname, rversion, rarch = path.parse_name(name)
73
74 if rname and (pathname:match(".rockspec$") or pathname:match(".rock$")) then
75 local result = results.new(rname, rversion, repo, rarch)
76 store_if_match(result_tree, result, query)
77 elseif fs.is_dir(pathname) then
78 for version in fs.dir(pathname) do
79 if version:match("-%d+$") then
80 local namespace = path.read_namespace(name, version, repo)
81 local result = results.new(name, version, repo, "installed", namespace)
82 store_if_match(result_tree, result, query)
83 end
84 end
85 end
86 end
87 return result_tree
88end
89
90
91
92
93
94
95
96
97
98
99
100local function manifest_search(result_tree, repo, query, lua_version, is_local)
101
102
103 if (not is_local) and query.namespace then
104 repo = repo .. "/manifests/" .. query.namespace
105 end
106
107 local manifest, err, errcode = manif.load_manifest(repo, lua_version, not is_local)
108 if not manifest then
109 return nil, err, errcode
110 end
111 for name, versions in pairs(manifest.repository) do
112 for version, items in pairs(versions) do
113 local namespace = is_local and path.read_namespace(name, version, repo) or query.namespace
114 for _, item in ipairs(items) do
115 local result = results.new(name, version, repo, item.arch, namespace)
116 store_if_match(result_tree, result, query)
117 end
118 end
119 end
120 return true
121end
122
123local function remote_manifest_search(result_tree, repo, query, lua_version)
124 return manifest_search(result_tree, repo, query, lua_version, false)
125end
126
127function search.local_manifest_search(result_tree, repo, query, lua_version)
128 return manifest_search(result_tree, repo, query, lua_version, true)
129end
130
131
132
133
134
135
136
137function search.search_repos(query, lua_version)
138
139 local result_tree = {}
140 local repo = {}
141 for _, repostr in ipairs(cfg.rocks_servers) do
142 if type(repostr) == "string" then
143 repo = { repostr }
144 else
145 repo = repostr
146 end
147 for _, mirror in ipairs(repo) do
148 if not cfg.disabled_servers[mirror] then
149 local protocol, pathname = dir.split_url(mirror)
150 if protocol == "file" then
151 mirror = pathname
152 end
153 local ok, err, errcode = remote_manifest_search(result_tree, mirror, query, lua_version)
154 if errcode == "network" then
155 cfg.disabled_servers[mirror] = true
156 end
157 if ok then
158 break
159 else
160 util.warning("Failed searching manifest: " .. err)
161 if errcode == "downloader" then
162 break
163 end
164 end
165 end
166 end
167 end
168
169 local provided_repo = "provided by VM or rocks_provided"
170 for name, version in pairs(util.get_rocks_provided()) do
171 local result = results.new(name, version, provided_repo, "installed")
172 store_if_match(result_tree, result, query)
173 end
174 return result_tree
175end
176
177
178
179
180
181
182
183local function pick_latest_version(name, versions)
184 assert(not name:match("/"))
185
186 local vtables = {}
187 for v, _ in pairs(versions) do
188 table.insert(vtables, vers.parse_version(v))
189 end
190 table.sort(vtables)
191 local version = vtables[#vtables].string
192 local items = versions[version]
193 if items then
194 local pick = 1
195 for i, item in ipairs(items) do
196 if (item.arch == 'src' and items[pick].arch == 'rockspec') or
197 (item.arch ~= 'src' and item.arch ~= 'rockspec') then
198 pick = i
199 end
200 end
201 return path.make_url(items[pick].repo, name, version, items[pick].arch)
202 end
203 return nil
204end
205
206
207
208
209local function supported_lua_versions(query)
210 local result_tree = {}
211
212 for lua_version in util.lua_versions() do
213 if lua_version ~= cfg.lua_version then
214 util.printout("Checking for Lua " .. lua_version .. "...")
215 if search.search_repos(query, lua_version)[query.name] then
216 table.insert(result_tree, lua_version)
217 end
218 end
219 end
220
221 return result_tree
222end
223
224
225
226
227
228
229function search.find_suitable_rock(query)
230
231 local rocks_provided = util.get_rocks_provided()
232
233 if rocks_provided[query.name] then
234
235 return nil, "Rock " .. query.name .. " " .. rocks_provided[query.name] ..
236 " is already provided by VM or via 'rocks_provided' in the config file.", "provided"
237 end
238
239 local result_tree = search.search_repos(query)
240 local first_rock = next(result_tree)
241 if not first_rock then
242 return nil, "No results matching query were found for Lua " .. cfg.lua_version .. ".", "notfound"
243 elseif next(result_tree, first_rock) then
244
245 return nil, "Several rocks matched query.", "manyfound"
246 else
247 return pick_latest_version(query.name, result_tree[first_rock])
248 end
249end
250
251function search.find_rock_checking_lua_versions(query, check_lua_versions)
252 local url, err, errcode = search.find_suitable_rock(query)
253 if url then
254 return url
255 end
256
257 if errcode == "notfound" then
258 local add
259 if check_lua_versions then
260 util.printout(query.name .. " not found for Lua " .. cfg.lua_version .. ".")
261 util.printout("Checking if available for other Lua versions...")
262
263
264 local lua_versions = supported_lua_versions(query)
265
266 if #lua_versions ~= 0 then
267
268 for i, lua_version in ipairs(lua_versions) do
269 lua_versions[i] = "Lua " .. lua_version
270 end
271
272 local versions_message = "only " .. table.concat(lua_versions, " and ") ..
273 " but not Lua " .. cfg.lua_version .. "."
274
275 if #query.constraints == 0 then
276 add = query.name .. " supports " .. versions_message
277 elseif #query.constraints == 1 and query.constraints[1].op == "==" then
278 local queryversion = tostring(query.constraints[1].version)
279 add = query.name .. " " .. queryversion .. " supports " .. versions_message
280 else
281 add = "Matching " .. query.name .. " versions support " .. versions_message
282 end
283 else
284 add = query.name .. " is not available for any Lua versions."
285 end
286 else
287 add = "To check if it is available for other Lua versions, use --check-lua-versions."
288 end
289 err = err .. "\n" .. add
290 end
291
292 return nil, err
293end
294
295function search.find_src_or_rockspec(name, namespace, version, check_lua_versions)
296 local query = queries.new(name, namespace, version, false, "src|rockspec")
297 local url, err = search.find_rock_checking_lua_versions(query, check_lua_versions)
298 if not url then
299 return nil, "Could not find a result named " .. tostring(query) .. ": " .. err
300 end
301 return url
302end
303
304
305
306
307function search.print_result_tree(result_tree, porcelain)
308
309 if porcelain then
310 for packagestr, versions in util.sortedpairs(result_tree) do
311 for version, repos in util.sortedpairs(versions, vers.compare_versions) do
312 for _, repo in ipairs(repos) do
313 local nrepo = dir.normalize(repo.repo)
314 util.printout(packagestr, version, repo.arch, nrepo, repo.namespace)
315 end
316 end
317 end
318 return
319 end
320
321 for packagestr, versions in util.sortedpairs(result_tree) do
322 local namespaces = {}
323 for version, repos in util.sortedpairs(versions, vers.compare_versions) do
324 for _, repo in ipairs(repos) do
325 local key = repo.namespace or ""
326 local list = namespaces[key] or {}
327 namespaces[key] = list
328
329 repo.repo = dir.normalize(repo.repo)
330 table.insert(list, " " .. version .. " (" .. repo.arch .. ") - " .. path.root_dir(repo.repo))
331 end
332 end
333 for key, list in util.sortedpairs(namespaces) do
334 util.printout(key == "" and packagestr or key .. "/" .. packagestr)
335 for _, line in ipairs(list) do
336 util.printout(line)
337 end
338 util.printout()
339 end
340 end
341end
342
343function search.pick_installed_rock(query, given_tree)
344
345 local result_tree = {}
346 local tree_map = {}
347 local trees = cfg.rocks_trees
348 if given_tree then
349 trees = { given_tree }
350 end
351 for _, tree in ipairs(trees) do
352 local rocks_dir = path.rocks_dir(tree)
353 tree_map[rocks_dir] = tree
354 search.local_manifest_search(result_tree, rocks_dir, query)
355 end
356 if not next(result_tree) then
357 return nil, "cannot find package " .. tostring(query) .. "\nUse 'list' to find installed rocks."
358 end
359
360 if not result_tree[query.name] and next(result_tree, next(result_tree)) then
361 local out = { "multiple installed packages match the name '" .. tostring(query) .. "':\n\n" }
362 for name, _ in util.sortedpairs(result_tree) do
363 table.insert(out, " " .. name .. "\n")
364 end
365 table.insert(out, "\nPlease specify a single rock.\n")
366 return nil, table.concat(out)
367 end
368
369 local repo_url
370
371 local name, versions
372 if result_tree[query.name] then
373 name, versions = query.name, result_tree[query.name]
374 else
375 name, versions = util.sortedpairs(result_tree)()
376 end
377
378 local version, repositories = util.sortedpairs(versions, vers.compare_versions)()
379 for _, rp in ipairs(repositories) do repo_url = rp.repo end
380
381 local repo = tree_map[repo_url]
382 return name, version, repo, repo_url
383end
384
385return search
diff --git a/src/luarocks/signing.lua b/src/luarocks/signing.lua
new file mode 100644
index 00000000..cb91643a
--- /dev/null
+++ b/src/luarocks/signing.lua
@@ -0,0 +1,48 @@
1local signing = {}
2
3local cfg = require("luarocks.core.cfg")
4local fs = require("luarocks.fs")
5
6local function get_gpg()
7 local vars = cfg.variables
8 local gpg = vars.GPG
9 local gpg_ok, err = fs.is_tool_available(gpg, "gpg")
10 if not gpg_ok then
11 return nil, err
12 end
13 return gpg
14end
15
16function signing.signature_url(url)
17 return url .. ".asc"
18end
19
20function signing.sign_file(file)
21 local gpg, err = get_gpg()
22 if not gpg then
23 return nil, err
24 end
25
26 local sigfile = file .. ".asc"
27 if fs.execute(gpg, "--armor", "--output", sigfile, "--detach-sign", file) then
28 return sigfile
29 else
30 return nil, "failed running " .. gpg .. " to sign " .. file
31 end
32end
33
34function signing.verify_signature(file, sigfile)
35 local gpg, err = get_gpg()
36 if not gpg then
37 return nil, err
38 end
39
40 if fs.execute(gpg, "--verify", sigfile, file) then
41 return true
42 else
43 return nil, "GPG returned a verification error"
44 end
45
46end
47
48return signing
diff --git a/src/luarocks/test.lua b/src/luarocks/test.lua
new file mode 100644
index 00000000..01dfae12
--- /dev/null
+++ b/src/luarocks/test.lua
@@ -0,0 +1,110 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pcall = _tl_compat and _tl_compat.pcall or pcall; local table = _tl_compat and _tl_compat.table or table; local test = {}
2
3
4local fetch = require("luarocks.fetch")
5local deps = require("luarocks.deps")
6local util = require("luarocks.util")
7
8
9
10
11
12
13
14local test_types = {
15 "busted",
16 "command",
17}
18
19local test_modules = {}
20local typetomod = {}
21local modtotype = {}
22
23for _, test_type in ipairs(test_types) do
24 local mod
25 if test_type == "command" then
26 mod = require("luarocks.test.command")
27 elseif test_type == "busted" then
28 mod = require("luarocks.test.busted")
29 end
30 table.insert(test_modules, mod)
31 typetomod[test_type] = mod
32 modtotype[mod] = test_type
33end
34
35local function get_test_type(rockspec)
36 if rockspec.test and rockspec.test.type then
37 return rockspec.test.type
38 end
39
40 for _, test_module in ipairs(test_modules) do
41 if test_module.detect_type() then
42 return modtotype[test_module]
43 end
44 end
45
46 return nil, "could not detect test type -- no test suite for " .. rockspec.package .. "?"
47end
48
49
50function test.run_test_suite(rockspec_arg, test_type, args, prepare)
51 local rockspec
52 if type(rockspec_arg) == "string" then
53 local err, errcode
54 rockspec, err, errcode = fetch.load_rockspec(rockspec_arg)
55 if err then
56 return nil, err, errcode
57 end
58 else
59 rockspec = rockspec_arg
60 end
61
62 if not test_type then
63 local err
64 test_type, err = get_test_type(rockspec)
65 if not test_type then
66 return nil, err
67 end
68 end
69
70 local all_deps = {
71 "dependencies",
72 "build_dependencies",
73 "test_dependencies",
74 }
75 for _, dep_kind in ipairs(all_deps) do
76 if (rockspec)[dep_kind] and next((rockspec)[dep_kind]) ~= nil then
77 local _, err, errcode = deps.fulfill_dependencies(rockspec, dep_kind, "all")
78 if err then
79 return nil, err, errcode
80 end
81 end
82 end
83
84 local pok, test_mod = pcall(require, "luarocks.test." .. test_type)
85 if not pok then
86 return nil, "failed loading test execution module luarocks.test." .. test_type
87 end
88
89 if prepare then
90 if test_type == "busted" then
91 return test_mod.run_tests(rockspec.test, { "--version" })
92 else
93 return true
94 end
95 else
96 local flags = rockspec.test and rockspec.test.flags
97 if type(flags) == "table" then
98 util.variable_substitutions(flags, rockspec.variables)
99
100
101 for i = 1, #flags do
102 table.insert(args, i, flags[i])
103 end
104 end
105
106 return test_mod.run_tests(rockspec.test, args)
107 end
108end
109
110return test
diff --git a/src/luarocks/test/busted.lua b/src/luarocks/test/busted.lua
new file mode 100644
index 00000000..bc00b33a
--- /dev/null
+++ b/src/luarocks/test/busted.lua
@@ -0,0 +1,55 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack
2local busted = {}
3
4
5local fs = require("luarocks.fs")
6local deps = require("luarocks.deps")
7local path = require("luarocks.path")
8local dir = require("luarocks.dir")
9local queries = require("luarocks.queries")
10local install = require("luarocks.cmd.install")
11
12
13
14function busted.detect_type()
15 if fs.exists(".busted") then
16 return true
17 end
18 return false
19end
20
21function busted.run_tests(test, args)
22 if not test then
23 test = {}
24 end
25
26 local ok, bustedver, where = deps.fulfill_dependency(queries.new("busted"), nil, nil, nil, "test_dependencies")
27 if not ok then
28 return nil, bustedver
29 end
30
31 local busted_exe
32 if test.busted_executable then
33 busted_exe = test.busted_executable
34 else
35 busted_exe = dir.path(path.root_dir(where), "bin", "busted")
36
37
38 local busted_bat = dir.path(path.root_dir(where), "bin", "busted.bat")
39
40 if not fs.exists(busted_exe) and not fs.exists(busted_bat) then
41 return nil, "'busted' executable failed to be installed"
42 end
43 end
44
45 local err
46 ok, err = fs.execute(busted_exe, _tl_table_unpack(args))
47 if ok then
48 return true
49 else
50 return nil, err or "test suite failed."
51 end
52end
53
54
55return busted
diff --git a/src/luarocks/test/command.lua b/src/luarocks/test/command.lua
new file mode 100644
index 00000000..41d30378
--- /dev/null
+++ b/src/luarocks/test/command.lua
@@ -0,0 +1,55 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack
2local command = {}
3
4
5local fs = require("luarocks.fs")
6local cfg = require("luarocks.core.cfg")
7
8
9
10function command.detect_type()
11 if fs.exists("test.lua") then
12 return true
13 end
14 return false
15end
16
17function command.run_tests(test, args)
18 if not test then
19 test = {
20 script = "test.lua",
21 }
22 end
23
24 if not test.script and not test.command then
25 test.script = "test.lua"
26 end
27
28 local ok
29
30 if test.script then
31 local test_script = test.script
32 if not (type(test_script) == "string") then
33 return nil, "Malformed rockspec: 'script' expects a string"
34 end
35 if not fs.exists(test.script) then
36 return nil, "Test script " .. test.script .. " does not exist"
37 end
38 local lua = fs.Q(cfg.variables["LUA"])
39 ok = fs.execute(lua, test.script, _tl_table_unpack(args))
40 elseif test.command then
41 local test_command = test.command
42 if not (type(test_command) == "string") then
43 return nil, "Malformed rockspec: 'command' expects a string"
44 end
45 ok = fs.execute(test.command, _tl_table_unpack(args))
46 end
47
48 if ok then
49 return true
50 else
51 return nil, "tests failed with non-zero exit code"
52 end
53end
54
55return command
diff --git a/src/luarocks/tools/patch.lua b/src/luarocks/tools/patch.lua
new file mode 100644
index 00000000..c9567bc6
--- /dev/null
+++ b/src/luarocks/tools/patch.lua
@@ -0,0 +1,746 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local debug = _tl_compat and _tl_compat.debug or debug; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local math = _tl_compat and _tl_compat.math or math; local os = _tl_compat and _tl_compat.os or os; local pairs = _tl_compat and _tl_compat.pairs or pairs; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2
3
4
5
6
7
8
9
10
11local patch = {Lineends = {}, Hunk = {}, File = {}, Files = {}, }
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43local fs = require("luarocks.fs")
44
45
46
47
48
49
50
51local debugmode = false
52local function debug(_) end
53local function info(_) end
54local function warning(s) io.stderr:write(s .. '\n') end
55
56
57local function startswith(s, s2)
58 return s:sub(1, #s2) == s2
59end
60
61
62local function endswith(s, s2)
63 return #s >= #s2 and s:sub(#s - #s2 + 1) == s2
64end
65
66
67local function endlstrip(s)
68 return s:gsub('[\r\n]+$', '')
69end
70
71
72local function table_copy(t)
73 local t2 = {}
74 for k, v in pairs(t) do t2[k] = v end
75 return t2
76end
77
78local function exists(filename)
79 local fh = io.open(filename)
80 local result = fh ~= nil
81 if fh then fh:close() end
82 return result
83end
84local function isfile() return true end
85
86local function string_as_file(s)
87 return {
88 at = 0,
89 str = s,
90 len = #s,
91 eof = false,
92 read = function(self, n)
93 if self.eof then return nil end
94 local chunk = self.str:sub(self.at, self.at + n - 1)
95 self.at = self.at + n
96 if self.at > self.len then
97 self.eof = true
98 end
99 return chunk
100 end,
101 close = function(self)
102 self.eof = true
103 end,
104 }
105end
106
107
108
109
110
111
112
113
114
115
116local function file_lines(f)
117 local CHUNK_SIZE = 1024
118 local buffer = ""
119 local pos_beg = 1
120 return function()
121 local pos, chars
122 while 1 do
123 pos, chars = buffer:match('()([\r\n].)', pos_beg)
124 if pos or not f then
125 break
126 elseif f then
127 local chunk = f:read(CHUNK_SIZE)
128 if chunk then
129 buffer = buffer:sub(pos_beg) .. chunk
130 pos_beg = 1
131 else
132 f = nil
133 end
134 end
135 end
136 local posi = math.tointeger(pos)
137 if not posi then
138 posi = #buffer
139 elseif chars == '\r\n' then
140 posi = posi + 1
141 end
142 local line = buffer:sub(pos_beg, posi)
143 pos_beg = posi + 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
158local function match_epoch(str)
159 return str:match("[^0-9]1969[^0-9]") or str:match("[^0-9]1970[^0-9]")
160end
161
162function patch.read_patch(filename, data)
163
164 local state = 'header'
165
166
167
168
169
170
171 local all_ok = true
172 local lineends = { lf = 0, crlf = 0, cr = 0 }
173 local files = { source = {}, target = {}, epoch = {}, hunks = {}, fileends = {}, hunkends = {} }
174 local nextfileno = 0
175 local nexthunkno = 0
176
177
178
179 local hunkinfo = {
180 startsrc = nil, linessrc = nil, starttgt = nil, linestgt = nil,
181 invalid = false, text = {},
182 }
183 local hunkactual = { linessrc = nil, linestgt = nil }
184
185 info(string.format("reading patch %s", filename))
186
187 local fp
188 if data then
189 fp = string_as_file(data)
190 else
191 fp = filename == '-' and io.stdin or assert(io.open(filename, "rb"))
192 end
193 local lineno = 0
194
195 for line in file_lines(fp) do
196 lineno = lineno + 1
197 if state == 'header' then
198 if startswith(line, "--- ") then
199 state = 'filenames'
200 end
201
202 end
203 if state == 'hunkbody' then
204
205
206 if line:match("^[\r\n]*$") then
207
208 line = " " .. line
209 end
210
211
212 if line:match("^[- +\\]") then
213
214 local he = files.hunkends[nextfileno]
215 if endswith(line, "\r\n") then
216 he.crlf = he.crlf + 1
217 elseif endswith(line, "\n") then
218 he.lf = he.lf + 1
219 elseif endswith(line, "\r") then
220 he.cr = he.cr + 1
221 end
222 if startswith(line, "-") then
223 hunkactual.linessrc = hunkactual.linessrc + 1
224 elseif startswith(line, "+") then
225 hunkactual.linestgt = hunkactual.linestgt + 1
226 elseif startswith(line, "\\") then
227
228 else
229 hunkactual.linessrc = hunkactual.linessrc + 1
230 hunkactual.linestgt = hunkactual.linestgt + 1
231 end
232 table.insert(hunkinfo.text, line)
233
234 else
235 warning(string.format("invalid hunk no.%d at %d for target file %s",
236 nexthunkno, lineno, files.target[nextfileno]))
237
238 table.insert(files.hunks[nextfileno], table_copy(hunkinfo))
239 files.hunks[nextfileno][nexthunkno].invalid = true
240 all_ok = false
241 state = 'hunkskip'
242 end
243
244
245 if hunkactual.linessrc > hunkinfo.linessrc or
246 hunkactual.linestgt > hunkinfo.linestgt then
247
248 warning(string.format("extra hunk no.%d lines at %d for target %s",
249 nexthunkno, lineno, files.target[nextfileno]))
250
251 table.insert(files.hunks[nextfileno], table_copy(hunkinfo))
252 files.hunks[nextfileno][nexthunkno].invalid = true
253 state = 'hunkskip'
254 elseif hunkinfo.linessrc == hunkactual.linessrc and
255 hunkinfo.linestgt == hunkactual.linestgt then
256
257 table.insert(files.hunks[nextfileno], table_copy(hunkinfo))
258 state = 'hunkskip'
259
260
261 local ends = files.hunkends[nextfileno]
262 if (ends.cr ~= 0 and 1 or 0) + (ends.crlf ~= 0 and 1 or 0) +
263 (ends.lf ~= 0 and 1 or 0) > 1 then
264
265 warning(string.format("inconsistent line ends in patch hunks for %s",
266 files.source[nextfileno]))
267 end
268 end
269
270 end
271
272 if state == 'hunkskip' then
273 if match_linerange(line) then
274 state = 'hunkhead'
275 elseif startswith(line, "--- ") then
276 state = 'filenames'
277 if debugmode and #files.source > 0 then
278 debug(string.format("- %2d hunks for %s", #files.hunks[nextfileno],
279 files.source[nextfileno]))
280 end
281 end
282
283 end
284 local advance
285 if state == 'filenames' then
286 if startswith(line, "--- ") then
287 if files.source[nextfileno] then
288 all_ok = false
289 warning(string.format("skipping invalid patch for %s",
290 files.source[nextfileno + 1]))
291 table.remove(files.source, nextfileno + 1)
292
293
294 end
295
296
297
298 local match, rest = line:match("^%-%-%- ([^ \t\r\n]+)(.*)")
299 if not match then
300 all_ok = false
301 warning(string.format("skipping invalid filename at line %d", lineno + 1))
302 state = 'header'
303 else
304 if match_epoch(rest) then
305 files.epoch[nextfileno + 1] = true
306 end
307 table.insert(files.source, match)
308 end
309 elseif not startswith(line, "+++ ") then
310 if files.source[nextfileno] then
311 all_ok = false
312 warning(string.format("skipping invalid patch with no target for %s",
313 files.source[nextfileno + 1]))
314 table.remove(files.source, nextfileno + 1)
315 else
316
317 warning("skipping invalid target patch")
318 end
319 state = 'header'
320 else
321 if files.target[nextfileno] then
322 all_ok = false
323 warning(string.format("skipping invalid patch - double target at line %d",
324 lineno + 1))
325 table.remove(files.source, nextfileno + 1)
326 table.remove(files.target, nextfileno + 1)
327 nextfileno = nextfileno - 1
328
329
330 state = 'header'
331 else
332
333
334
335 local re_filename = "^%+%+%+ ([^ \t\r\n]+)(.*)$"
336 local match, rest = line:match(re_filename)
337 if not match then
338 all_ok = false
339 warning(string.format(
340 "skipping invalid patch - no target filename at line %d",
341 lineno + 1))
342 state = 'header'
343 else
344 table.insert(files.target, match)
345 nextfileno = nextfileno + 1
346 if match_epoch(rest) then
347 files.epoch[nextfileno] = true
348 end
349 nexthunkno = 0
350 table.insert(files.hunks, {})
351 table.insert(files.hunkends, table_copy(lineends))
352 table.insert(files.fileends, table_copy(lineends))
353 state = 'hunkhead'
354 advance = true
355 end
356 end
357 end
358
359 end
360 if not advance and state == 'hunkhead' then
361 local m1, m2, m3, m4 = match_linerange(line)
362 if not m1 then
363 if not files.hunks[nextfileno - 1] then
364 all_ok = false
365 warning(string.format("skipping invalid patch with no hunks for file %s",
366 files.target[nextfileno]))
367 end
368 state = 'header'
369 else
370 hunkinfo.startsrc = math.tointeger(m1)
371 hunkinfo.linessrc = math.tointeger(m2) or 1
372 hunkinfo.starttgt = math.tointeger(m3)
373 hunkinfo.linestgt = math.tointeger(m4) or 1
374 hunkinfo.invalid = false
375 hunkinfo.text = {}
376
377 hunkactual.linessrc = 0
378 hunkactual.linestgt = 0
379
380 state = 'hunkbody'
381 nexthunkno = nexthunkno + 1
382 end
383
384 end
385 end
386 if state ~= 'hunkskip' then
387 warning(string.format("patch file incomplete - %s", filename))
388 all_ok = false
389
390 else
391
392 if debugmode and #files.source > 0 then
393 debug(string.format("- %2d hunks for %s", #files.hunks[nextfileno],
394 files.source[nextfileno]))
395 end
396 end
397
398 local sum = 0; for _, hset in ipairs(files.hunks) do sum = sum + #hset end
399 info(string.format("total files: %d total hunks: %d", #files.source, sum))
400 fp:close()
401 return files, all_ok
402end
403
404local function find_hunk(file, h, hno)
405 for fuzz = 0, 2 do
406 local lineno = h.startsrc
407 for i = 0, #file do
408 local found = true
409 local location = lineno
410 for l, hline in ipairs(h.text) do
411 if l > fuzz then
412
413 if startswith(hline, " ") or startswith(hline, "-") then
414 local line = file[lineno]
415 lineno = lineno + 1
416 if not line or #line == 0 then
417 found = false
418 break
419 end
420 if endlstrip(line) ~= endlstrip(hline:sub(2)) then
421 found = false
422 break
423 end
424 end
425 end
426 end
427 if found then
428 local offset = location - h.startsrc - fuzz
429 if offset ~= 0 then
430 warning(string.format("Hunk %d found at offset %d%s...", hno, offset, fuzz == 0 and "" or string.format(" (fuzz %d)", fuzz)))
431 end
432 h.startsrc = location
433 h.starttgt = h.starttgt + offset
434 for _ = 1, fuzz do
435 table.remove(h.text, 1)
436 table.remove(h.text, #h.text)
437 end
438 return true
439 end
440 lineno = i
441 end
442 end
443 return false
444end
445
446local function load_file(filename)
447 local fp = assert(io.open(filename))
448 local file = {}
449 local readline = file_lines(fp)
450 while true do
451 local line = readline()
452 if not line then break end
453 table.insert(file, line)
454 end
455 fp:close()
456 return file
457end
458
459local function find_hunks(file, hunks)
460 for hno, h in ipairs(hunks) do
461 find_hunk(file, h, hno)
462 end
463end
464
465local function check_patched(file, hunks)
466 local lineno = 1
467 local _, err = pcall(function()
468 if #file == 0 then
469 error('nomatch', 0)
470 end
471 for hno, h in ipairs(hunks) do
472
473 if #file < h.starttgt then
474 error('nomatch', 0)
475 end
476 lineno = h.starttgt
477 for _, hline in ipairs(h.text) do
478
479 if not startswith(hline, "-") and not startswith(hline, "\\") then
480 local line = file[lineno]
481 lineno = lineno + 1
482 if #line == 0 then
483 error('nomatch', 0)
484 end
485 if endlstrip(line) ~= endlstrip(hline:sub(2)) then
486 warning(string.format("file is not patched - failed hunk: %d", hno))
487 error('nomatch', 0)
488 end
489 end
490 end
491 end
492 end)
493
494 return err ~= 'nomatch'
495end
496
497local function patch_hunks(srcname, tgtname, hunks)
498 local src = assert(io.open(srcname, "rb"))
499 local tgt = assert(io.open(tgtname, "wb"))
500
501 local src_readline = file_lines(src)
502
503
504
505
506
507
508
509 local srclineno = 1
510 local lineends = { ['\n'] = 0, ['\r\n'] = 0, ['\r'] = 0 }
511 for hno, h in ipairs(hunks) do
512 debug(string.format("processing hunk %d for file %s", hno, tgtname))
513
514 while srclineno < h.startsrc do
515 local line = src_readline()
516
517 if endswith(line, "\r\n") then
518 lineends["\r\n"] = lineends["\r\n"] + 1
519 elseif endswith(line, "\n") then
520 lineends["\n"] = lineends["\n"] + 1
521 elseif endswith(line, "\r") then
522 lineends["\r"] = lineends["\r"] + 1
523 end
524 tgt:write(line)
525 srclineno = srclineno + 1
526 end
527
528 for _, hline in ipairs(h.text) do
529
530 if startswith(hline, "-") or startswith(hline, "\\") then
531 src_readline()
532 srclineno = srclineno + 1
533 else
534 if not startswith(hline, "+") then
535 src_readline()
536 srclineno = srclineno + 1
537 end
538 local line2write = hline:sub(2)
539
540 local sum = 0
541 for _, v in pairs(lineends) do if v > 0 then sum = sum + 1 end end
542 if sum == 1 then
543 local newline
544 for k, v in pairs(lineends) do if v ~= 0 then newline = k end end
545 tgt:write(endlstrip(line2write) .. newline)
546 else
547 tgt:write(line2write)
548 end
549 end
550 end
551 end
552 for line in src_readline do
553 tgt:write(line)
554 end
555 tgt:close()
556 src:close()
557 return true
558end
559
560local function strip_dirs(filename, strip)
561 if strip == nil then return filename end
562 for _ = 1, strip do
563 filename = filename:gsub("^[^/]*/", "")
564 end
565 return filename
566end
567
568local function write_new_file(filename, hunk)
569 local fh = io.open(filename, "wb")
570 if not fh then return false end
571 for _, hline in ipairs(hunk.text) do
572 local c = hline:sub(1, 1)
573 if c ~= "+" and c ~= "-" and c ~= " " then
574 return false, "malformed patch"
575 end
576 fh:write(hline:sub(2))
577 end
578 fh:close()
579 return true
580end
581
582local function patch_file(source, target, epoch, hunks, strip, create_delete)
583 local create_file = false
584 if create_delete then
585 local is_src_epoch = epoch and #hunks == 1 and hunks[1].startsrc == 0 and hunks[1].linessrc == 0
586 if is_src_epoch or source == "/dev/null" then
587 info(string.format("will create %s", target))
588 create_file = true
589 end
590 end
591 if create_file then
592 return write_new_file(fs.absolute_name(strip_dirs(target, strip)), hunks[1])
593 end
594 source = strip_dirs(source, strip)
595 local f2patch = source
596 if not exists(f2patch) then
597 f2patch = strip_dirs(target, strip)
598 f2patch = fs.absolute_name(f2patch)
599 if not exists(f2patch) then
600 warning(string.format("source/target file does not exist\n--- %s\n+++ %s",
601 source, f2patch))
602 return false
603 end
604 end
605
606 if not isfile() then
607 warning(string.format("not a file - %s", f2patch))
608 return false
609 end
610
611 source = f2patch
612
613
614 local file = load_file(source)
615 local hunkno = 1
616 local hunk = hunks[hunkno]
617 local hunkfind = {}
618 local validhunks = 0
619 local canpatch = false
620 local hunklineno
621 if not file then
622 return nil, "failed reading file " .. source
623 end
624
625 if create_delete then
626 if epoch and #hunks == 1 and hunks[1].starttgt == 0 and hunks[1].linestgt == 0 then
627 local ok = os.remove(source)
628 if not ok then
629 return false
630 end
631 info(string.format("successfully removed %s", source))
632 return true
633 end
634 end
635
636 find_hunks(file, hunks)
637
638 local function process_line(line, lineno)
639 if not hunk or lineno < hunk.startsrc then
640 return false
641 end
642 if lineno == hunk.startsrc then
643 hunkfind = {}
644 for _, x in ipairs(hunk.text) do
645 if x:sub(1, 1) == ' ' or x:sub(1, 1) == '-' then
646 hunkfind[#hunkfind + 1] = endlstrip(x:sub(2))
647 end
648 end
649 hunklineno = 1
650
651
652 end
653
654 if lineno < hunk.startsrc + #hunkfind - 1 then
655 if endlstrip(line) == hunkfind[hunklineno] then
656 hunklineno = hunklineno + 1
657 else
658 debug(string.format("hunk no.%d doesn't match source file %s",
659 hunkno, source))
660
661 hunkno = hunkno + 1
662 if hunkno <= #hunks then
663 hunk = hunks[hunkno]
664 return false
665 else
666 return true
667 end
668 end
669 end
670
671 if lineno == hunk.startsrc + #hunkfind - 1 then
672 debug(string.format("file %s hunk no.%d -- is ready to be patched",
673 source, hunkno))
674 hunkno = hunkno + 1
675 validhunks = validhunks + 1
676 if hunkno <= #hunks then
677 hunk = hunks[hunkno]
678 else
679 if validhunks == #hunks then
680
681 canpatch = true
682 return true
683 end
684 end
685 end
686 return false
687 end
688
689 local done = false
690 for lineno, line in ipairs(file) do
691 done = process_line(line, lineno)
692 if done then
693 break
694 end
695 end
696 if not done then
697 if hunkno <= #hunks and not create_file then
698 warning(string.format("premature end of source file %s at hunk %d",
699 source, hunkno))
700 return false
701 end
702 end
703 if validhunks < #hunks then
704 if check_patched(file, hunks) then
705 warning(string.format("already patched %s", source))
706 elseif not create_file then
707 warning(string.format("source file is different - %s", source))
708 return false
709 end
710 end
711 if not canpatch then
712 return true
713 end
714 local backupname = source .. ".orig"
715 if exists(backupname) then
716 warning(string.format("can't backup original file to %s - aborting",
717 backupname))
718 return false
719 end
720 local ok = os.rename(source, backupname)
721 if not ok then
722 warning(string.format("failed backing up %s when patching", source))
723 return false
724 end
725 patch_hunks(backupname, source, hunks)
726 info(string.format("successfully patched %s", source))
727 os.remove(backupname)
728 return true
729end
730
731function patch.apply_patch(the_patch, strip, create_delete)
732 local all_ok = true
733 local total = #the_patch.source
734 for fileno, source in ipairs(the_patch.source) do
735 local target = the_patch.target[fileno]
736 local hunks = the_patch.hunks[fileno]
737 local epoch = the_patch.epoch[fileno]
738 info(string.format("processing %d/%d:\t %s", fileno, total, source))
739 local ok = patch_file(source, target, epoch, hunks, strip, create_delete)
740 all_ok = all_ok and ok
741 end
742
743 return all_ok
744end
745
746return patch
diff --git a/src/luarocks/tools/tar.lua b/src/luarocks/tools/tar.lua
new file mode 100644
index 00000000..25e1d0ec
--- /dev/null
+++ b/src/luarocks/tools/tar.lua
@@ -0,0 +1,210 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local math = _tl_compat and _tl_compat.math or math; local string = _tl_compat and _tl_compat.string or string
2
3local tar = {Header = {}, }
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24local fs = require("luarocks.fs")
25local dir = require("luarocks.dir")
26local fun = require("luarocks.fun")
27
28
29
30local blocksize = 512
31
32local function get_typeflag(flag)
33 if flag == "0" or flag == "\0" then return "file"
34 elseif flag == "1" then return "link"
35 elseif flag == "2" then return "symlink"
36 elseif flag == "3" then return "character"
37 elseif flag == "4" then return "block"
38 elseif flag == "5" then return "directory"
39 elseif flag == "6" then return "fifo"
40 elseif flag == "7" then return "contiguous"
41 elseif flag == "x" then return "next file"
42 elseif flag == "g" then return "global extended header"
43 elseif flag == "L" then return "long name"
44 elseif flag == "K" then return "long link name"
45 end
46 return "unknown"
47end
48
49local function octal_to_number(octal)
50 local exp = 0
51 local number = 0
52 octal = octal:gsub("%s", "")
53 for i = #octal, 1, -1 do
54 local digit = math.tointeger(octal:sub(i, i))
55 if not digit then
56 break
57 end
58 number = number + (digit * math.tointeger(8 ^ exp))
59 exp = exp + 1
60 end
61 return number
62end
63
64local function checksum_header(block)
65 local sum = 256
66
67 if block:byte(1) == 0 then
68 return 0
69 end
70
71 for i = 1, 148 do
72 local b = block:byte(i) or 0
73 sum = sum + b
74 end
75 for i = 157, 500 do
76 local b = block:byte(i) or 0
77 sum = sum + b
78 end
79
80 return sum
81end
82
83local function nullterm(s)
84 return s:match("^[^%z]*")
85end
86
87local function read_header_block(block)
88 local header = {}
89 header.name = nullterm(block:sub(1, 100))
90 header.mode = nullterm(block:sub(101, 108)):gsub(" ", "")
91 header.uid = octal_to_number(nullterm(block:sub(109, 116)))
92 header.gid = octal_to_number(nullterm(block:sub(117, 124)))
93 header.size = octal_to_number(nullterm(block:sub(125, 136)))
94 header.mtime = octal_to_number(nullterm(block:sub(137, 148)))
95 header.chksum = octal_to_number(nullterm(block:sub(149, 156)))
96 header.typeflag = get_typeflag(block:sub(157, 157))
97 header.linkname = nullterm(block:sub(158, 257))
98 header.magic = block:sub(258, 263)
99 header.version = block:sub(264, 265)
100 header.uname = nullterm(block:sub(266, 297))
101 header.gname = nullterm(block:sub(298, 329))
102 header.devmajor = octal_to_number(nullterm(block:sub(330, 337)))
103 header.devminor = octal_to_number(nullterm(block:sub(338, 345)))
104 header.prefix = block:sub(346, 500)
105
106
107
108
109
110
111
112 if header.typeflag == "unknown" then
113 if checksum_header(block) ~= header.chksum then
114 return false, "Failed header checksum"
115 end
116 end
117 return header
118end
119
120function tar.untar(filename, destdir)
121
122 local tar_handle = io.open(filename, "rb")
123 if not tar_handle then return nil, "Error opening file " .. filename end
124
125 local long_name, long_link_name
126 local ok, err
127 local make_dir = fun.memoize(fs.make_dir)
128 while true do
129 local block
130 repeat
131 block = tar_handle:read(blocksize)
132 until (not block) or block:byte(1) > 0
133 if not block then break end
134 if #block < blocksize then
135 ok, err = nil, "Invalid block size -- corrupted file?"
136 break
137 end
138
139 local headerp
140 headerp, err = read_header_block(block)
141 if not headerp then
142 ok = false
143 break
144 end
145 local header = headerp
146 local file_data = ""
147 if header.size > 0 then
148 local nread = math.ceil(header.size / blocksize) * blocksize
149 file_data = tar_handle:read(header.size)
150 if nread > header.size then
151 tar_handle:seek("cur", nread - header.size)
152 end
153 end
154
155 if header.typeflag == "long name" then
156 long_name = nullterm(file_data)
157 elseif header.typeflag == "long link name" then
158 long_link_name = nullterm(file_data)
159 else
160 if long_name then
161 header.name = long_name
162 long_name = nil
163 end
164 if long_link_name then
165 header.name = long_link_name
166 long_link_name = nil
167 end
168 end
169 local pathname = dir.path(destdir, header.name)
170 pathname = fs.absolute_name(pathname)
171 if header.typeflag == "directory" then
172 ok, err = make_dir(pathname)
173 if not ok then
174 break
175 end
176 elseif header.typeflag == "file" then
177 local dirname = dir.dir_name(pathname)
178 if dirname ~= "" then
179 ok, err = make_dir(dirname)
180 if not ok then
181 break
182 end
183 end
184 local file_handle
185 file_handle, err = io.open(pathname, "wb")
186 if not file_handle then
187 ok = nil
188 break
189 end
190 file_handle:write(file_data)
191 file_handle:close()
192 fs.set_time(pathname, header.mtime)
193 if header.mode:match("[75]") then
194 fs.set_permissions(pathname, "exec", "all")
195 else
196 fs.set_permissions(pathname, "read", "all")
197 end
198 end
199
200
201
202
203
204
205 end
206 tar_handle:close()
207 return ok, err
208end
209
210return tar
diff --git a/src/luarocks/tools/zip.lua b/src/luarocks/tools/zip.lua
new file mode 100644
index 00000000..b54d9e86
--- /dev/null
+++ b/src/luarocks/tools/zip.lua
@@ -0,0 +1,575 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local math = _tl_compat and _tl_compat.math or math; local os = _tl_compat and _tl_compat.os or os; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local _tl_table_pack = table.pack or function(...) return { n = select("#", ...), ... } end
2
3
4local zip = {ZipHandle = {}, LocalFileHeader = {}, Zip = {}, }
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49local zlib = require("zlib")
50local fs = require("luarocks.fs")
51local fun = require("luarocks.fun")
52local dir = require("luarocks.dir")
53
54
55
56
57
58local function shr(n, m)
59 return math.floor(n / 2 ^ m)
60end
61
62local function shl(n, m)
63 return (n * 2 ^ m)
64end
65
66local function lowbits(n, m)
67 return (n % 2 ^ m)
68end
69
70local function mode_to_windowbits(mode)
71 if mode == "gzip" then
72 return 31
73 elseif mode == "zlib" then
74 return 0
75 elseif mode == "raw" then
76 return -15
77 end
78end
79
80
81
82local zlib_compress
83local zlib_uncompress
84local zlib_crc32
85if zlib._VERSION:match("^lua%-zlib") then
86 function zlib_compress(data, mode)
87 return (zlib.deflate(6, mode_to_windowbits(mode))(data, "finish"))
88 end
89
90 function zlib_uncompress(data, mode)
91 return (zlib.inflate(mode_to_windowbits(mode))(data))
92 end
93
94 function zlib_crc32(data)
95 return zlib.crc32()(data)
96 end
97elseif zlib._VERSION:match("^lzlib") then
98 function zlib_compress(data, mode)
99 return zlib.compress(data, -1, nil, mode_to_windowbits(mode))
100 end
101
102 function zlib_uncompress(data, mode)
103 return zlib.decompress(data, mode_to_windowbits(mode))
104 end
105
106 function zlib_crc32(data)
107 return zlib.crc32(zlib.crc32(), data)
108 end
109else
110 error("unknown zlib library", 0)
111end
112
113local function number_to_lestring(number, nbytes)
114 local out = {}
115 for _ = 1, nbytes do
116 local byte = number % 256
117 table.insert(out, string.char(byte))
118 number = (number - byte) / 256
119 end
120 return table.concat(out)
121end
122
123local function lestring_to_number(str)
124 local n = 0
125 local bytes = { string.byte(str, 1, #str) }
126 for b = 1, #str do
127 n = n + shl(bytes[b], (b - 1) * 8)
128 end
129 return math.floor(n)
130end
131
132local LOCAL_FILE_HEADER_SIGNATURE = number_to_lestring(0x04034b50, 4)
133local DATA_DESCRIPTOR_SIGNATURE = number_to_lestring(0x08074b50, 4)
134local CENTRAL_DIRECTORY_SIGNATURE = number_to_lestring(0x02014b50, 4)
135local END_OF_CENTRAL_DIR_SIGNATURE = number_to_lestring(0x06054b50, 4)
136
137
138
139
140
141local function zipwriter_open_new_file_in_zip(self, filename)
142 if self.in_open_file then
143 self:close_file_in_zip()
144 return nil
145 end
146 local lfh = {}
147 self.local_file_header = lfh
148 lfh.last_mod_file_time = 0
149 lfh.last_mod_file_date = 0
150 lfh.file_name_length = #filename
151 lfh.extra_field_length = 0
152 lfh.file_name = filename:gsub("\\", "/")
153 lfh.external_attr = shl(493, 16)
154 self.in_open_file = true
155 return true
156end
157
158
159
160
161
162local function zipwriter_write_file_in_zip(self, data)
163 if not self.in_open_file then
164 return nil
165 end
166 local lfh = self.local_file_header
167 local compressed = zlib_compress(data, "raw")
168 lfh.crc32 = zlib_crc32(data)
169 lfh.compressed_size = #compressed
170 lfh.uncompressed_size = #data
171 self.data = compressed
172 return true
173end
174
175
176
177
178local function zipwriter_close_file_in_zip(self)
179 local zh = self.ZipHandle
180
181 if not self.in_open_file then
182 return nil
183 end
184
185
186 local lfh = self.local_file_header
187 lfh.offset = zh:seek()
188 zh:write(LOCAL_FILE_HEADER_SIGNATURE)
189 zh:write(number_to_lestring(20, 2))
190 zh:write(number_to_lestring(4, 2))
191 zh:write(number_to_lestring(8, 2))
192 zh:write(number_to_lestring(lfh.last_mod_file_time, 2))
193 zh:write(number_to_lestring(lfh.last_mod_file_date, 2))
194 zh:write(number_to_lestring(lfh.crc32, 4))
195 zh:write(number_to_lestring(lfh.compressed_size, 4))
196 zh:write(number_to_lestring(lfh.uncompressed_size, 4))
197 zh:write(number_to_lestring(lfh.file_name_length, 2))
198 zh:write(number_to_lestring(lfh.extra_field_length, 2))
199 zh:write(lfh.file_name)
200
201
202 zh:write(self.data)
203
204
205 zh:write(DATA_DESCRIPTOR_SIGNATURE)
206 zh:write(number_to_lestring(lfh.crc32, 4))
207 zh:write(number_to_lestring(lfh.compressed_size, 4))
208 zh:write(number_to_lestring(lfh.uncompressed_size, 4))
209
210 table.insert(self.files, lfh)
211 self.in_open_file = false
212
213 return true
214end
215
216
217
218local function zipwriter_add(self, file)
219 local fin
220 local ok, err = self:open_new_file_in_zip(file)
221 if not ok then
222 err = "error in opening " .. file .. " in zipfile"
223 else
224 fin = io.open(fs.absolute_name(file), "rb")
225 if not fin then
226 ok = false
227 err = "error opening " .. file .. " for reading"
228 end
229 end
230 if ok then
231 local data = fin:read("*a")
232 if not data then
233 err = "error reading " .. file
234 ok = false
235 else
236 ok = self:write_file_in_zip(data)
237 if not ok then
238 err = "error in writing " .. file .. " in the zipfile"
239 end
240 end
241 end
242 if fin then
243 fin:close()
244 end
245 if ok then
246 ok = self:close_file_in_zip()
247 if not ok then
248 err = "error in writing " .. file .. " in the zipfile"
249 end
250 end
251 return ok == true, err
252end
253
254
255
256
257local function zipwriter_close(self)
258 local zh = self.ZipHandle
259
260 local central_directory_offset = zh:seek()
261
262 local size_of_central_directory = 0
263
264 for _, lfh in ipairs(self.files) do
265 zh:write(CENTRAL_DIRECTORY_SIGNATURE)
266 zh:write(number_to_lestring(3, 2))
267 zh:write(number_to_lestring(20, 2))
268 zh:write(number_to_lestring(0, 2))
269 zh:write(number_to_lestring(8, 2))
270 zh:write(number_to_lestring(lfh.last_mod_file_time, 2))
271 zh:write(number_to_lestring(lfh.last_mod_file_date, 2))
272 zh:write(number_to_lestring(lfh.crc32, 4))
273 zh:write(number_to_lestring(lfh.compressed_size, 4))
274 zh:write(number_to_lestring(lfh.uncompressed_size, 4))
275 zh:write(number_to_lestring(lfh.file_name_length, 2))
276 zh:write(number_to_lestring(lfh.extra_field_length, 2))
277 zh:write(number_to_lestring(0, 2))
278 zh:write(number_to_lestring(0, 2))
279 zh:write(number_to_lestring(0, 2))
280 zh:write(number_to_lestring(lfh.external_attr, 4))
281 zh:write(number_to_lestring(lfh.offset, 4))
282 zh:write(lfh.file_name)
283 size_of_central_directory = size_of_central_directory + 46 + lfh.file_name_length
284 end
285
286
287 zh:write(END_OF_CENTRAL_DIR_SIGNATURE)
288 zh:write(number_to_lestring(0, 2))
289 zh:write(number_to_lestring(0, 2))
290 zh:write(number_to_lestring(#self.files, 2))
291 zh:write(number_to_lestring(#self.files, 2))
292 zh:write(number_to_lestring(size_of_central_directory, 4))
293 zh:write(number_to_lestring(central_directory_offset, 4))
294 zh:write(number_to_lestring(0, 2))
295 zh:close()
296
297 return true
298end
299
300
301
302
303function zip.new_zipwriter(name)
304
305 local zw = {}
306
307 zw.ZipHandle = io.open(fs.absolute_name(name), "wb")
308 if not zw.ZipHandle then
309 return nil
310 end
311 zw.files = {}
312 zw.in_open_file = false
313
314 zw.add = zipwriter_add
315 zw.close = zipwriter_close
316 zw.open_new_file_in_zip = zipwriter_open_new_file_in_zip
317 zw.write_file_in_zip = zipwriter_write_file_in_zip
318 zw.close_file_in_zip = zipwriter_close_file_in_zip
319
320 return zw
321end
322
323
324
325
326
327
328
329function zip.zip(zipfile, ...)
330 local zw = zip.new_zipwriter(zipfile)
331 if not zw then
332 return nil, "error opening " .. zipfile
333 end
334
335 local args = _tl_table_pack(...)
336 local ok, err
337 for i = 1, args.n do
338 local file = args[i]
339 if fs.is_dir(file) then
340 for _, entry in ipairs(fs.find(file)) do
341 local fullname = dir.path(file, entry)
342 if fs.is_file(fullname) then
343 ok, err = zw:add(fullname)
344 if not ok then break end
345 end
346 end
347 else
348 ok, err = zw:add(file)
349 if not ok then break end
350 end
351 end
352
353 zw:close()
354 return ok, err
355end
356
357
358local function ziptime_to_luatime(ztime, zdate)
359 local date = {
360 year = shr(zdate, 9) + 1980,
361 month = shr(lowbits(zdate, 9), 5),
362 day = lowbits(zdate, 5),
363 hour = shr(ztime, 11),
364 min = shr(lowbits(ztime, 11), 5),
365 sec = lowbits(ztime, 5) * 2,
366 }
367
368 if date.month == 0 then date.month = 1 end
369 if date.day == 0 then date.day = 1 end
370
371 return date
372end
373
374local function read_file_in_zip(zh, cdr)
375 local sig = zh:read(4)
376 if sig ~= LOCAL_FILE_HEADER_SIGNATURE then
377 return nil, "failed reading Local File Header signature"
378 end
379
380
381
382 zh:seek("cur", 22)
383 local file_name_length = lestring_to_number(zh:read(2))
384 local extra_field_length = lestring_to_number(zh:read(2))
385 zh:read(file_name_length)
386 zh:read(extra_field_length)
387
388 local data = zh:read(cdr.compressed_size)
389
390 local uncompressed
391 if cdr.compression_method == 8 then
392 uncompressed = zlib_uncompress(data, "raw")
393 elseif cdr.compression_method == 0 then
394 uncompressed = data
395 else
396 return nil, "unknown compression method " .. cdr.compression_method
397 end
398
399 if #uncompressed ~= cdr.uncompressed_size then
400 return nil, "uncompressed size doesn't match"
401 end
402 if cdr.crc32 ~= zlib_crc32(uncompressed) then
403 return nil, "crc32 failed (expected " .. cdr.crc32 .. ") - data: " .. uncompressed
404 end
405
406 return uncompressed
407end
408
409local function process_end_of_central_dir(zh)
410 local at, errend = zh:seek("end", -22)
411 if not at then
412 return nil, errend
413 end
414
415 while true do
416 local sig = zh:read(4)
417 if sig == END_OF_CENTRAL_DIR_SIGNATURE then
418 break
419 end
420 at = at - 1
421 local at1 = zh:seek("set", at)
422 if at1 ~= at then
423 return nil, "Could not find End of Central Directory signature"
424 end
425 end
426
427
428
429
430
431 zh:seek("cur", 6)
432
433 local central_directory_entries = lestring_to_number(zh:read(2))
434
435
436 zh:seek("cur", 4)
437
438 local central_directory_offset = lestring_to_number(zh:read(4))
439
440 return central_directory_entries, central_directory_offset
441end
442
443local function process_central_dir(zh, cd_entries)
444
445 local files = {}
446
447 for i = 1, cd_entries do
448 local sig = zh:read(4)
449 if sig ~= CENTRAL_DIRECTORY_SIGNATURE then
450 return nil, "failed reading Central Directory signature"
451 end
452
453 local cdr = {}
454 files[i] = cdr
455
456 cdr.version_made_by = lestring_to_number(zh:read(2))
457 cdr.version_needed = lestring_to_number(zh:read(2))
458 cdr.bitflag = lestring_to_number(zh:read(2))
459 cdr.compression_method = lestring_to_number(zh:read(2))
460 cdr.last_mod_file_time = lestring_to_number(zh:read(2))
461 cdr.last_mod_file_date = lestring_to_number(zh:read(2))
462 cdr.last_mod_luatime = ziptime_to_luatime(cdr.last_mod_file_time, cdr.last_mod_file_date)
463 cdr.crc32 = lestring_to_number(zh:read(4))
464 cdr.compressed_size = lestring_to_number(zh:read(4))
465 cdr.uncompressed_size = lestring_to_number(zh:read(4))
466 cdr.file_name_length = lestring_to_number(zh:read(2))
467 cdr.extra_field_length = lestring_to_number(zh:read(2))
468 cdr.file_comment_length = lestring_to_number(zh:read(2))
469 cdr.disk_number_start = lestring_to_number(zh:read(2))
470 cdr.internal_attr = lestring_to_number(zh:read(2))
471 cdr.external_attr = lestring_to_number(zh:read(4))
472 cdr.offset = lestring_to_number(zh:read(4))
473 cdr.file_name = zh:read(cdr.file_name_length)
474 cdr.extra_field = zh:read(cdr.extra_field_length)
475 cdr.file_comment = zh:read(cdr.file_comment_length)
476 end
477 return files
478end
479
480
481
482
483
484function zip.unzip(zipfile)
485 zipfile = fs.absolute_name(zipfile)
486 local zh, erropen = io.open(zipfile, "rb")
487 if not zh then
488 return nil, erropen
489 end
490
491 local cd_entries, cd_offset = process_end_of_central_dir(zh)
492 if type(cd_offset) == "string" then
493 return nil, cd_offset
494 end
495
496 local okseek, errseek = zh:seek("set", cd_offset)
497 if not okseek then
498 return nil, errseek
499 end
500
501 local files, errproc = process_central_dir(zh, cd_entries)
502 if not files then
503 return nil, errproc
504 end
505
506 for _, cdr in ipairs(files) do
507 local file = cdr.file_name
508 if file:sub(#file) == "/" then
509 local okmake, errmake = fs.make_dir(dir.path(fs.current_dir(), file))
510 if not okmake then
511 return nil, errmake
512 end
513 else
514 local base = dir.dir_name(file)
515 if base ~= "" then
516 base = dir.path(fs.current_dir(), base)
517 if not fs.is_dir(base) then
518 local okmake, errmake = fs.make_dir(base)
519 if not okmake then
520 return nil, errmake
521 end
522 end
523 end
524
525 local okseek2, errseek2 = zh:seek("set", cdr.offset)
526 if not okseek2 then
527 return nil, errseek2
528 end
529
530 local contents, err = read_file_in_zip(zh, cdr)
531 if not contents then
532 return nil, err
533 end
534 local pathname = dir.path(fs.current_dir(), file)
535 local wf, erropen2 = io.open(pathname, "wb")
536 if not wf then
537 zh:close()
538 return nil, erropen2
539 end
540 wf:write(contents)
541 wf:close()
542
543 if cdr.external_attr > 0 then
544 fs.set_permissions(pathname, "exec", "all")
545 else
546 fs.set_permissions(pathname, "read", "all")
547 end
548 fs.set_time(pathname, cdr.last_mod_luatime)
549 end
550 end
551 zh:close()
552 return true
553end
554
555function zip.gzip(input_filename, output_filename)
556
557 if not output_filename then
558 output_filename = input_filename .. ".gz"
559 end
560
561 local fn = fun.partial(fun.flip(zlib_compress), "gzip")
562 return fs.filter_file(fn, input_filename, output_filename)
563end
564
565function zip.gunzip(input_filename, output_filename)
566
567 if not output_filename then
568 output_filename = input_filename:gsub("%.gz$", "")
569 end
570
571 local fn = fun.partial(fun.flip(zlib_uncompress), "gzip")
572 return fs.filter_file(fn, input_filename, output_filename)
573end
574
575return zip
diff --git a/src/luarocks/type/manifest.lua b/src/luarocks/type/manifest.lua
new file mode 100644
index 00000000..3f3be728
--- /dev/null
+++ b/src/luarocks/type/manifest.lua
@@ -0,0 +1,92 @@
1local type_manifest = {}
2
3
4
5
6local type_check = require("luarocks.type_check")
7
8local manifest_formats = type_check.declare_schemas({
9 ["3.0"] = {
10 fields = {
11 repository = {
12 _mandatory = true,
13
14 _any = {
15
16 _any = {
17
18 _any = {
19 fields = {
20 arch = { _type = "string", _mandatory = true },
21 modules = { _any = { _type = "string" } },
22 commands = { _any = { _type = "string" } },
23 dependencies = { _any = { _type = "string" } },
24
25 },
26 },
27 },
28 },
29 },
30 modules = {
31 _mandatory = true,
32
33 _any = {
34
35 _any = { _type = "string" },
36 },
37 },
38 commands = {
39 _mandatory = true,
40
41 _any = {
42
43 _any = { _type = "string" },
44 },
45 },
46 dependencies = {
47
48 _any = {
49
50 _any = {
51
52 _any = {
53 fields = {
54 name = { _type = "string" },
55 namespace = { _type = "string" },
56 constraints = {
57 _any = {
58 fields = {
59 no_upgrade = { _type = "boolean" },
60 op = { _type = "string" },
61 version = {
62 fields = {
63 string = { _type = "string" },
64 },
65 _any = { _type = "number" },
66 },
67 },
68 },
69 },
70 },
71 },
72 },
73 },
74 },
75 },
76 },
77})
78
79
80
81
82
83
84
85function type_manifest.check(manifest, globals)
86 local format = manifest_formats["3.0"]
87 local ok, err = type_check.check_undeclared_globals(globals, format)
88 if not ok then return nil, err end
89 return type_check.type_check_table("3.0", manifest, format, "")
90end
91
92return type_manifest
diff --git a/src/luarocks/type/rockspec.lua b/src/luarocks/type/rockspec.lua
new file mode 100644
index 00000000..cd4044f6
--- /dev/null
+++ b/src/luarocks/type/rockspec.lua
@@ -0,0 +1,261 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local type_rockspec = {}
2
3
4
5
6
7
8
9
10local type_check = require("luarocks.type_check")
11
12
13
14type_rockspec.rockspec_format = "3.0"
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29local rockspec_formats, versions = type_check.declare_schemas({
30 ["1.0"] = {
31 fields = {
32 rockspec_format = { _type = "string" },
33 package = { _type = "string", _mandatory = true },
34 version = { _type = "string", _pattern = "[%w.]+-[%d]+", _mandatory = true },
35 description = {
36 fields = {
37 summary = { _type = "string" },
38 detailed = { _type = "string" },
39 homepage = { _type = "string" },
40 license = { _type = "string" },
41 maintainer = { _type = "string" },
42 },
43 },
44 dependencies = {
45 fields = {
46 platforms = type_check.MAGIC_PLATFORMS,
47 },
48 _any = {
49 _type = "string",
50 _name = "a valid dependency string",
51 _pattern = "%s*([a-zA-Z0-9][a-zA-Z0-9%.%-%_]*)%s*([^/]*)",
52 },
53 },
54 supported_platforms = {
55 _any = { _type = "string" },
56 },
57 external_dependencies = {
58 fields = {
59 platforms = type_check.MAGIC_PLATFORMS,
60 },
61 _any = {
62 fields = {
63 program = { _type = "string" },
64 header = { _type = "string" },
65 library = { _type = "string" },
66 },
67 },
68 },
69 source = {
70 _mandatory = true,
71 fields = {
72 platforms = type_check.MAGIC_PLATFORMS,
73 url = { _type = "string", _mandatory = true },
74 md5 = { _type = "string" },
75 file = { _type = "string" },
76 dir = { _type = "string" },
77 tag = { _type = "string" },
78 branch = { _type = "string" },
79 module = { _type = "string" },
80 cvs_tag = { _type = "string" },
81 cvs_module = { _type = "string" },
82 },
83 },
84 build = {
85 fields = {
86 platforms = type_check.MAGIC_PLATFORMS,
87 type = { _type = "string" },
88 install = {
89 fields = {
90 lua = {
91 _more = true,
92 },
93 lib = {
94 _more = true,
95 },
96 conf = {
97 _more = true,
98 },
99 bin = {
100 _more = true,
101 },
102 },
103 },
104 copy_directories = {
105 _any = { _type = "string" },
106 },
107 },
108 _more = true,
109 _mandatory = true,
110 },
111 hooks = {
112 fields = {
113 platforms = type_check.MAGIC_PLATFORMS,
114 post_install = { _type = "string" },
115 },
116 },
117 },
118 },
119
120 ["1.1"] = {
121 fields = {
122 deploy = {
123 fields = {
124 wrap_bin_scripts = { _type = "boolean" },
125 },
126 },
127 },
128 },
129
130 ["3.0"] = {
131 fields = {
132 description = {
133 fields = {
134 labels = {
135 _any = { _type = "string" },
136 },
137 issues_url = { _type = "string" },
138 },
139 },
140 dependencies = {
141 _any = {
142 _pattern = "%s*([a-zA-Z0-9%.%-%_]*/?[a-zA-Z0-9][a-zA-Z0-9%.%-%_]*)%s*([^/]*)",
143 },
144 },
145 build_dependencies = {
146 fields = {
147 platforms = type_check.MAGIC_PLATFORMS,
148 },
149 _any = {
150 _type = "string",
151 _name = "a valid dependency string",
152 _pattern = "%s*([a-zA-Z0-9%.%-%_]*/?[a-zA-Z0-9][a-zA-Z0-9%.%-%_]*)%s*([^/]*)",
153 },
154 },
155 test_dependencies = {
156 fields = {
157 platforms = type_check.MAGIC_PLATFORMS,
158 },
159 _any = {
160 _type = "string",
161 _name = "a valid dependency string",
162 _pattern = "%s*([a-zA-Z0-9%.%-%_]*/?[a-zA-Z0-9][a-zA-Z0-9%.%-%_]*)%s*([^/]*)",
163 },
164 },
165 build = {
166 _mandatory = false,
167 },
168 test = {
169 fields = {
170 platforms = type_check.MAGIC_PLATFORMS,
171 type = { _type = "string" },
172 },
173 _more = true,
174 },
175 },
176 },
177})
178
179
180
181
182
183
184
185
186
187type_rockspec.order = {
188 "rockspec_format",
189 "package",
190 "version",
191 "source",
192 "description",
193 "supported_platforms",
194 "dependencies",
195 "build_dependencies",
196 "external_dependencies",
197 "build",
198 "test_dependencies",
199 "test",
200 "hooks",
201 sub_orders = {
202 ["source"] = { "url", "tag", "branch", "md5" },
203 ["description"] = { "summary", "detailed", "homepage", "license" },
204 ["build"] = { "type", "modules", "copy_directories", "platforms" },
205 ["test"] = { "type" },
206 },
207}
208
209local function check_rockspec_using_version(rockspec, globals, version)
210 local schema = rockspec_formats[version]
211 if not schema then
212 return nil, "unknown rockspec format " .. version
213 end
214 local ok, err = type_check.check_undeclared_globals(globals, schema)
215 if ok then
216 ok, err = type_check.type_check_table(version, rockspec, schema, "")
217 end
218 if ok then
219 return true
220 else
221 return nil, err
222 end
223end
224
225
226
227
228
229
230
231function type_rockspec.check(rockspec, globals)
232
233 local version = rockspec.rockspec_format or "1.0"
234 local ok, err = check_rockspec_using_version(rockspec, globals, version)
235 if ok then
236 return true
237 end
238
239
240
241
242 local found = false
243 for _, v in ipairs(versions) do
244 if not found then
245 if v == version then
246 found = true
247 end
248 else
249 local v_ok = check_rockspec_using_version(rockspec, globals, v)
250 if v_ok then
251 return nil, err .. " (using rockspec format " .. version .. " -- " ..
252 [[adding 'rockspec_format = "]] .. v .. [["' to the rockspec ]] ..
253 [[will fix this)]]
254 end
255 end
256 end
257
258 return nil, err .. " (using rockspec format " .. version .. ")"
259end
260
261return type_rockspec
diff --git a/src/luarocks/type_check.lua b/src/luarocks/type_check.lua
new file mode 100644
index 00000000..23305baf
--- /dev/null
+++ b/src/luarocks/type_check.lua
@@ -0,0 +1,237 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2local type_check = {TableSchema = {}, }
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19local cfg = require("luarocks.core.cfg")
20local fun = require("luarocks.fun")
21local util = require("luarocks.util")
22local vers = require("luarocks.core.vers")
23
24
25
26
27
28
29type_check.MAGIC_PLATFORMS = {}
30
31do
32 local function fill_in_version(tbl, version)
33
34 if not tbl.fields then
35 return
36 end
37
38 for _, v in pairs(tbl.fields) do
39 if type(v) == "table" then
40 if v._version == nil then
41 v._version = version
42 end
43 fill_in_version(v)
44 end
45 end
46 end
47
48 local function expand_magic_platforms(tbl)
49 for k, v in pairs(tbl.fields) do
50 if v == type_check.MAGIC_PLATFORMS then
51 tbl.fields[k] = {
52 _any = util.deep_copy(tbl),
53 }
54 tbl.fields[k]._any.fields[k] = nil
55 expand_magic_platforms(v)
56 end
57 end
58 end
59
60
61
62
63
64
65 function type_check.declare_schemas(inputs)
66 local schemas = {}
67 local parent_version
68
69 local versions = fun.reverse_in(fun.sort_in(util.keys(inputs), vers.compare_versions))
70
71 for _, version in ipairs(versions) do
72 local schema = inputs[version]
73 if parent_version then
74 local copy = util.deep_copy(schemas[parent_version])
75 util.deep_merge(copy, schema)
76 schema = copy
77 end
78 fill_in_version(schema, version)
79 expand_magic_platforms(schema)
80 parent_version = version
81 schemas[version] = schema
82 end
83
84 return schemas, versions
85 end
86end
87
88
89
90local function check_version(version, typetbl, context)
91 local typetbl_version = typetbl._version or "1.0"
92 if vers.compare_versions(typetbl_version, version) then
93 if context == "" then
94 return nil, "Invalid rockspec_format version number in rockspec? Please fix rockspec accordingly."
95 else
96 return nil, context .. " is not supported in rockspec format " .. version .. " (requires version " .. typetbl_version .. "), please fix the rockspec_format field accordingly."
97 end
98 end
99 return true
100end
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116local function type_check_item(version, item, typetbl, context)
117
118 if typetbl._version and typetbl._version ~= "1.0" then
119 local ok, err = check_version(version, typetbl, context)
120 if not ok then
121 return nil, err
122 end
123 end
124
125 local expected_type = typetbl._type or "table"
126
127 if expected_type == "number" then
128 if not tonumber(item) then
129 return nil, "Type mismatch on field " .. context .. ": expected a number"
130 end
131 elseif expected_type == "string" then
132 if not (type(item) == "string") then
133 return nil, "Type mismatch on field " .. context .. ": expected a string, got " .. type(item)
134 end
135 local pattern = typetbl._pattern
136 if pattern then
137 if not item:match("^" .. pattern .. "$") then
138 local what = typetbl._name or ("'" .. pattern .. "'")
139 return nil, "Type mismatch on field " .. context .. ": invalid value '" .. item .. "' does not match " .. what
140 end
141 end
142 elseif expected_type == "table" then
143 if not (type(item) == "table") then
144 return nil, "Type mismatch on field " .. context .. ": expected a table"
145 else
146 return type_check.type_check_table(version, item, typetbl, context)
147 end
148 elseif type(item) ~= expected_type then
149 return nil, "Type mismatch on field " .. context .. ": expected " .. expected_type
150 end
151 return true
152end
153
154local function mkfield(context, field)
155 if context == "" then
156 return tostring(field)
157 elseif type(field) == "string" then
158 return context .. "." .. field
159 else
160 return context .. "[" .. tostring(field) .. "]"
161 end
162end
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186function type_check.type_check_table(version, tbl, typetbl, context)
187
188 local ok, err = check_version(version, typetbl, context)
189 if not ok then
190 return nil, err
191 end
192
193 if not typetbl.fields then
194
195 return true
196 end
197
198 for k, v in pairs(tbl) do
199 local t = typetbl.fields[tostring(k)] or typetbl._any
200 if t then
201 ok, err = type_check_item(version, v, t, mkfield(context, k))
202 if not ok then return nil, err end
203 elseif typetbl._more then
204
205 else
206 if not cfg.accept_unknown_fields then
207 return nil, "Unknown field " .. tostring(k)
208 end
209 end
210 end
211
212 for k, v in pairs(typetbl.fields) do
213 if k:sub(1, 1) ~= "_" and v._mandatory then
214 if not tbl[k] then
215 return nil, "Mandatory field " .. mkfield(context, k) .. " is missing."
216 end
217 end
218 end
219 return true
220end
221
222function type_check.check_undeclared_globals(globals, typetbl)
223 local undeclared = {}
224 for glob, _ in pairs(globals) do
225 if not (typetbl.fields[glob] or typetbl.fields["MUST_" .. glob]) then
226 table.insert(undeclared, glob)
227 end
228 end
229 if #undeclared == 1 then
230 return nil, "Unknown variable: " .. undeclared[1]
231 elseif #undeclared > 1 then
232 return nil, "Unknown variables: " .. table.concat(undeclared, ", ")
233 end
234 return true
235end
236
237return type_check
diff --git a/src/luarocks/upload/api.lua b/src/luarocks/upload/api.lua
new file mode 100644
index 00000000..ba2de943
--- /dev/null
+++ b/src/luarocks/upload/api.lua
@@ -0,0 +1,300 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local api = {Configuration = {}, Api = {}, }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21local cfg = require("luarocks.core.cfg")
22local fs = require("luarocks.fs")
23local dir = require("luarocks.dir")
24local util = require("luarocks.util")
25local persist = require("luarocks.persist")
26local multipart = require("luarocks.upload.multipart")
27local json = require("luarocks.vendor.dkjson")
28local dir_sep = package.config:sub(1, 1)
29
30
31local Api = api.Api
32
33
34
35
36
37
38local function upload_config_file()
39 if not cfg.config_files.user.file then
40 return nil
41 end
42 return (cfg.config_files.user.file:gsub("[\\/][^\\/]+$", dir_sep .. "upload_config.lua"))
43end
44
45function api.Api:load_config()
46 local upload_conf = upload_config_file()
47 if not upload_conf then return nil end
48 local config = persist.load_into_table(upload_conf)
49 return config
50end
51
52function api.Api:save_config()
53
54 local res, errraw = self:raw_method("status")
55 if not res then
56 return nil, errraw
57 end
58 local reserrors = res.errors
59 if type(reserrors) == "table" then
60 return nil, ("Server error: " .. tostring(reserrors[1]))
61 end
62 local upload_conf = upload_config_file()
63 if not upload_conf then return nil end
64 local ok, errmake = fs.make_dir(dir.dir_name(upload_conf))
65 if not ok then
66 return nil, errmake
67 end
68 persist.save_from_table(upload_conf, self.config)
69 fs.set_permissions(upload_conf, "read", "user")
70 return true
71end
72
73function api.Api:check_version()
74 if not self._server_tool_version then
75 local tool_version = cfg.upload.tool_version
76 local res, err = self:request(tostring(self.config.server) .. "/api/tool_version", {
77 current = tool_version,
78 })
79 if not res then
80 return nil, err
81 end
82 if not res.version then
83 return nil, "failed to fetch tool version"
84 end
85 self._server_tool_version = tostring(res.version)
86 if res.force_update then
87 return nil, "Your upload client is too out of date to continue, please upgrade LuaRocks."
88 end
89 if res.version ~= tool_version then
90 util.warning("your LuaRocks is out of date, consider upgrading.")
91 end
92 end
93 return true
94end
95
96function api.Api:method(path, ...)
97 local res, err = self:raw_method(path, ...)
98 if not res then
99 return nil, err
100 end
101 local reserrors = res.errors
102 if type(reserrors) == "table" then
103 if reserrors[1] == "Invalid key" then
104 return nil, reserrors[1] .. " (use the --api-key flag to change)"
105 end
106 local msg = table.concat(reserrors, ", ")
107 return nil, "API Failed: " .. msg
108 end
109 return res
110end
111
112function api.Api:raw_method(path, ...)
113 self:check_version()
114 local url = tostring(self.config.server) .. "/api/" .. tostring(cfg.upload.api_version) .. "/" .. tostring(self.config.key) .. "/" .. path
115 return self:request(url, ...)
116end
117
118local function encode_query_string(t, sep)
119 if sep == nil then
120 sep = "&"
121 end
122 local i = 0
123 local buf = {}
124 for k, v in pairs(t) do
125 local ks, vs
126 local vf = v
127 if type(vf) == "table" then
128 ks, vs = k, vf:content()
129 else
130 ks, vs = k, vf
131 end
132 buf[i + 1] = multipart.url_escape(ks)
133 buf[i + 2] = "="
134 buf[i + 3] = multipart.url_escape(vs)
135 buf[i + 4] = sep
136 i = i + 4
137 end
138 buf[i] = nil
139 return table.concat(buf)
140end
141
142local function redact_api_url(url)
143 local urls = tostring(url)
144 return (urls:gsub(".*/api/[^/]+/[^/]+", "")) or ""
145end
146
147local ltn12_ok, ltn12 = pcall(require, "ltn12")
148if not ltn12_ok then
149
150 api.Api.request = function(self, url, params, post_params)
151 local vars = cfg.variables
152
153 if fs.which_tool("downloader") == "wget" then
154 local curl_ok, err = fs.is_tool_available(vars.CURL, "curl")
155 if not curl_ok then
156 return nil, err
157 end
158 end
159
160 if not self.config.key then
161 return nil, "Must have API key before performing any actions."
162 end
163 if params and next(params) then
164 url = url .. ("?" .. encode_query_string(params))
165 end
166 local method = "GET"
167 local out
168 local tmpfile = fs.tmpname()
169 if post_params then
170 method = "POST"
171 local curl_cmd = vars.CURL .. " " .. vars.CURLNOCERTFLAG .. " -f -L --silent --user-agent \"" .. cfg.user_agent .. " via curl\" "
172 for k, v in pairs(post_params) do
173 local var
174 if type(v) == "table" then
175 var = "@" .. v.fname
176 else
177 var = v
178 end
179 curl_cmd = curl_cmd .. "--form \"" .. k .. "=" .. var .. "\" "
180 end
181 if cfg.connection_timeout and cfg.connection_timeout > 0 then
182 curl_cmd = curl_cmd .. "--connect-timeout " .. tonumber(cfg.connection_timeout) .. " "
183 end
184 local ok = fs.execute_string(curl_cmd .. fs.Q(url) .. " -o " .. fs.Q(tmpfile))
185 if not ok then
186 return nil, "API failure: " .. redact_api_url(url)
187 end
188 else
189 local name, err = fs.download(url, tmpfile)
190 if not name then
191 return nil, "API failure: " .. tostring(err) .. " - " .. redact_api_url(url)
192 end
193 end
194
195 local tmpfd = io.open(tmpfile)
196 if not tmpfd then
197 os.remove(tmpfile)
198 return nil, "API failure reading temporary file - " .. redact_api_url(url)
199 end
200 out = tmpfd:read("*a")
201 tmpfd:close()
202 os.remove(tmpfile)
203
204 if self.debug then
205 util.printout("[" .. tostring(method) .. " via curl] " .. redact_api_url(url) .. " ... ")
206 end
207
208 return json.decode(out)
209 end
210
211else
212
213 local warned_luasec = false
214
215 api.Api.request = function(self, url, params, post_params)
216 local server = tostring(self.config.server)
217
218 local http_ok, http
219 local via = "luasocket"
220 if server:match("^https://") then
221 http_ok, http = pcall(require, "ssl.https")
222 if http_ok then
223 via = "luasec"
224 else
225 if not warned_luasec then
226 util.printerr("LuaSec is not available; using plain HTTP. Install 'luasec' to enable HTTPS.")
227 warned_luasec = true
228 end
229 http_ok, http = pcall(require, "socket.http")
230 url = url:gsub("^https", "http")
231 via = "luasocket"
232 end
233 else
234 http_ok, http = pcall(require, "socket.http")
235 end
236 if not http_ok then
237 return nil, "Failed loading socket library!"
238 end
239
240 if not self.config.key then
241 return nil, "Must have API key before performing any actions."
242 end
243 local body
244 local headers = {}
245 if params and next(params) then
246 url = url .. ("?" .. encode_query_string(params))
247 end
248 if post_params then
249 local boundary
250 body, boundary = multipart.encode(post_params)
251 headers["Content-length"] = tostring(#body)
252 headers["Content-type"] = "multipart/form-data; boundary=" .. tostring(boundary)
253 end
254 local method = post_params and "POST" or "GET"
255 if self.debug then
256 util.printout("[" .. tostring(method) .. " via " .. via .. "] " .. redact_api_url(url) .. " ... ")
257 end
258 local out = {}
259 local _, status = http.request({
260 url = url,
261 headers = headers,
262 method = method,
263 sink = ltn12.sink.table(out),
264 source = body and ltn12.source.string(body),
265 })
266 if self.debug then
267 util.printout(tostring(status))
268 end
269 local pok, ret = pcall(json.decode, table.concat(out))
270 if pok and ret then
271 return ret
272 end
273 return nil, "API returned " .. tostring(status) .. " - " .. redact_api_url(url)
274 end
275
276end
277
278function api.new(args)
279 local self = {}
280 setmetatable(self, { __index = Api })
281 self.config = self:load_config() or {}
282 self.config.server = args.server or self.config.server or cfg.upload.server
283 self.config.version = self.config.version or cfg.upload.version
284 self.config.key = args.temp_key or args.api_key or self.config.key
285 self.debug = args.debug
286 if not self.config.key then
287 return nil, "You need an API key to upload rocks.\n" ..
288 "Navigate to " .. self.config.server .. "/settings to get a key\n" ..
289 "and then pass it through the --api-key=<key> flag."
290 end
291 if args.api_key then
292 local ok, err = self:save_config()
293 if not ok then
294 return nil, err
295 end
296 end
297 return self
298end
299
300return api
diff --git a/src/luarocks/upload/multipart.lua b/src/luarocks/upload/multipart.lua
new file mode 100644
index 00000000..cf68bdef
--- /dev/null
+++ b/src/luarocks/upload/multipart.lua
@@ -0,0 +1,117 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local math = _tl_compat and _tl_compat.math or math; local pairs = _tl_compat and _tl_compat.pairs or pairs; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack
2local multipart = {File = {}, }
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17local File = multipart.File
18
19
20function multipart.url_escape(s)
21 return (string.gsub(s, "([^A-Za-z0-9_])", function(c)
22 return string.format("%%%02x", string.byte(c))
23 end))
24end
25
26function multipart.File:mime()
27 if not self.mimetype then
28 local mimetypes_ok, mimetypes = pcall(require, "mimetypes")
29 if mimetypes_ok then
30 self.mimetype = mimetypes.guess(self.fname)
31 end
32 self.mimetype = self.mimetype or "application/octet-stream"
33 end
34 return self.mimetype
35end
36
37function multipart.File:content()
38 local fd = io.open(self.fname, "rb")
39 if not fd then
40 return nil, "Failed to open file: " .. self.fname
41 end
42 local data = fd:read("*a")
43 fd:close()
44 return data
45end
46
47local function rand_string(len)
48 local shuffled = {}
49 for i = 1, len do
50 local r = math.random(97, 122)
51 if math.random() >= 0.5 then
52 r = r - 32
53 end
54 shuffled[i] = r
55 end
56 return string.char(_tl_table_unpack(shuffled))
57end
58
59
60
61
62
63
64
65
66
67function multipart.encode(params)
68 local tuples = {}
69 for k, v in pairs(params) do
70 if type(k) == "string" then
71 table.insert(tuples, { k, v })
72 end
73 end
74 local chunks = {}
75 for _, tuple in ipairs(tuples) do
76 local k, v = _tl_table_unpack(tuple)
77 k = multipart.url_escape(k)
78 local buffer = { 'Content-Disposition: form-data; name="' .. k .. '"' }
79 local content
80 if type(v) == "table" then
81 buffer[1] = buffer[1] .. ('; filename="' .. v.fname:gsub(".*[/\\]", "") .. '"')
82 table.insert(buffer, "Content-type: " .. v:mime())
83 content = v:content()
84 else
85 content = v
86 end
87 table.insert(buffer, "")
88 table.insert(buffer, content)
89 table.insert(chunks, table.concat(buffer, "\r\n"))
90 end
91 local boundary
92 while not boundary do
93 boundary = "Boundary" .. rand_string(16)
94 for _, chunk in ipairs(chunks) do
95 if chunk:find(boundary) then
96 boundary = nil
97 break
98 end
99 end
100 end
101 local inner = "\r\n--" .. boundary .. "\r\n"
102 return table.concat({ "--", boundary, "\r\n",
103table.concat(chunks, inner),
104"\r\n", "--", boundary, "--", "\r\n", }), boundary
105end
106
107function multipart.new_file(fname, mime)
108 local self = {}
109
110 setmetatable(self, { __index = File })
111
112 self.fname = fname
113 self.mimetype = mime
114 return self
115end
116
117return multipart
diff --git a/src/luarocks/util.lua b/src/luarocks/util.lua
new file mode 100644
index 00000000..7035f305
--- /dev/null
+++ b/src/luarocks/util.lua
@@ -0,0 +1,611 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local debug = _tl_compat and _tl_compat.debug or debug; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2
3
4
5
6
7local core = require("luarocks.core.util")
8local cfg = require("luarocks.core.cfg")
9
10
11
12
13local util = {Fn = {}, }
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32util.cleanup_path = core.cleanup_path
33util.split_string = core.split_string
34util.sortedpairs = core.sortedpairs
35util.deep_merge = core.deep_merge
36util.deep_merge_under = core.deep_merge_under
37util.popen_read = core.popen_read
38util.show_table = core.show_table
39util.printerr = core.printerr
40util.warning = core.warning
41util.keys = core.keys
42util.matchquote = core.matchquote
43
44
45
46
47
48
49
50local scheduled_functions = {}
51
52
53
54
55
56
57
58
59function util.schedule_function(f, ...)
60 local item = { fn = f, args = _tl_table_pack(...) }
61 table.insert(scheduled_functions, item)
62 return item
63end
64
65
66
67
68
69function util.remove_scheduled_function(item)
70 for k, v in ipairs(scheduled_functions) do
71 if v == item then
72 table.remove(scheduled_functions, k)
73 return
74 end
75 end
76end
77
78
79
80
81
82
83function util.run_scheduled_functions()
84 local fs = require("luarocks.fs")
85 if fs.change_dir_to_root then
86 fs.change_dir_to_root()
87 end
88 for i = #scheduled_functions, 1, -1 do
89 local item = scheduled_functions[i]
90 item.fn(_tl_table_unpack(item.args, 1, item.args.n))
91 end
92end
93
94local var_format_pattern = "%$%((%a[%a%d_]+)%)"
95
96
97
98
99
100
101
102
103
104
105
106function util.warn_if_not_used(var_defs, needed_set, msg)
107 local seen = {}
108 for _, val in pairs(var_defs) do
109 for used in val:gmatch(var_format_pattern) do
110 seen[used] = true
111 end
112 end
113 for var, _ in pairs(needed_set) do
114 if not seen[var] then
115 util.warning(msg:format(var))
116 end
117 end
118end
119
120
121
122
123local function warn_failed_matches(line)
124 local any_failed = false
125 if line:match(var_format_pattern) then
126 for unmatched in line:gmatch(var_format_pattern) do
127 util.warning("unmatched variable " .. unmatched)
128 any_failed = true
129 end
130 end
131 return any_failed
132end
133
134
135
136
137
138
139
140
141
142function util.variable_substitutions(tbl, vars)
143
144 local updated = {}
145 for k, v in pairs(tbl) do
146 if type(v) == "string" then
147 updated[k] = string.gsub(v, var_format_pattern, vars)
148 if warn_failed_matches(updated[k]) then
149 updated[k] = updated[k]:gsub(var_format_pattern, "")
150 end
151 end
152 end
153 for k, v in pairs(updated) do
154 tbl[k] = v
155 end
156end
157
158function util.lua_versions(sort)
159 local versions = { "5.1", "5.2", "5.3", "5.4" }
160 local i = 0
161 if sort == "descending" then
162 i = #versions + 1
163 return function()
164 i = i - 1
165 return versions[i]
166 end
167 else
168 return function()
169 i = i + 1
170 return versions[i]
171 end
172 end
173end
174
175function util.lua_path_variables()
176 local lpath_var = "LUA_PATH"
177 local lcpath_var = "LUA_CPATH"
178
179 local lv = cfg.lua_version:gsub("%.", "_")
180 if lv ~= "5_1" then
181 if os.getenv("LUA_PATH_" .. lv) then
182 lpath_var = "LUA_PATH_" .. lv
183 end
184 if os.getenv("LUA_CPATH_" .. lv) then
185 lcpath_var = "LUA_CPATH_" .. lv
186 end
187 end
188 return lpath_var, lcpath_var
189end
190
191function util.starts_with(s, prefix)
192 return s:sub(1, #prefix) == prefix
193end
194
195
196function util.printout(...)
197 io.stdout:write(table.concat({ ... }, "\t"))
198 io.stdout:write("\n")
199end
200
201function util.title(msg, porcelain, underline)
202 if porcelain then return end
203 util.printout()
204 util.printout(msg)
205 util.printout((underline or "-"):rep(#msg))
206 util.printout()
207end
208
209function util.this_program(default)
210 local i = 1
211 local last, cur = default, default
212 while i do
213 local dbg = debug and debug.getinfo(i, "S")
214 if not dbg then break end
215 last = cur
216 cur = dbg.source
217 i = i + 1
218 end
219 local prog = last:sub(1, 1) == "@" and last:sub(2) or last
220
221
222 local lrdir, binpath = prog:match("^(.*)/lib/luarocks/rocks%-[0-9.]*/[^/]+/[^/]+(/bin/[^/]+)$")
223 if lrdir then
224
225 return lrdir .. binpath
226 end
227
228 return prog
229end
230
231function util.format_rock_name(name, namespace, version)
232 return (namespace and namespace .. "/" or "") .. name .. (version and " " .. version or "")
233end
234
235function util.deps_mode_option(parser, program)
236
237 parser:option("--deps-mode", "How to handle dependencies. Four modes are supported:\n" ..
238 "* all - use all trees from the rocks_trees list for finding dependencies\n" ..
239 "* one - use only the current tree (possibly set with --tree)\n" ..
240 "* order - use trees based on order (use the current tree and all " ..
241 "trees below it on the rocks_trees list)\n" ..
242 "* none - ignore dependencies altogether.\n" ..
243 "The default mode may be set with the deps_mode entry in the configuration file.\n" ..
244 'The current default is "' .. cfg.deps_mode .. '".\n' ..
245 "Type '" .. util.this_program(program or "luarocks") .. "' with no " ..
246 "arguments to see your list of rocks trees."):
247 argname("<mode>"):
248 choices({ "all", "one", "order", "none" })
249 parser:flag("--nodeps"):hidden(true)
250end
251
252function util.see_help(command, program)
253 return "See '" .. util.this_program(program or "luarocks") .. ' help' .. (command and " " .. command or "") .. "'."
254end
255
256function util.see_also(text)
257 local see_also = "See also:\n"
258 if text then
259 see_also = see_also .. text .. "\n"
260 end
261 return see_also .. " '" .. util.this_program("luarocks") .. " help' for general options and configuration."
262end
263
264function util.announce_install(rockspec)
265 local path = require("luarocks.path")
266
267 local suffix = ""
268 if rockspec.description and rockspec.description.license then
269 suffix = " (license: " .. rockspec.description.license .. ")"
270 end
271
272 util.printout(rockspec.name .. " " .. rockspec.version .. " is now installed in " .. path.root_dir(cfg.root_dir) .. suffix)
273 util.printout()
274end
275
276
277
278
279
280
281
282local function collect_rockspecs(versions, paths, unnamed_paths, subdir)
283 local fs = require("luarocks.fs")
284 local dir = require("luarocks.dir")
285 local path = require("luarocks.path")
286 local vers = require("luarocks.core.vers")
287 if fs.is_dir(subdir) then
288 for file in fs.dir(subdir) do
289 file = dir.path(subdir, file)
290
291 if file:match("rockspec$") and fs.is_file(file) then
292 local rock, version = path.parse_name(file)
293
294 if rock then
295 if not versions[rock] or vers.compare_versions(version, versions[rock]) then
296 versions[rock] = version
297 paths[rock] = file
298 end
299 else
300 table.insert(unnamed_paths, file)
301 end
302 end
303 end
304 end
305end
306
307
308
309function util.get_default_rockspec()
310
311 local versions = {}
312 local paths = {}
313 local unnamed_paths = {}
314
315 collect_rockspecs(versions, paths, unnamed_paths, ".")
316 collect_rockspecs(versions, paths, unnamed_paths, "rockspec")
317 collect_rockspecs(versions, paths, unnamed_paths, "rockspecs")
318
319 if #unnamed_paths > 0 then
320
321
322 if #unnamed_paths > 1 then
323 return nil, "Please specify which rockspec file to use."
324 else
325 return unnamed_paths[1]
326 end
327 else
328 local fs = require("luarocks.fs")
329 local dir = require("luarocks.dir")
330 local basename = dir.base_name(fs.current_dir())
331
332 if paths[basename] then
333 return paths[basename]
334 end
335
336 local rock = next(versions)
337
338 if rock then
339
340 if next(versions, rock) then
341 return nil, "Please specify which rockspec file to use."
342 else
343 return paths[rock]
344 end
345 else
346 return nil, "Argument missing: please specify a rockspec to use on current directory."
347 end
348 end
349end
350
351
352
353
354function util.LQ(s)
355 return ("%q"):format(s)
356end
357
358
359
360
361function util.split_namespace(ns_name)
362 local p1, p2 = ns_name:match("^([^/]+)/([^/]+)$")
363 if p1 then
364 return p2, p1
365 end
366 return ns_name
367end
368
369
370function util.namespaced_name_action(args, target, ns_name)
371
372 if not ns_name then
373 return
374 end
375
376 if ns_name:match("%.rockspec$") or ns_name:match("%.rock$") then
377 args[target] = ns_name
378 else
379 local name, namespace = util.split_namespace(ns_name)
380 args[target] = name:lower()
381 if namespace then
382 args.namespace = namespace:lower()
383 end
384 end
385end
386
387function util.deep_copy(tbl)
388 local copy = {}
389 for k, v in pairs(tbl) do
390 if type(v) == "table" then
391 copy[k] = util.deep_copy(v)
392 else
393 copy[k] = v
394 end
395 end
396 return copy
397end
398
399
400
401
402function util.exists(file)
403 local fd, _, code = io.open(file, "r")
404 if code == 13 then
405
406
407 return true
408 end
409 if fd then
410 fd:close()
411 return true
412 end
413 return false
414end
415
416function util.lua_is_wrapper(interp)
417 local fd, err = io.open(interp, "r")
418 if not fd then
419 return nil, err
420 end
421 local data
422 data, err = fd:read(1000)
423 fd:close()
424 if not data then
425 return nil, err
426 end
427 return not not data:match("LUAROCKS_SYSCONFDIR")
428end
429
430do
431 local function Q(pathname)
432 if pathname:match("^.:") then
433 return pathname:sub(1, 2) .. '"' .. pathname:sub(3) .. '"'
434 end
435 return '"' .. pathname .. '"'
436 end
437
438 function util.check_lua_version(lua, luaver)
439 if not util.exists(lua) then
440 return nil
441 end
442 local lv = util.popen_read(Q(lua) .. ' -e "io.write(_VERSION:sub(5))"')
443 if lv == "" then
444 return nil
445 end
446 if luaver and luaver ~= lv then
447 return nil
448 end
449 return lv
450 end
451
452 function util.get_luajit_version()
453 if cfg.cache.luajit_version_checked then
454 return cfg.cache.luajit_version
455 end
456 cfg.cache.luajit_version_checked = true
457
458 if not cfg.variables.LUA then
459 return nil
460 end
461
462 local ljv
463 if cfg.lua_version == "5.1" then
464
465 ljv = util.popen_read(Q(cfg.variables.LUA) .. ' -e "io.write(tostring(jit and jit.version:gsub([[^%S+ (%S+).*]], [[%1]])))"')
466 if ljv == "nil" then
467 ljv = nil
468 end
469 end
470 cfg.cache.luajit_version = ljv
471 return ljv
472 end
473
474 local find_lua_bindir
475 do
476 local exe_suffix = (package.config:sub(1, 1) == "\\" and ".exe" or "")
477
478 local function insert_lua_variants(names, luaver)
479 local variants = {
480 "lua" .. luaver .. exe_suffix,
481 "lua" .. luaver:gsub("%.", "") .. exe_suffix,
482 "lua-" .. luaver .. exe_suffix,
483 "lua-" .. luaver:gsub("%.", "") .. exe_suffix,
484 }
485 for _, name in ipairs(variants) do
486 table.insert(names, name)
487 end
488 end
489
490 find_lua_bindir = function(prefix, luaver, verbose)
491 local names = {}
492 if luaver then
493 insert_lua_variants(names, luaver)
494 else
495 for v in util.lua_versions("descending") do
496 insert_lua_variants(names, v)
497 end
498 end
499 if luaver == "5.1" or not luaver then
500 table.insert(names, "luajit" .. exe_suffix)
501 end
502 table.insert(names, "lua" .. exe_suffix)
503
504 local tried = {}
505 local dir_sep = package.config:sub(1, 1)
506 for _, d in ipairs({ prefix .. dir_sep .. "bin", prefix }) do
507 for _, name in ipairs(names) do
508 local lua = d .. dir_sep .. name
509 local is_wrapper, err = util.lua_is_wrapper(lua)
510 if is_wrapper == false then
511 local lv = util.check_lua_version(lua, luaver)
512 if lv then
513 return lua, d, lv
514 end
515 elseif is_wrapper == true or err == nil then
516 table.insert(tried, lua)
517 else
518 table.insert(tried, string.format("%-13s (%s)", lua, err))
519 end
520 end
521 end
522 local interp = luaver and
523 ("Lua " .. luaver .. " interpreter") or
524 "Lua interpreter"
525 return nil, interp .. " not found at " .. prefix .. "\n" ..
526 (verbose and "Tried:\t" .. table.concat(tried, "\n\t") or "")
527 end
528 end
529
530 function util.find_lua(prefix, luaver, verbose)
531 local lua, bindir
532 lua, bindir, luaver = find_lua_bindir(prefix, luaver, verbose)
533 if not lua then
534 return nil, bindir
535 end
536
537 return {
538 lua_version = luaver,
539 lua = lua,
540 lua_dir = prefix,
541 lua_bindir = bindir,
542 }
543 end
544end
545
546
547
548
549
550
551
552
553
554function util.get_rocks_provided(rockspec)
555
556 if not rockspec and cfg.cache.rocks_provided then
557 return cfg.cache.rocks_provided
558 end
559
560 local rocks_provided = {}
561
562 local lv = cfg.lua_version
563
564 rocks_provided["lua"] = lv .. "-1"
565
566 if lv == "5.2" then
567 rocks_provided["bit32"] = lv .. "-1"
568 end
569
570 if lv == "5.3" or lv == "5.4" then
571 rocks_provided["utf8"] = lv .. "-1"
572 end
573
574 if lv == "5.1" then
575 local ljv = util.get_luajit_version()
576 if ljv then
577 rocks_provided["luabitop"] = ljv .. "-1"
578 if (not rockspec) or rockspec:format_is_at_least("3.0") then
579 rocks_provided["luajit"] = ljv .. "-1"
580 end
581 end
582 end
583
584 if cfg.rocks_provided then
585 util.deep_merge_under(rocks_provided, cfg.rocks_provided)
586 end
587
588 if not rockspec then
589 cfg.cache.rocks_provided = rocks_provided
590 end
591
592 return rocks_provided
593end
594
595function util.remove_doc_dir(name, version)
596 local path = require("luarocks.path")
597 local fs = require("luarocks.fs")
598 local dir = require("luarocks.dir")
599
600 local install_dir = path.install_dir(name, version)
601 for _, f in ipairs(fs.list_dir(install_dir)) do
602 local doc_dirs = { "doc", "docs" }
603 for _, d in ipairs(doc_dirs) do
604 if f == d then
605 fs.delete(dir.path(install_dir, f))
606 end
607 end
608 end
609end
610
611return util