view share/lua/5.2/luarocks/build.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


--- Module implementing the LuaRocks "build" command.
-- Builds a rock, compiling its C parts if any.
module("luarocks.build", package.seeall)

local pack = require("luarocks.pack")
local path = require("luarocks.path")
local util = require("luarocks.util")
local repos = require("luarocks.repos")
local fetch = require("luarocks.fetch")
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local deps = require("luarocks.deps")
local manif = require("luarocks.manif")
local cfg = require("luarocks.cfg")

help_summary = "Build/compile a rock."
help_arguments = "[--pack-binary-rock] {<rockspec>|<rock>|<name> [<version>]}"
help = [[
Build and install a rock, compiling its C parts if any.
Argument may be a rockspec file, a source rock file
or the name of a rock to be fetched from a repository.

If --pack-binary-rock is passed, the rock is not installed;
instead, a .rock file with the contents of compilation is produced
in the current directory.
]]

--- Install files to a given location.
-- Takes a table where the array part is a list of filenames to be copied.
-- In the hash part, other keys, if is_module_path is set, are identifiers
-- in Lua module format, to indicate which subdirectory the file should be
-- copied to. For example, install_files({["foo.bar"] = "src/bar.lua"}, "boo")
-- will copy src/bar.lua to boo/foo.
-- @param files table or nil: A table containing a list of files to copy in
-- the format described above. If nil is passed, this function is a no-op.
-- Directories should be delimited by forward slashes as in internet URLs.
-- @param location string: The base directory files should be copied to.
-- @param is_module_path boolean: True if string keys in files should be
-- interpreted as dotted module paths.
-- @return boolean or (nil, string): True if succeeded or 
-- nil and an error message.
local function install_files(files, location, is_module_path)
   assert(type(files) == "table" or not files)
   assert(type(location) == "string")
   if files then
      for k, file in pairs(files) do
         local dest = location
         if type(k) == "string" then
            if is_module_path then
               dest = dir.path(location, path.module_to_path(k))
               fs.make_dir(dest)
            else
               dest = dir.path(location, dir.dir_name(k))
               fs.make_dir(dest)
               dest = dir.path(dest, dir.base_name(k))
            end
         else
            fs.make_dir(dest)
         end
         local ok = fs.copy(dir.path(file), dest)
         if not ok then
            return nil, "Failed copying "..file
         end
      end
   end
   return true
end

--- Write to the current directory the contents of a table,
-- where each key is a file name and its value is the file content.
-- @param files table: The table of files to be written.
local function extract_from_rockspec(files)
   for name, content in pairs(files) do
      local fd = io.open(dir.path(fs.current_dir(), name), "w+")
      fd:write(content)
      fd:close()
   end
end

--- Applies patches inlined in the build.patches section
-- and extracts files inlined in the build.extra_files section
-- of a rockspec. 
-- @param rockspec table: A rockspec table.
-- @return boolean or (nil, string): True if succeeded or 
-- nil and an error message.
function apply_patches(rockspec)
   assert(type(rockspec) == "table")

   local build = rockspec.build
   if build.extra_files then
      extract_from_rockspec(build.extra_files)
   end
   if build.patches then
      extract_from_rockspec(build.patches)
      for patch, patchdata in util.sortedpairs(build.patches) do
         util.printout("Applying patch "..patch.."...")
         local ok, err = fs.apply_patch(tostring(patch), patchdata)
         if not ok then
            return nil, "Failed applying patch "..patch
         end
      end
   end
   return true
end

--- Build and install a rock given a rockspec.
-- @param rockspec_file string: local or remote filename of a rockspec.
-- @param need_to_fetch boolean: true if sources need to be fetched,
-- false if the rockspec was obtained from inside a source rock.
-- @param minimal_mode boolean: true if there's no need to fetch,
-- unpack or change dir (this is used by "luarocks make"). Implies
-- need_to_fetch = false.
-- @param deps_mode: string: Which trees to check dependencies for:
-- "none", "one", "order" or "all".
-- @return boolean or (nil, string, [string]): True if succeeded or 
-- nil and an error message followed by an error code.
function build_rockspec(rockspec_file, need_to_fetch, minimal_mode, deps_mode)
   assert(type(rockspec_file) == "string")
   assert(type(need_to_fetch) == "boolean")

   local rockspec, err, errcode = fetch.load_rockspec(rockspec_file)
   if err then
      return nil, err, errcode
   elseif not rockspec.build then
      return nil, "Rockspec error: build table not specified"
   elseif not rockspec.build.type then
      return nil, "Rockspec error: build type not specified"
   end

   if deps_mode == "none" then
      util.printerr("Warning: skipping dependency checks.")
   else
      local ok, err, errcode = deps.fulfill_dependencies(rockspec, deps_mode)
      if err then
         return nil, err, errcode
      end
   end

   ok, err, errcode = deps.check_external_deps(rockspec, "build")
   if err then
      return nil, err, errcode
   end

   local name, version = rockspec.name, rockspec.version
   if repos.is_installed(name, version) then
      repos.delete_version(name, version)
   end

   if not minimal_mode then
      local _, source_dir
      if need_to_fetch then
         ok, source_dir, errcode = fetch.fetch_sources(rockspec, true)
         if not ok then
            return nil, source_dir, errcode
         end
         fs.change_dir(source_dir)
      elseif rockspec.source.file then
         local ok, err = fs.unpack_archive(rockspec.source.file)
         if not ok then
            return nil, err
         end
      end
      fs.change_dir(rockspec.source.dir)
   end
   
   local dirs = {
      lua = { name = path.lua_dir(name, version), is_module_path = true },
      lib = { name = path.lib_dir(name, version), is_module_path = true },
      conf = { name = path.conf_dir(name, version), is_module_path = false },
      bin = { name = path.bin_dir(name, version), is_module_path = false },
   }
   
   for _, d in pairs(dirs) do
      fs.make_dir(d.name)
   end
   local rollback = util.schedule_function(function()
      fs.delete(path.install_dir(name, version))
      fs.remove_dir_if_empty(path.versions_dir(name))
   end)

   local build = rockspec.build
   
   if not minimal_mode then
      ok, err = apply_patches(rockspec)
      if err then
         return nil, err
      end
   end
   
   if build.type ~= "none" then

      -- Temporary compatibility
      if build.type == "module" then
         util.printout("Do not use 'module' as a build type. Use 'builtin' instead.")
         build.type = "builtin"
      end

      local build_type
      ok, build_type = pcall(require, "luarocks.build." .. build.type)
      if not ok or not type(build_type) == "table" then
         return nil, "Failed initializing build back-end for build type '"..build.type.."': "..build_type
      end
  
      ok, err = build_type.run(rockspec)
      if not ok then
         return nil, "Build error: " .. err
      end
   end

   if build.install then
      for id, install_dir in pairs(dirs) do
         ok, err = install_files(build.install[id], install_dir.name, install_dir.is_module_path)
         if not ok then 
            return nil, err
         end
      end
   end
   
   local copy_directories = build.copy_directories or {"doc"}

   for _, copy_dir in pairs(copy_directories) do
      if fs.is_dir(copy_dir) then
         local dest = dir.path(path.install_dir(name, version), copy_dir)
         fs.make_dir(dest)
         fs.copy_contents(copy_dir, dest)
      else
         util.warning("Directory '"..copy_dir.."' not found")
      end
   end

   for _, d in pairs(dirs) do
      fs.remove_dir_if_empty(d.name)
   end

   fs.pop_dir()
   
   fs.copy(rockspec.local_filename, path.rockspec_file(name, version))
   if need_to_fetch then
      fs.pop_dir()
   end

   ok, err = manif.make_rock_manifest(name, version)
   if err then return nil, err end

   ok, err = repos.deploy_files(name, version, repos.should_wrap_bin_scripts(rockspec))
   if err then return nil, err end
   
   util.remove_scheduled_function(rollback)
   rollback = util.schedule_function(function()
      repos.delete_version(name, version)
   end)

   ok, err = repos.run_hook(rockspec, "post_install")
   if err then return nil, err end

   ok, err = manif.update_manifest(name, version, nil, deps_mode)
   if err then return nil, err end

   local license = ""
   if rockspec.description and rockspec.description.license then
      license = ("(license: "..rockspec.description.license..")")
   end

   local root_dir = path.root_dir(cfg.rocks_dir)
   util.printout()
   util.printout(name.." "..version.." is now built and installed in "..root_dir.." "..license)
   
   util.remove_scheduled_function(rollback)
   return true
end

--- Build and install a rock.
-- @param rock_file string: local or remote filename of a rock.
-- @param need_to_fetch boolean: true if sources need to be fetched,
-- false if the rockspec was obtained from inside a source rock.
-- @param deps_mode: string: Which trees to check dependencies for:
-- "none", "one", "order" or "all".
-- @return boolean or (nil, string, [string]): True if build was successful,
-- or false and an error message and an optional error code.
function build_rock(rock_file, need_to_fetch, deps_mode)
   assert(type(rock_file) == "string")
   assert(type(need_to_fetch) == "boolean")
  
   local unpack_dir, err, errcode = fetch.fetch_and_unpack_rock(rock_file)
   if not unpack_dir then
      return nil, err, errcode
   end
   local rockspec_file = path.rockspec_name_from_rock(rock_file)
   fs.change_dir(unpack_dir)
   local ok, err, errcode = build_rockspec(rockspec_file, need_to_fetch, false, deps_mode)
   fs.pop_dir()
   return ok, err, errcode
end
 
local function do_build(name, version, deps_mode)
   if name:match("%.rockspec$") then
      return build_rockspec(name, true, false, deps_mode)
   elseif name:match("%.src%.rock$") then
      return build_rock(name, false, deps_mode)
   elseif name:match("%.all%.rock$") then
      local install = require("luarocks.install")
      return install.install_binary_rock(name, deps_mode)
   elseif name:match("%.rock$") then
      return build_rock(name, true, deps_mode)
   elseif not name:match(dir.separator) then
      local search = require("luarocks.search")
      return search.act_on_src_or_rockspec(run, name:lower(), version, deps.deps_mode_to_flag(deps_mode))
   end
   return nil, "Don't know what to do with "..name
end

--- Driver function for "build" command.
-- @param name string: A local or remote rockspec or rock file.
-- If a package name is given, forwards the request to "search" and,
-- if returned a result, installs the matching rock.
-- @param version string: When passing a package name, a version number may
-- also be given.
-- @return boolean or (nil, string): True if build was successful; nil and an
-- error message otherwise.
function run(...)
   local flags, name, version = util.parse_flags(...)
   if type(name) ~= "string" then
      return nil, "Argument missing, see help."
   end
   assert(type(version) == "string" or not version)

   if flags["pack-binary-rock"] then
      return pack.pack_binary_rock(name, version, do_build, name, version, deps.get_deps_mode(flags))
   else
      local ok, err = fs.check_command_permissions(flags)
      if not ok then return nil, err end
      return do_build(name, version, deps.get_deps_mode(flags))
   end
end