summaryrefslogtreecommitdiff
path: root/src/luarocks/fs/win32.lua
diff options
context:
space:
mode:
Diffstat (limited to 'src/luarocks/fs/win32.lua')
-rw-r--r--src/luarocks/fs/win32.lua384
1 files changed, 384 insertions, 0 deletions
diff --git a/src/luarocks/fs/win32.lua b/src/luarocks/fs/win32.lua
new file mode 100644
index 0000000..bba6873
--- /dev/null
+++ b/src/luarocks/fs/win32.lua
@@ -0,0 +1,384 @@
+--- Windows implementation of filesystem and platform abstractions.
+-- Download http://unxutils.sourceforge.net/ for Windows GNU utilities
+-- used by this module.
+local win32 = {}
+
+local fs = require("luarocks.fs")
+
+local cfg = require("luarocks.core.cfg")
+local dir = require("luarocks.dir")
+local path = require("luarocks.path")
+local util = require("luarocks.util")
+
+-- Monkey patch io.popen and os.execute to make sure quoting
+-- works as expected.
+-- See http://lua-users.org/lists/lua-l/2013-11/msg00367.html
+local _prefix = "type NUL && "
+local _popen, _execute = io.popen, os.execute
+
+-- luacheck: push globals io os
+io.popen = function(cmd, ...) return _popen(_prefix..cmd, ...) end
+os.execute = function(cmd, ...) return _execute(_prefix..cmd, ...) end
+-- luacheck: pop
+
+--- Annotate command string for quiet execution.
+-- @param cmd string: A command-line string.
+-- @return string: The command-line, with silencing annotation.
+function win32.quiet(cmd)
+ return cmd.." 2> NUL 1> NUL"
+end
+
+--- Annotate command string for execution with quiet stderr.
+-- @param cmd string: A command-line string.
+-- @return string: The command-line, with stderr silencing annotation.
+function win32.quiet_stderr(cmd)
+ return cmd.." 2> NUL"
+end
+
+function win32.execute_env(env, command, ...)
+ assert(type(command) == "string")
+ local cmdstr = {}
+ for var, val in pairs(env) do
+ table.insert(cmdstr, fs.export_cmd(var, val))
+ end
+ table.insert(cmdstr, fs.quote_args(command, ...))
+ return fs.execute_string(table.concat(cmdstr, " & "))
+end
+
+-- Split path into drive, root and the rest.
+-- Example: "c:\\hello\\world" becomes "c:" "\\" "hello\\world"
+-- if any part is missing from input, it becomes an empty string.
+local function split_root(pathname)
+ local drive = ""
+ local root = ""
+ local rest
+
+ local unquoted = pathname:match("^['\"](.*)['\"]$")
+ if unquoted then
+ pathname = unquoted
+ end
+
+ if pathname:match("^.:") then
+ drive = pathname:sub(1, 2)
+ pathname = pathname:sub(3)
+ end
+
+ if pathname:match("^[\\/]") then
+ root = pathname:sub(1, 1)
+ rest = pathname:sub(2)
+ else
+ rest = pathname
+ end
+
+ return drive, root, rest
+end
+
+--- Quote argument for shell processing. Fixes paths on Windows.
+-- Adds double quotes and escapes.
+-- @param arg string: Unquoted argument.
+-- @return string: Quoted argument.
+function win32.Q(arg)
+ assert(type(arg) == "string")
+ -- Use Windows-specific directory separator for paths.
+ -- Paths should be converted to absolute by now.
+ local drive, root, rest = split_root(arg)
+ if root ~= "" then
+ arg = arg:gsub("/", "\\")
+ end
+ if arg == "\\" then
+ return '\\' -- CHDIR needs special handling for root dir
+ end
+ -- URLs and anything else
+ arg = arg:gsub('\\(\\*)"', '\\%1%1"')
+ arg = arg:gsub('\\+$', '%0%0')
+ arg = arg:gsub('"', '\\"')
+ arg = arg:gsub('(\\*)%%', '%1%1"%%"')
+ return '"' .. arg .. '"'
+end
+
+--- Quote argument for shell processing in batch files.
+-- Adds double quotes and escapes.
+-- @param arg string: Unquoted argument.
+-- @return string: Quoted argument.
+function win32.Qb(arg)
+ assert(type(arg) == "string")
+ -- Use Windows-specific directory separator for paths.
+ -- Paths should be converted to absolute by now.
+ local drive, root, rest = split_root(arg)
+ if root ~= "" then
+ arg = arg:gsub("/", "\\")
+ end
+ if arg == "\\" then
+ return '\\' -- CHDIR needs special handling for root dir
+ end
+ -- URLs and anything else
+ arg = arg:gsub('\\(\\*)"', '\\%1%1"')
+ arg = arg:gsub('\\+$', '%0%0')
+ arg = arg:gsub('"', '\\"')
+ arg = arg:gsub('%%', '%%%%')
+ return '"' .. arg .. '"'
+end
+
+--- Return an absolute pathname from a potentially relative one.
+-- @param pathname string: pathname to convert.
+-- @param relative_to string or nil: path to prepend when making
+-- pathname absolute, or the current dir in the dir stack if
+-- not given.
+-- @return string: The pathname converted to absolute.
+function win32.absolute_name(pathname, relative_to)
+ assert(type(pathname) == "string")
+ assert(type(relative_to) == "string" or not relative_to)
+
+ relative_to = (relative_to or fs.current_dir()):gsub("[\\/]*$", "")
+ local drive, root, rest = split_root(pathname)
+ if root:match("[\\/]$") then
+ -- It's an absolute path already. Ensure is not quoted.
+ return dir.normalize(drive .. root .. rest)
+ else
+ -- It's a relative path, join it with base path.
+ -- This drops drive letter from paths like "C:foo".
+ return dir.path(relative_to, rest)
+ end
+end
+
+--- Return the root directory for the given path.
+-- For example, for "c:\hello", returns "c:\"
+-- @param pathname string: pathname to use.
+-- @return string: The root of the given pathname.
+function win32.root_of(pathname)
+ local drive, root, rest = split_root(fs.absolute_name(pathname))
+ return drive .. root
+end
+
+--- Create a wrapper to make a script executable from the command-line.
+-- @param script string: Pathname of script to be made executable.
+-- @param target string: wrapper target pathname (without wrapper suffix).
+-- @param name string: rock name to be used in loader context.
+-- @param version string: rock version to be used in loader context.
+-- @return boolean or (nil, string): True if succeeded, or nil and
+-- an error message.
+function win32.wrap_script(script, target, deps_mode, name, version, ...)
+ assert(type(script) == "string" or not script)
+ assert(type(target) == "string")
+ assert(type(deps_mode) == "string")
+ assert(type(name) == "string" or not name)
+ assert(type(version) == "string" or not version)
+
+ local wrapper = io.open(target, "wb")
+ if not wrapper then
+ return nil, "Could not open "..target.." for writing."
+ end
+
+ local lpath, lcpath = path.package_paths(deps_mode)
+
+ local luainit = {
+ "package.path="..util.LQ(lpath..";").."..package.path",
+ "package.cpath="..util.LQ(lcpath..";").."..package.cpath",
+ }
+
+ local remove_interpreter = false
+ local base = dir.base_name(target):gsub("%..*$", "")
+ if base == "luarocks" or base == "luarocks-admin" then
+ if cfg.is_binary then
+ remove_interpreter = true
+ end
+ luainit = {
+ "package.path="..util.LQ(package.path),
+ "package.cpath="..util.LQ(package.cpath),
+ }
+ end
+
+ if name and version then
+ local addctx = "local k,l,_=pcall(require,'luarocks.loader') _=k " ..
+ "and l.add_context('"..name.."','"..version.."')"
+ table.insert(luainit, addctx)
+ end
+
+ local argv = {
+ fs.Qb(cfg.variables["LUA"]),
+ "-e",
+ fs.Qb(table.concat(luainit, ";")),
+ script and fs.Qb(script) or "%I%",
+ ...
+ }
+ if remove_interpreter then
+ table.remove(argv, 1)
+ table.remove(argv, 1)
+ table.remove(argv, 1)
+ end
+
+ wrapper:write("@echo off\r\n")
+ wrapper:write("setlocal\r\n")
+ if not script then
+ wrapper:write([[IF "%*"=="" (set I=-i) ELSE (set I=)]] .. "\r\n")
+ end
+ wrapper:write("set "..fs.Qb("LUAROCKS_SYSCONFDIR="..cfg.sysconfdir) .. "\r\n")
+ wrapper:write(table.concat(argv, " ") .. " %*\r\n")
+ wrapper:write("exit /b %ERRORLEVEL%\r\n")
+ wrapper:close()
+ return true
+end
+
+function win32.is_actual_binary(name)
+ name = name:lower()
+ if name:match("%.bat$") or name:match("%.exe$") then
+ return true
+ end
+ return false
+end
+
+function win32.copy_binary(filename, dest)
+ local ok, err = fs.copy(filename, dest)
+ if not ok then
+ return nil, err
+ end
+ local exe_pattern = "%.[Ee][Xx][Ee]$"
+ local base = dir.base_name(filename)
+ dest = dir.dir_name(dest)
+ if base:match(exe_pattern) then
+ base = base:gsub(exe_pattern, ".lua")
+ local helpname = dest.."\\"..base
+ local helper = io.open(helpname, "w")
+ if not helper then
+ return nil, "Could not open "..helpname.." for writing."
+ end
+ helper:write('package.path=\"'..package.path:gsub("\\","\\\\")..';\"..package.path\n')
+ helper:write('package.cpath=\"'..package.path:gsub("\\","\\\\")..';\"..package.cpath\n')
+ helper:close()
+ end
+ return true
+end
+
+--- Move a file on top of the other.
+-- The new file ceases to exist under its original name,
+-- and takes over the name of the old file.
+-- On Windows this is done by removing the original file and
+-- renaming the new file to its original name.
+-- @param old_file The name of the original file,
+-- which will be the new name of new_file.
+-- @param new_file The name of the new file,
+-- which will replace old_file.
+-- @return boolean or (nil, string): True if succeeded, or nil and
+-- an error message.
+function win32.replace_file(old_file, new_file)
+ os.remove(old_file)
+ return os.rename(new_file, old_file)
+end
+
+function win32.is_dir(file)
+ file = fs.absolute_name(file)
+ file = dir.normalize(file)
+ local fd, _, code = io.open(file, "r")
+ if code == 13 then -- directories return "Permission denied"
+ fd, _, code = io.open(file .. "\\", "r")
+ if code == 2 then -- directories return 2, files return 22
+ return true
+ end
+ end
+ if fd then
+ fd:close()
+ end
+ return false
+end
+
+function win32.is_file(file)
+ file = fs.absolute_name(file)
+ file = dir.normalize(file)
+ local fd, _, code = io.open(file, "r")
+ if code == 13 then -- if "Permission denied"
+ fd, _, code = io.open(file .. "\\", "r")
+ if code == 2 then -- directories return 2, files return 22
+ return false
+ elseif code == 22 then
+ return true
+ end
+ end
+ if fd then
+ fd:close()
+ return true
+ end
+ return false
+end
+
+--- Test is file/dir is writable.
+-- Warning: testing if a file/dir is writable does not guarantee
+-- that it will remain writable and therefore it is no replacement
+-- for checking the result of subsequent operations.
+-- @param file string: filename to test
+-- @return boolean: true if file exists, false otherwise.
+function win32.is_writable(file)
+ assert(file)
+ file = dir.normalize(file)
+ local result
+ local tmpname = 'tmpluarockstestwritable.deleteme'
+ if fs.is_dir(file) then
+ local file2 = dir.path(file, tmpname)
+ local fh = io.open(file2, 'wb')
+ result = fh ~= nil
+ if fh then fh:close() end
+ if result then
+ -- the above test might give a false positive when writing to
+ -- c:\program files\ because of VirtualStore redirection on Vista and up
+ -- So check whether it's really there
+ result = fs.exists(file2)
+ end
+ os.remove(file2)
+ else
+ local fh = io.open(file, 'r+b')
+ result = fh ~= nil
+ if fh then fh:close() end
+ end
+ return result
+end
+
+function win32.tmpname()
+ local name = os.tmpname()
+ local tmp = os.getenv("TMP")
+ if tmp and name:sub(1, #tmp) ~= tmp then
+ name = (tmp .. "\\" .. name):gsub("\\+", "\\")
+ end
+ return name
+end
+
+function win32.current_user()
+ return os.getenv("USERNAME")
+end
+
+function win32.is_superuser()
+ return false
+end
+
+function win32.export_cmd(var, val)
+ return ("SET %s"):format(fs.Q(var.."="..val))
+end
+
+function win32.system_cache_dir()
+ return dir.path(fs.system_temp_dir(), "cache")
+end
+
+function win32.search_in_path(program)
+ if program:match("\\") then
+ local fd = io.open(dir.path(program), "r")
+ if fd then
+ fd:close()
+ return true, program
+ end
+
+ return false
+ end
+
+ if not program:lower():match("exe$") then
+ program = program .. ".exe"
+ end
+
+ for d in (os.getenv("PATH") or ""):gmatch("([^;]+)") do
+ local fd = io.open(dir.path(d, program), "r")
+ if fd then
+ fd:close()
+ return true, d
+ end
+ end
+ return false
+end
+
+return win32