michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: # This is a partial python port of nsinstall. michael@0: # It's intended to be used when there's no natively compile nsinstall michael@0: # available, and doesn't intend to be fully equivalent. michael@0: # Its major use is for l10n repackaging on systems that don't have michael@0: # a full build environment set up. michael@0: # The basic limitation is, it doesn't even try to link and ignores michael@0: # all related options. michael@0: from __future__ import print_function michael@0: from optparse import OptionParser michael@0: import os michael@0: import os.path michael@0: import sys michael@0: import shutil michael@0: import stat michael@0: michael@0: def _nsinstall_internal(argv): michael@0: usage = "usage: %prog [options] arg1 [arg2 ...] target-directory" michael@0: p = OptionParser(usage=usage) michael@0: michael@0: p.add_option('-D', action="store_true", michael@0: help="Create a single directory only") michael@0: p.add_option('-t', action="store_true", michael@0: help="Preserve time stamp") michael@0: p.add_option('-m', action="store", michael@0: help="Set mode", metavar="mode") michael@0: p.add_option('-d', action="store_true", michael@0: help="Create directories in target") michael@0: p.add_option('-R', action="store_true", michael@0: help="Use relative symbolic links (ignored)") michael@0: p.add_option('-L', action="store", metavar="linkprefix", michael@0: help="Link prefix (ignored)") michael@0: p.add_option('-X', action="append", metavar="file", michael@0: help="Ignore a file when installing a directory recursively.") michael@0: michael@0: # The remaining arguments are not used in our tree, thus they're not michael@0: # implented. michael@0: def BadArg(option, opt, value, parser): michael@0: parser.error('option not supported: {0}'.format(opt)) michael@0: michael@0: p.add_option('-C', action="callback", metavar="CWD", michael@0: callback=BadArg, michael@0: help="NOT SUPPORTED") michael@0: p.add_option('-o', action="callback", callback=BadArg, michael@0: help="Set owner (NOT SUPPORTED)", metavar="owner") michael@0: p.add_option('-g', action="callback", callback=BadArg, michael@0: help="Set group (NOT SUPPORTED)", metavar="group") michael@0: michael@0: (options, args) = p.parse_args(argv) michael@0: michael@0: if options.m: michael@0: # mode is specified michael@0: try: michael@0: options.m = int(options.m, 8) michael@0: except: michael@0: sys.stderr.write('nsinstall: {0} is not a valid mode\n' michael@0: .format(options.m)) michael@0: return 1 michael@0: michael@0: # just create one directory? michael@0: def maybe_create_dir(dir, mode, try_again): michael@0: dir = os.path.abspath(dir) michael@0: if os.path.exists(dir): michael@0: if not os.path.isdir(dir): michael@0: print('nsinstall: {0} is not a directory'.format(dir), file=sys.stderr) michael@0: return 1 michael@0: if mode: michael@0: os.chmod(dir, mode) michael@0: return 0 michael@0: michael@0: try: michael@0: if mode: michael@0: os.makedirs(dir, mode) michael@0: else: michael@0: os.makedirs(dir) michael@0: except Exception as e: michael@0: # We might have hit EEXIST due to a race condition (see bug 463411) -- try again once michael@0: if try_again: michael@0: return maybe_create_dir(dir, mode, False) michael@0: print("nsinstall: failed to create directory {0}: {1}".format(dir, e)) michael@0: return 1 michael@0: else: michael@0: return 0 michael@0: michael@0: if options.X: michael@0: options.X = [os.path.abspath(p) for p in options.X] michael@0: michael@0: if options.D: michael@0: return maybe_create_dir(args[0], options.m, True) michael@0: michael@0: # nsinstall arg1 [...] directory michael@0: if len(args) < 2: michael@0: p.error('not enough arguments') michael@0: michael@0: def copy_all_entries(entries, target): michael@0: for e in entries: michael@0: e = os.path.abspath(e) michael@0: if options.X and e in options.X: michael@0: continue michael@0: michael@0: dest = os.path.join(target, os.path.basename(e)) michael@0: dest = os.path.abspath(dest) michael@0: handleTarget(e, dest) michael@0: if options.m: michael@0: os.chmod(dest, options.m) michael@0: michael@0: # set up handler michael@0: if options.d: michael@0: # we're supposed to create directories michael@0: def handleTarget(srcpath, targetpath): michael@0: # target directory was already created, just use mkdir michael@0: os.mkdir(targetpath) michael@0: else: michael@0: # we're supposed to copy files michael@0: def handleTarget(srcpath, targetpath): michael@0: if os.path.isdir(srcpath): michael@0: if not os.path.exists(targetpath): michael@0: os.mkdir(targetpath) michael@0: entries = [os.path.join(srcpath, e) for e in os.listdir(srcpath)] michael@0: copy_all_entries(entries, targetpath) michael@0: # options.t is not relevant for directories michael@0: if options.m: michael@0: os.chmod(targetpath, options.m) michael@0: else: michael@0: if os.path.exists(targetpath): michael@0: # On Windows, read-only files can't be deleted michael@0: os.chmod(targetpath, stat.S_IWUSR) michael@0: os.remove(targetpath) michael@0: if options.t: michael@0: shutil.copy2(srcpath, targetpath) michael@0: else: michael@0: shutil.copy(srcpath, targetpath) michael@0: michael@0: # the last argument is the target directory michael@0: target = args.pop() michael@0: # ensure target directory (importantly, we do not apply a mode to the directory michael@0: # because we want to copy files into it and the mode might be read-only) michael@0: rv = maybe_create_dir(target, None, True) michael@0: if rv != 0: michael@0: return rv michael@0: michael@0: copy_all_entries(args, target) michael@0: return 0 michael@0: michael@0: # nsinstall as a native command is always UTF-8 michael@0: def nsinstall(argv): michael@0: return _nsinstall_internal([unicode(arg, "utf-8") for arg in argv]) michael@0: michael@0: if __name__ == '__main__': michael@0: # sys.argv corrupts characters outside the system code page on Windows michael@0: # . Use ctypes instead. This is also michael@0: # useful because switching to Unicode strings makes python use the wide michael@0: # Windows APIs, which is what we want here since the wide APIs normally do a michael@0: # better job at handling long paths and such. michael@0: if sys.platform == "win32": michael@0: import ctypes michael@0: from ctypes import wintypes michael@0: GetCommandLine = ctypes.windll.kernel32.GetCommandLineW michael@0: GetCommandLine.argtypes = [] michael@0: GetCommandLine.restype = wintypes.LPWSTR michael@0: michael@0: CommandLineToArgv = ctypes.windll.shell32.CommandLineToArgvW michael@0: CommandLineToArgv.argtypes = [wintypes.LPWSTR, ctypes.POINTER(ctypes.c_int)] michael@0: CommandLineToArgv.restype = ctypes.POINTER(wintypes.LPWSTR) michael@0: michael@0: argc = ctypes.c_int(0) michael@0: argv_arr = CommandLineToArgv(GetCommandLine(), ctypes.byref(argc)) michael@0: # The first argv will be "python", the second will be the .py file michael@0: argv = argv_arr[1:argc.value] michael@0: else: michael@0: # For consistency, do it on Unix as well michael@0: if sys.stdin.encoding is not None: michael@0: argv = [unicode(arg, sys.stdin.encoding) for arg in sys.argv] michael@0: else: michael@0: argv = [unicode(arg) for arg in sys.argv] michael@0: michael@0: sys.exit(_nsinstall_internal(argv[1:]))