summaryrefslogtreecommitdiff
path: root/src/luarocks/manif.lua
blob: a4ddda11e70212d30f7d713c8122667a5589179b (plain)
1
2
3
4
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
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
--- Module for handling manifest files and tables.
-- Manifest files describe the contents of a LuaRocks tree or server.
-- They are loaded into manifest tables, which are then used for
-- performing searches, matching dependencies, etc.
local manif = {}

local core = require("luarocks.core.manif")
local persist = require("luarocks.persist")
local fetch = require("luarocks.fetch")
local dir = require("luarocks.dir")
local fs = require("luarocks.fs")
local cfg = require("luarocks.core.cfg")
local path = require("luarocks.path")
local util = require("luarocks.util")
local queries = require("luarocks.queries")
local type_manifest = require("luarocks.type.manifest")

manif.cache_manifest = core.cache_manifest
manif.load_rocks_tree_manifests = core.load_rocks_tree_manifests
manif.scan_dependencies = core.scan_dependencies

manif.rock_manifest_cache = {}

local function check_manifest(repo_url, manifest, globals)
   local ok, err = type_manifest.check(manifest, globals)
   if not ok then
      core.cache_manifest(repo_url, cfg.lua_version, nil)
      return nil, "Error checking manifest: "..err, "type"
   end
   return manifest
end

local postprocess_dependencies
do
   local postprocess_check = setmetatable({}, { __mode = "k" })
   postprocess_dependencies = function(manifest)
      if postprocess_check[manifest] then
         return
      end
      if manifest.dependencies then
         for name, versions in pairs(manifest.dependencies) do
            for version, entries in pairs(versions) do
               for k, v in pairs(entries) do
                  entries[k] = queries.from_persisted_table(v)
               end
            end
         end
      end
      postprocess_check[manifest] = true
   end
end

function manif.load_rock_manifest(name, version, root)
   assert(type(name) == "string" and not name:match("/"))
   assert(type(version) == "string")

   local name_version = name.."/"..version
   if manif.rock_manifest_cache[name_version] then
      return manif.rock_manifest_cache[name_version].rock_manifest
   end
   local pathname = path.rock_manifest_file(name, version, root)
   local rock_manifest = persist.load_into_table(pathname)
   if not rock_manifest then
      return nil, "rock_manifest file not found for "..name.." "..version.." - not a LuaRocks tree?"
   end
   manif.rock_manifest_cache[name_version] = rock_manifest
   return rock_manifest.rock_manifest
end

--- Load a local or remote manifest describing a repository.
-- All functions that use manifest tables assume they were obtained
-- through this function.
-- @param repo_url string: URL or pathname for the repository.
-- @param lua_version string: Lua version in "5.x" format, defaults to installed version.
-- @param versioned_only boolean: If true, do not fall back to the main manifest
-- if a versioned manifest was not found.
-- @return table or (nil, string, [string]): A table representing the manifest,
-- or nil followed by an error message and an optional error code.
function manif.load_manifest(repo_url, lua_version, versioned_only)
   assert(type(repo_url) == "string")
   assert(type(lua_version) == "string" or not lua_version)
   lua_version = lua_version or cfg.lua_version

   local cached_manifest = core.get_cached_manifest(repo_url, lua_version)
   if cached_manifest then
      postprocess_dependencies(cached_manifest)
      return cached_manifest
   end

   local filenames = {
      "manifest-"..lua_version..".zip",
      "manifest-"..lua_version,
      not versioned_only and "manifest" or nil,
   }

   local protocol, repodir = dir.split_url(repo_url)
   local pathname, from_cache
   if protocol == "file" then
      for _, filename in ipairs(filenames) do
         pathname = dir.path(repodir, filename)
         if fs.exists(pathname) then
            break
         end
      end
   else
      local err, errcode
      for _, filename in ipairs(filenames) do
         pathname, err, errcode, from_cache = fetch.fetch_caching(dir.path(repo_url, filename), "no_mirror")
         if pathname then
            break
         end
      end
      if not pathname then
         return nil, err, errcode
      end
   end
   if pathname:match(".*%.zip$") then
      pathname = fs.absolute_name(pathname)
      local nozip = pathname:match("(.*)%.zip$")
      if not from_cache then
         local dirname = dir.dir_name(pathname)
         fs.change_dir(dirname)
         fs.delete(nozip)
         local ok, err = fs.unzip(pathname)
         fs.pop_dir()
         if not ok then
            fs.delete(pathname)
            fs.delete(pathname..".timestamp")
            return nil, "Failed extracting manifest file: " .. err
         end
      end
      pathname = nozip
   end
   local manifest, err, errcode = core.manifest_loader(pathname, repo_url, lua_version)
   if not manifest then
      return nil, err, errcode
   end

   postprocess_dependencies(manifest)
   return check_manifest(repo_url, manifest, err)
end

--- Get type and name of an item (a module or a command) provided by a file.
-- @param deploy_type string: rock manifest subtree the file comes from ("bin", "lua", or "lib").
-- @param file_path string: path to the file relatively to deploy_type subdirectory.
-- @return (string, string): item type ("module" or "command") and name.
function manif.get_provided_item(deploy_type, file_path)
   assert(type(deploy_type) == "string")
   assert(type(file_path) == "string")
   local item_type = deploy_type == "bin" and "command" or "module"
   local item_name = item_type == "command" and file_path or path.path_to_module(file_path)
   return item_type, item_name
end

local function get_providers(item_type, item_name, repo)
   assert(type(item_type) == "string")
   assert(type(item_name) == "string")
   local rocks_dir = path.rocks_dir(repo or cfg.root_dir)
   local manifest = manif.load_manifest(rocks_dir)
   return manifest and manifest[item_type .. "s"][item_name]
end

--- Given a name of a module or a command, figure out which rock name and version
-- correspond to it in the rock tree manifest.
-- @param item_type string: "module" or "command".
-- @param item_name string: module or command name.
-- @param root string or nil: A local root dir for a rocks tree. If not given, the default is used.
-- @return (string, string) or nil: name and version of the provider rock or nil if there
-- is no provider.
function manif.get_current_provider(item_type, item_name, repo)
   local providers = get_providers(item_type, item_name, repo)
   if providers then
      return providers[1]:match("([^/]*)/([^/]*)")
   end
end

function manif.get_next_provider(item_type, item_name, repo)
   local providers = get_providers(item_type, item_name, repo)
   if providers and providers[2] then
      return providers[2]:match("([^/]*)/([^/]*)")
   end
end

--- Get all versions of a package listed in a manifest file.
-- @param name string: a package name.
-- @param deps_mode string: "one", to use only the currently
-- configured tree; "order" to select trees based on order
-- (use the current tree and all trees below it on the list)
-- or "all", to use all trees.
-- @return table: An array of strings listing installed
-- versions of a package, and a table indicating where they are found.
function manif.get_versions(dep, deps_mode)
   assert(type(dep) == "table")
   assert(type(deps_mode) == "string")

   local name = dep.name
   local namespace = dep.namespace

   local version_set = {}
   path.map_trees(deps_mode, function(tree)
      local manifest = manif.load_manifest(path.rocks_dir(tree))

      if manifest and manifest.repository[name] then
         for version in pairs(manifest.repository[name]) do
            if dep.namespace then
               local ns_file = path.rock_namespace_file(name, version, tree)
               local fd = io.open(ns_file, "r")
               if fd then
                  local ns = fd:read("*a")
                  fd:close()
                  if ns == namespace then
                     version_set[version] = tree
                  end
               end
            else
               version_set[version] = tree
            end
         end
      end
   end)

   return util.keys(version_set), version_set
end

return manif