view share/lua/5.2/luarocks/fetch.lua @ 3026:c8ea00fb641d

<shachaf> addquote <mnoqy> the theory\'s probably not bad, but calculus is one of those things that\'s so dang applicable that everyone only ever talks about how to apply it and compute with it and uuuuurgh(barf) <mnoqy> so i stay away from it
author HackBot
date Sun, 02 Jun 2013 04:42:47 +0000
parents d137f631bad5
children
line wrap: on
line source


--- Functions related to fetching and loading local and remote files.
module("luarocks.fetch", package.seeall)

local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local type_check = require("luarocks.type_check")
local path = require("luarocks.path")
local deps = require("luarocks.deps")
local persist = require("luarocks.persist")
local util = require("luarocks.util")
local cfg = require("luarocks.cfg")

--- Fetch a local or remote file.
-- Make a remote or local URL/pathname local, fetching the file if necessary.
-- Other "fetch" and "load" functions use this function to obtain files.
-- If a local pathname is given, it is returned as a result.
-- @param url string: a local pathname or a remote URL.
-- @param filename string or nil: this function attempts to detect the
-- resulting local filename of the remote file as the basename of the URL;
-- if that is not correct (due to a redirection, for example), the local
-- filename can be given explicitly as this second argument.
-- @return string or (nil, string, [string]): the absolute local pathname for the
-- fetched file, or nil and a message in case of errors, followed by
-- an optional error code.
function fetch_url(url, filename)
   assert(type(url) == "string")
   assert(type(filename) == "string" or not filename)

   local protocol, pathname = dir.split_url(url)
   if protocol == "file" then
      return fs.absolute_name(pathname)
   elseif protocol == "http" or protocol == "ftp" or protocol == "https" then
      local ok, err = fs.download(url, filename)
      if not ok then
         return nil, "Failed downloading "..url..(err and " - "..err or ""), "network"
      end
      return dir.path(fs.current_dir(), filename or dir.base_name(url))
   else
      return nil, "Unsupported protocol "..protocol
   end
end

--- For remote URLs, create a temporary directory and download URL inside it.
-- This temporary directory will be deleted on program termination.
-- For local URLs, just return the local pathname and its directory.
-- @param url string: URL to be downloaded
-- @param tmpname string: name pattern to use for avoiding conflicts
-- when creating temporary directory.
-- @param filename string or nil: local filename of URL to be downloaded,
-- in case it can't be inferred from the URL.
-- @return (string, string) or (nil, string, [string]): absolute local pathname of
-- the fetched file and temporary directory name; or nil and an error message
-- followed by an optional error code
function fetch_url_at_temp_dir(url, tmpname, filename)
   assert(type(url) == "string")
   assert(type(tmpname) == "string")
   assert(type(filename) == "string" or not filename)
   filename = filename or dir.base_name(url)

   local protocol, pathname = dir.split_url(url)
   if protocol == "file" then
      if fs.exists(pathname) then
         return pathname, dir.dir_name(fs.absolute_name(pathname))
      else
         return nil, "File not found: "..pathname
      end
   else
      local temp_dir = fs.make_temp_dir(tmpname)
      if not temp_dir then
         return nil, "Failed creating temporary directory."
      end
      util.schedule_function(fs.delete, temp_dir)
      fs.change_dir(temp_dir)
      local file, err, errcode = fetch_url(url, filename)
      fs.pop_dir()
      if not file then
         return nil, "Error fetching file: "..err, errcode
      end
      return file, temp_dir
   end
end

--- Obtain a rock and unpack it.
-- If a directory is not given, a temporary directory will be created,
-- which will be deleted on program termination.
-- @param rock_file string: URL or filename of the rock.
-- @param dest string or nil: if given, directory will be used as
-- a permanent destination.
-- @return string or (nil, string, [string]): the directory containing the contents
-- of the unpacked rock.
function fetch_and_unpack_rock(rock_file, dest)
   assert(type(rock_file) == "string")
   assert(type(dest) == "string" or not dest)

   local name = dir.base_name(rock_file):match("(.*)%.[^.]*%.rock")
   
   local rock_file, err, errcode = fetch_url_at_temp_dir(rock_file,"luarocks-rock-"..name)
   if not rock_file then
      return nil, "Could not fetch rock file: " .. err, errcode
   end

   rock_file = fs.absolute_name(rock_file)
   local unpack_dir
   if dest then
      unpack_dir = dest
      fs.make_dir(unpack_dir)
   else
      unpack_dir = fs.make_temp_dir(name)
   end
   if not dest then
      util.schedule_function(fs.delete, unpack_dir)
   end
   fs.change_dir(unpack_dir)
   local ok = fs.unzip(rock_file)
   if not ok then
      return nil, "Failed unpacking rock file: " .. rock_file
   end
   fs.pop_dir()
   return unpack_dir
end

--- Back-end function that actually loads the local rockspec.
-- Performs some validation and postprocessing of the rockspec contents.
-- @param filename string: The local filename of the rockspec file.
-- @return table or (nil, string): A table representing the rockspec
-- or nil followed by an error message.
function load_local_rockspec(filename)
   assert(type(filename) == "string")
   filename = fs.absolute_name(filename)
   local rockspec, err = persist.load_into_table(filename)
   if not rockspec then
      return nil, "Could not load rockspec file "..filename.." ("..err..")"
   end

   local ok, err = type_check.type_check_rockspec(rockspec)
   if not ok then
      return nil, filename..": "..err
   end
   
   if rockspec.rockspec_format then
      if deps.compare_versions(rockspec.rockspec_format, type_check.rockspec_format) then
         return nil, "Rockspec format "..rockspec.rockspec_format.." is not supported, please upgrade LuaRocks."
      end
   end

   util.platform_overrides(rockspec.build)
   util.platform_overrides(rockspec.dependencies)
   util.platform_overrides(rockspec.external_dependencies)
   util.platform_overrides(rockspec.source)
   util.platform_overrides(rockspec.hooks)

   local basename = dir.base_name(filename)
   if basename == "rockspec" then
      rockspec.name = rockspec.package:lower()
   else
      rockspec.name = basename:match("(.*)-[^-]*-[0-9]*")
      if not rockspec.name then
         return nil, "Expected filename in format 'name-version-revision.rockspec'."
      end
   end

   local protocol, pathname = dir.split_url(rockspec.source.url)
   if protocol == "http" or protocol == "https" or protocol == "ftp" or protocol == "file" then
      rockspec.source.file = rockspec.source.file or dir.base_name(rockspec.source.url)
   end
   rockspec.source.protocol, rockspec.source.pathname = protocol, pathname

   -- Temporary compatibility
   if rockspec.source.cvs_module then rockspec.source.module = rockspec.source.cvs_module end
   if rockspec.source.cvs_tag then rockspec.source.tag = rockspec.source.cvs_tag end

   local name_version = rockspec.package:lower() .. "-" .. rockspec.version
   if basename ~= "rockspec" and basename ~= name_version .. ".rockspec" then
      return nil, "Inconsistency between rockspec filename ("..basename..") and its contents ("..name_version..".rockspec)."
   end

   rockspec.local_filename = filename
   local filebase = rockspec.source.file or rockspec.source.url
   local base = dir.base_name(filebase)
   base = base:gsub("%.[^.]*$", ""):gsub("%.tar$", "")
   rockspec.source.dir = rockspec.source.dir
                      or rockspec.source.module
                      or ((filebase:match(".lua$") or filebase:match(".c$")) and ".")
                      or base
   if rockspec.dependencies then
      for i = 1, #rockspec.dependencies do
         local parsed = deps.parse_dep(rockspec.dependencies[i])
         if not parsed then
            return nil, "Parse error processing dependency '"..rockspec.dependencies[i].."'"
         end
         rockspec.dependencies[i] = parsed
      end
   else
      rockspec.dependencies = {}
   end
   local ok, err = path.configure_paths(rockspec)
   if err then
      return nil, "Error verifying paths: "..err
   end

   return rockspec
end

--- Load a local or remote rockspec into a table.
-- This is the entry point for the LuaRocks tools. 
-- Only the LuaRocks runtime loader should use
-- load_local_rockspec directly.
-- @param filename string: Local or remote filename of a rockspec.
-- @param location string or nil: Where to download. If not given,
-- a temporary dir is created.
-- @return table or (nil, string, [string]): A table representing the rockspec
-- or nil followed by an error message and optional error code.
function load_rockspec(filename, location)
   assert(type(filename) == "string")

   local name
   local basename = dir.base_name(filename)
   if basename == "rockspec" then
      name = "rockspec"
   else
      name = basename:match("(.*)%.rockspec")
      if not name and not basename == "rockspec" then
         return nil, "Filename '"..filename.."' does not look like a rockspec."
      end
   end
   
   local err, errcode
   if location then
      fs.change_dir(location)
      filename, err = fetch_url(filename)
      fs.pop_dir()
   else
      filename, err, errcode = fetch_url_at_temp_dir(filename,"luarocks-rockspec-"..name)
   end
   if not filename then
      return nil, err, errcode
   end

   return load_local_rockspec(filename)
end

--- Download sources for building a rock using the basic URL downloader.
-- @param rockspec table: The rockspec table
-- @param extract boolean: Whether to extract the sources from
-- the fetched source tarball or not.
-- @param dest_dir string or nil: If set, will extract to the given directory.
-- @return (string, string) or (nil, string, [string]): The absolute pathname of
-- the fetched source tarball and the temporary directory created to
-- store it; or nil and an error message and optional error code.
function get_sources(rockspec, extract, dest_dir)
   assert(type(rockspec) == "table")
   assert(type(extract) == "boolean")
   assert(type(dest_dir) == "string" or not dest_dir)

   local url = rockspec.source.url
   local name = rockspec.name.."-"..rockspec.version
   local filename = rockspec.source.file
   local source_file, store_dir, err, errcode
   if dest_dir then
      fs.change_dir(dest_dir)
      source_file, err, errcode = fetch_url(url, filename)
      fs.pop_dir()
      store_dir = dest_dir
   else
      source_file, store_dir, errcode = fetch_url_at_temp_dir(url, "luarocks-source-"..name, filename)
   end
   if not source_file then
      return nil, err or store_dir, errcode
   end
   if rockspec.source.md5 then
      if not fs.check_md5(source_file, rockspec.source.md5) then
         return nil, "MD5 check for "..filename.." has failed."
      end
   end
   if extract then
      fs.change_dir(store_dir)
      fs.unpack_archive(rockspec.source.file)
      if not fs.exists(rockspec.source.dir) then
         return nil, "Directory "..rockspec.source.dir.." not found inside archive "..rockspec.source.file
      end
      fs.pop_dir()
   end
   return source_file, store_dir
end

--- Download sources for building a rock, calling the appropriate protocol method.
-- @param rockspec table: The rockspec table
-- @param extract boolean: When downloading compressed formats, whether to extract
-- the sources from the fetched archive or not.
-- @param dest_dir string or nil: If set, will extract to the given directory.
-- @return (string, string) or (nil, string): The absolute pathname of
-- the fetched source tarball and the temporary directory created to
-- store it; or nil and an error message.
function fetch_sources(rockspec, extract, dest_dir)
   assert(type(rockspec) == "table")
   assert(type(extract) == "boolean")
   assert(type(dest_dir) == "string" or not dest_dir)

   local protocol = rockspec.source.protocol
   local ok, proto
   if protocol == "http" or protocol == "https" or protocol == "ftp" or protocol == "file" then
      proto = require("luarocks.fetch")
   else
      ok, proto = pcall(require, "luarocks.fetch."..protocol:gsub("[+-]", "_"))
      if not ok then
         return nil, "Unknown protocol "..protocol
      end
   end
   
   if cfg.only_sources_from
   and rockspec.source.pathname
   and #rockspec.source.pathname > 0 then
      if #cfg.only_sources_from == 0 then
         return nil, "Can't download "..rockspec.source.url.." -- download from remote servers disabled"
      elseif rockspec.source.pathname:find(cfg.only_sources_from, 1, true) ~= 1 then
         return nil, "Can't download "..rockspec.source.url.." -- only downloading from "..cfg.only_sources_from
      end
   end
   return proto.get_sources(rockspec, extract, dest_dir)
end