Andrew Top | 2ea2238 | 2016-12-08 09:47:36 -0800 | [diff] [blame] | 1 | # This Source Code Form is subject to the terms of the Mozilla Public |
| 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
| 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| 4 | |
| 5 | # This is a partial python port of nsinstall. |
| 6 | # It's intended to be used when there's no natively compile nsinstall |
| 7 | # available, and doesn't intend to be fully equivalent. |
| 8 | # Its major use is for l10n repackaging on systems that don't have |
| 9 | # a full build environment set up. |
| 10 | # The basic limitation is, it doesn't even try to link and ignores |
| 11 | # all related options. |
| 12 | from __future__ import print_function |
| 13 | from optparse import OptionParser |
| 14 | import os |
| 15 | import os.path |
| 16 | import sys |
| 17 | import shutil |
| 18 | import stat |
| 19 | |
| 20 | def _nsinstall_internal(argv): |
| 21 | usage = "usage: %prog [options] arg1 [arg2 ...] target-directory" |
| 22 | p = OptionParser(usage=usage) |
| 23 | |
| 24 | p.add_option('-D', action="store_true", |
| 25 | help="Create a single directory only") |
| 26 | p.add_option('-t', action="store_true", |
| 27 | help="Preserve time stamp") |
| 28 | p.add_option('-m', action="store", |
| 29 | help="Set mode", metavar="mode") |
| 30 | p.add_option('-d', action="store_true", |
| 31 | help="Create directories in target") |
| 32 | p.add_option('-R', action="store_true", |
| 33 | help="Use relative symbolic links (ignored)") |
| 34 | p.add_option('-L', action="store", metavar="linkprefix", |
| 35 | help="Link prefix (ignored)") |
| 36 | p.add_option('-X', action="append", metavar="file", |
| 37 | help="Ignore a file when installing a directory recursively.") |
| 38 | |
| 39 | # The remaining arguments are not used in our tree, thus they're not |
| 40 | # implented. |
| 41 | def BadArg(option, opt, value, parser): |
| 42 | parser.error('option not supported: {0}'.format(opt)) |
| 43 | |
| 44 | p.add_option('-C', action="callback", metavar="CWD", |
| 45 | callback=BadArg, |
| 46 | help="NOT SUPPORTED") |
| 47 | p.add_option('-o', action="callback", callback=BadArg, |
| 48 | help="Set owner (NOT SUPPORTED)", metavar="owner") |
| 49 | p.add_option('-g', action="callback", callback=BadArg, |
| 50 | help="Set group (NOT SUPPORTED)", metavar="group") |
| 51 | |
| 52 | (options, args) = p.parse_args(argv) |
| 53 | |
| 54 | if options.m: |
| 55 | # mode is specified |
| 56 | try: |
| 57 | options.m = int(options.m, 8) |
| 58 | except: |
| 59 | sys.stderr.write('nsinstall: {0} is not a valid mode\n' |
| 60 | .format(options.m)) |
| 61 | return 1 |
| 62 | |
| 63 | # just create one directory? |
| 64 | def maybe_create_dir(dir, mode, try_again): |
| 65 | dir = os.path.abspath(dir) |
| 66 | if os.path.exists(dir): |
| 67 | if not os.path.isdir(dir): |
| 68 | print('nsinstall: {0} is not a directory'.format(dir), file=sys.stderr) |
| 69 | return 1 |
| 70 | if mode: |
| 71 | os.chmod(dir, mode) |
| 72 | return 0 |
| 73 | |
| 74 | try: |
| 75 | if mode: |
| 76 | os.makedirs(dir, mode) |
| 77 | else: |
| 78 | os.makedirs(dir) |
| 79 | except Exception as e: |
| 80 | # We might have hit EEXIST due to a race condition (see bug 463411) -- try again once |
| 81 | if try_again: |
| 82 | return maybe_create_dir(dir, mode, False) |
| 83 | print("nsinstall: failed to create directory {0}: {1}".format(dir, e)) |
| 84 | return 1 |
| 85 | else: |
| 86 | return 0 |
| 87 | |
| 88 | if options.X: |
| 89 | options.X = [os.path.abspath(p) for p in options.X] |
| 90 | |
| 91 | if options.D: |
| 92 | return maybe_create_dir(args[0], options.m, True) |
| 93 | |
| 94 | # nsinstall arg1 [...] directory |
| 95 | if len(args) < 2: |
| 96 | p.error('not enough arguments') |
| 97 | |
| 98 | def copy_all_entries(entries, target): |
| 99 | for e in entries: |
| 100 | e = os.path.abspath(e) |
| 101 | if options.X and e in options.X: |
| 102 | continue |
| 103 | |
| 104 | dest = os.path.join(target, os.path.basename(e)) |
| 105 | dest = os.path.abspath(dest) |
| 106 | handleTarget(e, dest) |
| 107 | if options.m: |
| 108 | os.chmod(dest, options.m) |
| 109 | |
| 110 | # set up handler |
| 111 | if options.d: |
| 112 | # we're supposed to create directories |
| 113 | def handleTarget(srcpath, targetpath): |
| 114 | # target directory was already created, just use mkdir |
| 115 | os.mkdir(targetpath) |
| 116 | else: |
| 117 | # we're supposed to copy files |
| 118 | def handleTarget(srcpath, targetpath): |
| 119 | if os.path.isdir(srcpath): |
| 120 | if not os.path.exists(targetpath): |
| 121 | os.mkdir(targetpath) |
| 122 | entries = [os.path.join(srcpath, e) for e in os.listdir(srcpath)] |
| 123 | copy_all_entries(entries, targetpath) |
| 124 | # options.t is not relevant for directories |
| 125 | if options.m: |
| 126 | os.chmod(targetpath, options.m) |
| 127 | else: |
| 128 | if os.path.exists(targetpath): |
| 129 | # On Windows, read-only files can't be deleted |
| 130 | if sys.platform == "win32": |
| 131 | os.chmod(targetpath, stat.S_IWUSR) |
| 132 | os.remove(targetpath) |
| 133 | if options.t: |
| 134 | shutil.copy2(srcpath, targetpath) |
| 135 | else: |
| 136 | shutil.copy(srcpath, targetpath) |
| 137 | |
| 138 | # the last argument is the target directory |
| 139 | target = args.pop() |
| 140 | # ensure target directory (importantly, we do not apply a mode to the directory |
| 141 | # because we want to copy files into it and the mode might be read-only) |
| 142 | rv = maybe_create_dir(target, None, True) |
| 143 | if rv != 0: |
| 144 | return rv |
| 145 | |
| 146 | copy_all_entries(args, target) |
| 147 | return 0 |
| 148 | |
| 149 | # nsinstall as a native command is always UTF-8 |
| 150 | def nsinstall(argv): |
| 151 | return _nsinstall_internal([unicode(arg, "utf-8") for arg in argv]) |
| 152 | |
| 153 | if __name__ == '__main__': |
| 154 | # sys.argv corrupts characters outside the system code page on Windows |
| 155 | # <http://bugs.python.org/issue2128>. Use ctypes instead. This is also |
| 156 | # useful because switching to Unicode strings makes python use the wide |
| 157 | # Windows APIs, which is what we want here since the wide APIs normally do a |
| 158 | # better job at handling long paths and such. |
| 159 | if sys.platform == "win32": |
| 160 | import ctypes |
| 161 | from ctypes import wintypes |
| 162 | GetCommandLine = ctypes.windll.kernel32.GetCommandLineW |
| 163 | GetCommandLine.argtypes = [] |
| 164 | GetCommandLine.restype = wintypes.LPWSTR |
| 165 | |
| 166 | CommandLineToArgv = ctypes.windll.shell32.CommandLineToArgvW |
| 167 | CommandLineToArgv.argtypes = [wintypes.LPWSTR, ctypes.POINTER(ctypes.c_int)] |
| 168 | CommandLineToArgv.restype = ctypes.POINTER(wintypes.LPWSTR) |
| 169 | |
| 170 | argc = ctypes.c_int(0) |
| 171 | argv_arr = CommandLineToArgv(GetCommandLine(), ctypes.byref(argc)) |
| 172 | # The first argv will be "python", the second will be the .py file |
| 173 | argv = argv_arr[1:argc.value] |
| 174 | else: |
| 175 | # For consistency, do it on Unix as well |
| 176 | if sys.stdin.encoding is not None: |
| 177 | argv = [unicode(arg, sys.stdin.encoding) for arg in sys.argv] |
| 178 | else: |
| 179 | argv = [unicode(arg) for arg in sys.argv] |
| 180 | |
| 181 | sys.exit(_nsinstall_internal(argv[1:])) |