diff options
| author | Mike Vink <mike@pionative.com> | 2025-02-03 21:29:42 +0100 |
|---|---|---|
| committer | Mike Vink <mike@pionative.com> | 2025-02-03 21:29:42 +0100 |
| commit | 5155816b7b925dec5d5feb1568b1d7ceb00938b9 (patch) | |
| tree | deca28ea15e79f6f804c3d90d2ba757881638af5 /src/luarocks/fs/win32.lua | |
Diffstat (limited to 'src/luarocks/fs/win32.lua')
| -rw-r--r-- | src/luarocks/fs/win32.lua | 384 |
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 |
