nsinstall.py (5538B)
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 import os 13 import os.path 14 import shutil 15 import sys 16 from optparse import OptionParser 17 18 import mozfile 19 20 21 def _nsinstall_internal(argv): 22 usage = "usage: %prog [options] arg1 [arg2 ...] target-directory" 23 p = OptionParser(usage=usage) 24 25 p.add_option("-D", action="store_true", help="Create a single directory only") 26 p.add_option("-t", action="store_true", help="Preserve time stamp") 27 p.add_option("-m", action="store", help="Set mode", metavar="mode") 28 p.add_option("-d", action="store_true", help="Create directories in target") 29 p.add_option( 30 "-R", action="store_true", help="Use relative symbolic links (ignored)" 31 ) 32 p.add_option( 33 "-L", action="store", metavar="linkprefix", help="Link prefix (ignored)" 34 ) 35 p.add_option( 36 "-X", 37 action="append", 38 metavar="file", 39 help="Ignore a file when installing a directory recursively.", 40 ) 41 42 # The remaining arguments are not used in our tree, thus they're not 43 # implented. 44 def BadArg(option, opt, value, parser): 45 parser.error(f"option not supported: {opt}") 46 47 p.add_option( 48 "-C", action="callback", metavar="CWD", callback=BadArg, help="NOT SUPPORTED" 49 ) 50 p.add_option( 51 "-o", 52 action="callback", 53 callback=BadArg, 54 help="Set owner (NOT SUPPORTED)", 55 metavar="owner", 56 ) 57 p.add_option( 58 "-g", 59 action="callback", 60 callback=BadArg, 61 help="Set group (NOT SUPPORTED)", 62 metavar="group", 63 ) 64 65 (options, args) = p.parse_args(argv) 66 67 if options.m: 68 # mode is specified 69 try: 70 options.m = int(options.m, 8) 71 except Exception: 72 sys.stderr.write(f"nsinstall: {options.m} is not a valid mode\n") 73 return 1 74 75 # just create one directory? 76 def maybe_create_dir(dir, mode, try_again): 77 dir = os.path.abspath(dir) 78 if os.path.exists(dir): 79 if not os.path.isdir(dir): 80 print(f"nsinstall: {dir} is not a directory", file=sys.stderr) 81 return 1 82 if mode: 83 os.chmod(dir, mode) 84 return 0 85 86 try: 87 if mode: 88 os.makedirs(dir, mode) 89 else: 90 os.makedirs(dir) 91 except Exception as e: 92 # We might have hit EEXIST due to a race condition (see bug 463411) -- try again once 93 if try_again: 94 return maybe_create_dir(dir, mode, False) 95 print(f"nsinstall: failed to create directory {dir}: {e}") 96 return 1 97 else: 98 return 0 99 100 if options.X: 101 options.X = [os.path.abspath(path) for path in options.X] 102 103 if options.D: 104 return maybe_create_dir(args[0], options.m, True) 105 106 # nsinstall arg1 [...] directory 107 if len(args) < 2: 108 p.error("not enough arguments") 109 110 def copy_all_entries(entries, target): 111 for e in entries: 112 e = os.path.abspath(e) 113 if options.X and e in options.X: 114 continue 115 116 dest = os.path.join(target, os.path.basename(e)) 117 dest = os.path.abspath(dest) 118 handleTarget(e, dest) 119 if options.m: 120 os.chmod(dest, options.m) 121 122 # set up handler 123 if options.d: 124 # we're supposed to create directories 125 def handleTarget(srcpath, targetpath): 126 # target directory was already created, just use mkdir 127 os.mkdir(targetpath) 128 129 else: 130 # we're supposed to copy files 131 def handleTarget(srcpath, targetpath): 132 if os.path.isdir(srcpath): 133 if not os.path.exists(targetpath): 134 os.mkdir(targetpath) 135 entries = [os.path.join(srcpath, e) for e in os.listdir(srcpath)] 136 copy_all_entries(entries, targetpath) 137 # options.t is not relevant for directories 138 if options.m: 139 os.chmod(targetpath, options.m) 140 else: 141 if os.path.exists(targetpath): 142 if sys.platform == "win32": 143 mozfile.remove(targetpath) 144 else: 145 os.remove(targetpath) 146 if options.t: 147 shutil.copy2(srcpath, targetpath) 148 else: 149 shutil.copy(srcpath, targetpath) 150 151 # the last argument is the target directory 152 target = args.pop() 153 # ensure target directory (importantly, we do not apply a mode to the directory 154 # because we want to copy files into it and the mode might be read-only) 155 rv = maybe_create_dir(target, None, True) 156 if rv != 0: 157 return rv 158 159 copy_all_entries(args, target) 160 return 0 161 162 163 # nsinstall as a native command is always UTF-8 164 165 166 def nsinstall(argv): 167 return _nsinstall_internal([arg for arg in argv]) 168 169 170 if __name__ == "__main__": 171 sys.exit(_nsinstall_internal(sys.argv[1:]))