PageRenderTime 28ms CodeModel.GetById 12ms app.highlight 12ms RepoModel.GetById 1ms app.codeStats 0ms

/src/luarocks/fs/win32.lua

http://github.com/keplerproject/luarocks
Lua | 354 lines | 278 code | 28 blank | 48 comment | 37 complexity | 46e689192997178541a392b384374321 MD5 | raw file
  1--- Windows implementation of filesystem and platform abstractions.
  2-- Download http://unxutils.sourceforge.net/ for Windows GNU utilities
  3-- used by this module.
  4local win32 = {}
  5
  6local fs = require("luarocks.fs")
  7
  8local cfg = require("luarocks.core.cfg")
  9local dir = require("luarocks.dir")
 10local path = require("luarocks.path")
 11local util = require("luarocks.util")
 12
 13-- Monkey patch io.popen and os.execute to make sure quoting
 14-- works as expected.
 15-- See http://lua-users.org/lists/lua-l/2013-11/msg00367.html
 16local _prefix = "type NUL && "
 17local _popen, _execute = io.popen, os.execute
 18io.popen = function(cmd, ...) return _popen(_prefix..cmd, ...) end
 19os.execute = function(cmd, ...) return _execute(_prefix..cmd, ...) end
 20
 21--- Annotate command string for quiet execution.
 22-- @param cmd string: A command-line string.
 23-- @return string: The command-line, with silencing annotation.
 24function win32.quiet(cmd)
 25   return cmd.." 2> NUL 1> NUL"
 26end
 27
 28--- Annotate command string for execution with quiet stderr.
 29-- @param cmd string: A command-line string.
 30-- @return string: The command-line, with stderr silencing annotation.
 31function win32.quiet_stderr(cmd)
 32   return cmd.." 2> NUL"
 33end
 34
 35-- Split path into drive, root and the rest.
 36-- Example: "c:\\hello\\world" becomes "c:" "\\" "hello\\world"
 37-- if any part is missing from input, it becomes an empty string.
 38local function split_root(pathname)
 39   local drive = ""
 40   local root = ""
 41   local rest = ""
 42
 43   local unquoted = pathname:match("^['\"](.*)['\"]$")
 44   if unquoted then
 45      pathname = unquoted
 46   end
 47
 48   if pathname:match("^.:") then
 49      drive = pathname:sub(1, 2)
 50      pathname = pathname:sub(3)
 51   end
 52
 53   if pathname:match("^[\\/]") then
 54      root = pathname:sub(1, 1)
 55      rest = pathname:sub(2)
 56   else
 57      rest = pathname
 58   end
 59
 60   return drive, root, rest
 61end
 62
 63--- Quote argument for shell processing. Fixes paths on Windows.
 64-- Adds double quotes and escapes.
 65-- @param arg string: Unquoted argument.
 66-- @return string: Quoted argument.
 67function win32.Q(arg)
 68   assert(type(arg) == "string")
 69   -- Use Windows-specific directory separator for paths.
 70   -- Paths should be converted to absolute by now.
 71   local drive, root, rest = split_root(arg)
 72   if root ~= "" then
 73      arg = arg:gsub("/", "\\")
 74   end
 75   if arg == "\\" then
 76      return '\\' -- CHDIR needs special handling for root dir
 77   end
 78   -- URLs and anything else
 79   arg = arg:gsub('\\(\\*)"', '\\%1%1"')
 80   arg = arg:gsub('\\+$', '%0%0')
 81   arg = arg:gsub('"', '\\"')
 82   arg = arg:gsub('(\\*)%%', '%1%1"%%"')
 83   return '"' .. arg .. '"'
 84end
 85
 86--- Quote argument for shell processing in batch files.
 87-- Adds double quotes and escapes.
 88-- @param arg string: Unquoted argument.
 89-- @return string: Quoted argument.
 90function win32.Qb(arg)
 91   assert(type(arg) == "string")
 92   -- Use Windows-specific directory separator for paths.
 93   -- Paths should be converted to absolute by now.
 94   local drive, root, rest = split_root(arg)
 95   if root ~= "" then
 96      arg = arg:gsub("/", "\\")
 97   end
 98   if arg == "\\" then
 99      return '\\' -- CHDIR needs special handling for root dir
100   end
101   -- URLs and anything else
102   arg = arg:gsub('\\(\\*)"', '\\%1%1"')
103   arg = arg:gsub('\\+$', '%0%0')
104   arg = arg:gsub('"', '\\"')
105   arg = arg:gsub('%%', '%%%%')
106   return '"' .. arg .. '"'
107end
108
109--- Return an absolute pathname from a potentially relative one.
110-- @param pathname string: pathname to convert.
111-- @param relative_to string or nil: path to prepend when making
112-- pathname absolute, or the current dir in the dir stack if
113-- not given.
114-- @return string: The pathname converted to absolute.
115function win32.absolute_name(pathname, relative_to)
116   assert(type(pathname) == "string")
117   assert(type(relative_to) == "string" or not relative_to)
118
119   relative_to = (relative_to or fs.current_dir()):gsub("[\\/]*$", "")
120   local drive, root, rest = split_root(pathname)
121   if root:match("[\\/]$") then
122      -- It's an absolute path already. Ensure is not quoted.
123      return drive .. root .. rest
124   else
125      -- It's a relative path, join it with base path.
126      -- This drops drive letter from paths like "C:foo".
127      return relative_to .. "/" .. rest
128   end
129end
130
131--- Return the root directory for the given path.
132-- For example, for "c:\hello", returns "c:\"
133-- @param pathname string: pathname to use.
134-- @return string: The root of the given pathname.
135function win32.root_of(pathname)
136   local drive, root, rest = split_root(fs.absolute_name(pathname))
137   return drive .. root
138end
139
140--- Create a wrapper to make a script executable from the command-line.
141-- @param script string: Pathname of script to be made executable.
142-- @param target string: wrapper target pathname (without wrapper suffix).
143-- @param name string: rock name to be used in loader context.
144-- @param version string: rock version to be used in loader context.
145-- @return boolean or (nil, string): True if succeeded, or nil and
146-- an error message.
147function win32.wrap_script(script, target, deps_mode, name, version, ...)
148   assert(type(script) == "string" or not script)
149   assert(type(target) == "string")
150   assert(type(deps_mode) == "string")
151   assert(type(name) == "string" or not name)
152   assert(type(version) == "string" or not version)
153
154   local batname = target .. ".bat"
155   local wrapper = io.open(batname, "wb")
156   if not wrapper then
157      return nil, "Could not open "..batname.." for writing."
158   end
159
160   local lpath, lcpath = path.package_paths(deps_mode)
161
162   local luainit = {
163      "package.path="..util.LQ(lpath..";").."..package.path",
164      "package.cpath="..util.LQ(lcpath..";").."..package.cpath",
165   }
166
167   local remove_interpreter = false
168   if target == "luarocks" or target == "luarocks-admin" then
169      if cfg.is_binary then
170         remove_interpreter = true
171      end
172      luainit = {
173         "package.path="..util.LQ(package.path),
174         "package.cpath="..util.LQ(package.cpath),
175      }
176   end
177
178   if name and version then
179      local addctx = "local k,l,_=pcall(require,'luarocks.loader') _=k " ..
180                     "and l.add_context('"..name.."','"..version.."')"
181      table.insert(luainit, addctx)
182   end
183
184   local argv = {
185      fs.Qb(dir.path(cfg.variables["LUA_BINDIR"], cfg.lua_interpreter)),
186      "-e",
187      fs.Qb(table.concat(luainit, ";")),
188      script and fs.Qb(script) or "%I%",
189      ...
190   }
191   if remove_interpreter then
192      table.remove(argv, 1)
193      table.remove(argv, 1)
194      table.remove(argv, 1)
195   end
196
197   wrapper:write("@echo off\r\n")
198   wrapper:write("setlocal\r\n")
199   if not script then
200      wrapper:write([[IF "%*"=="" (set I=-i) ELSE (set I=)]] .. "\r\n")
201   end
202   wrapper:write("set "..fs.Qb("LUAROCKS_SYSCONFDIR="..cfg.sysconfdir) .. "\r\n")
203   wrapper:write(table.concat(argv, " ") .. " %*\r\n")
204   wrapper:write("exit /b %ERRORLEVEL%\r\n")
205   wrapper:close()
206   return true
207end
208
209function win32.is_actual_binary(name)
210   name = name:lower()
211   if name:match("%.bat$") or name:match("%.exe$") then
212      return true
213   end
214   return false
215end
216
217function win32.copy_binary(filename, dest) 
218   local ok, err = fs.copy(filename, dest)
219   if not ok then
220      return nil, err
221   end
222   local exe_pattern = "%.[Ee][Xx][Ee]$"
223   local base = dir.base_name(filename)
224   dest = dir.dir_name(dest)
225   if base:match(exe_pattern) then
226      base = base:gsub(exe_pattern, ".lua")
227      local helpname = dest.."/"..base
228      local helper = io.open(helpname, "w")
229      if not helper then
230         return nil, "Could not open "..helpname.." for writing."
231      end
232      helper:write('package.path=\"'..package.path:gsub("\\","\\\\")..';\"..package.path\n')
233      helper:write('package.cpath=\"'..package.path:gsub("\\","\\\\")..';\"..package.cpath\n')
234      helper:close()
235   end
236   return true
237end
238
239--- Move a file on top of the other.
240-- The new file ceases to exist under its original name,
241-- and takes over the name of the old file.
242-- On Windows this is done by removing the original file and
243-- renaming the new file to its original name.
244-- @param old_file The name of the original file,
245-- which will be the new name of new_file.
246-- @param new_file The name of the new file,
247-- which will replace old_file.
248-- @return boolean or (nil, string): True if succeeded, or nil and
249-- an error message.
250function win32.replace_file(old_file, new_file)
251   os.remove(old_file)
252   return os.rename(new_file, old_file)
253end
254
255function win32.is_dir(file)
256   file = fs.absolute_name(file)
257   file = dir.normalize(file)
258   local fd, _, code = io.open(file, "r")
259   if code == 13 then -- directories return "Permission denied"
260      fd, _, code = io.open(file .. "\\", "r")
261      if code == 2 then -- directories return 2, files return 22
262         return true
263      end
264   end
265   if fd then
266      fd:close()
267   end
268   return false
269end
270
271function win32.is_file(file)
272   file = fs.absolute_name(file)
273   file = dir.normalize(file)
274   local fd, _, code = io.open(file, "r")
275   if code == 13 then -- if "Permission denied"
276      fd, _, code = io.open(file .. "\\", "r")
277      if code == 2 then -- directories return 2, files return 22
278         return false
279      elseif code == 22 then
280         return true
281      end
282   end
283   if fd then
284      fd:close()
285      return true
286   end
287   return false
288end
289
290--- Test is file/dir is writable.
291-- Warning: testing if a file/dir is writable does not guarantee
292-- that it will remain writable and therefore it is no replacement
293-- for checking the result of subsequent operations.
294-- @param file string: filename to test
295-- @return boolean: true if file exists, false otherwise.
296function win32.is_writable(file)
297   assert(file)
298   file = dir.normalize(file)
299   local result
300   local tmpname = 'tmpluarockstestwritable.deleteme'
301   if fs.is_dir(file) then
302      local file2 = dir.path(file, tmpname)
303      local fh = io.open(file2, 'wb')
304      result = fh ~= nil
305      if fh then fh:close() end
306      if result then
307         -- the above test might give a false positive when writing to
308         -- c:\program files\ because of VirtualStore redirection on Vista and up
309         -- So check whether it's really there
310         result = fs.exists(file2)
311      end
312      os.remove(file2)
313   else
314      local fh = io.open(file, 'r+b')
315      result = fh ~= nil
316      if fh then fh:close() end
317   end
318   return result
319end
320
321--- Create a temporary directory.
322-- @param name_pattern string: name pattern to use for avoiding conflicts
323-- when creating temporary directory.
324-- @return string or (nil, string): name of temporary directory or (nil, error message) on failure.
325function win32.make_temp_dir(name_pattern)
326   assert(type(name_pattern) == "string")
327   name_pattern = dir.normalize(name_pattern)
328
329   local temp_dir = os.getenv("TMP") .. "/luarocks_" .. name_pattern:gsub("/", "_") .. "-" .. tostring(math.floor(math.random() * 10000))
330   local ok, err = fs.make_dir(temp_dir)
331   if ok then
332      return temp_dir
333   else
334      return nil, err
335   end
336end
337
338function win32.tmpname()
339   return os.getenv("TMP")..os.tmpname()
340end
341
342function win32.current_user()
343   return os.getenv("USERNAME")
344end
345
346function win32.export_cmd(var, val)
347   return ("SET %s=%s"):format(var, val)
348end
349
350function win32.system_cache_dir()
351   return dir.path(fs.system_temp_dir(), "cache")
352end
353
354return win32