17db96d56Sopenharmony_ci"""distutils.command.build_scripts 27db96d56Sopenharmony_ci 37db96d56Sopenharmony_ciImplements the Distutils 'build_scripts' command.""" 47db96d56Sopenharmony_ci 57db96d56Sopenharmony_ciimport os, re 67db96d56Sopenharmony_cifrom stat import ST_MODE 77db96d56Sopenharmony_cifrom distutils import sysconfig 87db96d56Sopenharmony_cifrom distutils.core import Command 97db96d56Sopenharmony_cifrom distutils.dep_util import newer 107db96d56Sopenharmony_cifrom distutils.util import convert_path, Mixin2to3 117db96d56Sopenharmony_cifrom distutils import log 127db96d56Sopenharmony_ciimport tokenize 137db96d56Sopenharmony_ci 147db96d56Sopenharmony_ci# check if Python is called on the first line with this expression 157db96d56Sopenharmony_cifirst_line_re = re.compile(b'^#!.*python[0-9.]*([ \t].*)?$') 167db96d56Sopenharmony_ci 177db96d56Sopenharmony_ciclass build_scripts(Command): 187db96d56Sopenharmony_ci 197db96d56Sopenharmony_ci description = "\"build\" scripts (copy and fixup #! line)" 207db96d56Sopenharmony_ci 217db96d56Sopenharmony_ci user_options = [ 227db96d56Sopenharmony_ci ('build-dir=', 'd', "directory to \"build\" (copy) to"), 237db96d56Sopenharmony_ci ('force', 'f', "forcibly build everything (ignore file timestamps"), 247db96d56Sopenharmony_ci ('executable=', 'e', "specify final destination interpreter path"), 257db96d56Sopenharmony_ci ] 267db96d56Sopenharmony_ci 277db96d56Sopenharmony_ci boolean_options = ['force'] 287db96d56Sopenharmony_ci 297db96d56Sopenharmony_ci 307db96d56Sopenharmony_ci def initialize_options(self): 317db96d56Sopenharmony_ci self.build_dir = None 327db96d56Sopenharmony_ci self.scripts = None 337db96d56Sopenharmony_ci self.force = None 347db96d56Sopenharmony_ci self.executable = None 357db96d56Sopenharmony_ci self.outfiles = None 367db96d56Sopenharmony_ci 377db96d56Sopenharmony_ci def finalize_options(self): 387db96d56Sopenharmony_ci self.set_undefined_options('build', 397db96d56Sopenharmony_ci ('build_scripts', 'build_dir'), 407db96d56Sopenharmony_ci ('force', 'force'), 417db96d56Sopenharmony_ci ('executable', 'executable')) 427db96d56Sopenharmony_ci self.scripts = self.distribution.scripts 437db96d56Sopenharmony_ci 447db96d56Sopenharmony_ci def get_source_files(self): 457db96d56Sopenharmony_ci return self.scripts 467db96d56Sopenharmony_ci 477db96d56Sopenharmony_ci def run(self): 487db96d56Sopenharmony_ci if not self.scripts: 497db96d56Sopenharmony_ci return 507db96d56Sopenharmony_ci self.copy_scripts() 517db96d56Sopenharmony_ci 527db96d56Sopenharmony_ci 537db96d56Sopenharmony_ci def copy_scripts(self): 547db96d56Sopenharmony_ci r"""Copy each script listed in 'self.scripts'; if it's marked as a 557db96d56Sopenharmony_ci Python script in the Unix way (first line matches 'first_line_re', 567db96d56Sopenharmony_ci ie. starts with "\#!" and contains "python"), then adjust the first 577db96d56Sopenharmony_ci line to refer to the current Python interpreter as we copy. 587db96d56Sopenharmony_ci """ 597db96d56Sopenharmony_ci self.mkpath(self.build_dir) 607db96d56Sopenharmony_ci outfiles = [] 617db96d56Sopenharmony_ci updated_files = [] 627db96d56Sopenharmony_ci for script in self.scripts: 637db96d56Sopenharmony_ci adjust = False 647db96d56Sopenharmony_ci script = convert_path(script) 657db96d56Sopenharmony_ci outfile = os.path.join(self.build_dir, os.path.basename(script)) 667db96d56Sopenharmony_ci outfiles.append(outfile) 677db96d56Sopenharmony_ci 687db96d56Sopenharmony_ci if not self.force and not newer(script, outfile): 697db96d56Sopenharmony_ci log.debug("not copying %s (up-to-date)", script) 707db96d56Sopenharmony_ci continue 717db96d56Sopenharmony_ci 727db96d56Sopenharmony_ci # Always open the file, but ignore failures in dry-run mode -- 737db96d56Sopenharmony_ci # that way, we'll get accurate feedback if we can read the 747db96d56Sopenharmony_ci # script. 757db96d56Sopenharmony_ci try: 767db96d56Sopenharmony_ci f = open(script, "rb") 777db96d56Sopenharmony_ci except OSError: 787db96d56Sopenharmony_ci if not self.dry_run: 797db96d56Sopenharmony_ci raise 807db96d56Sopenharmony_ci f = None 817db96d56Sopenharmony_ci else: 827db96d56Sopenharmony_ci encoding, lines = tokenize.detect_encoding(f.readline) 837db96d56Sopenharmony_ci f.seek(0) 847db96d56Sopenharmony_ci first_line = f.readline() 857db96d56Sopenharmony_ci if not first_line: 867db96d56Sopenharmony_ci self.warn("%s is an empty file (skipping)" % script) 877db96d56Sopenharmony_ci continue 887db96d56Sopenharmony_ci 897db96d56Sopenharmony_ci match = first_line_re.match(first_line) 907db96d56Sopenharmony_ci if match: 917db96d56Sopenharmony_ci adjust = True 927db96d56Sopenharmony_ci post_interp = match.group(1) or b'' 937db96d56Sopenharmony_ci 947db96d56Sopenharmony_ci if adjust: 957db96d56Sopenharmony_ci log.info("copying and adjusting %s -> %s", script, 967db96d56Sopenharmony_ci self.build_dir) 977db96d56Sopenharmony_ci updated_files.append(outfile) 987db96d56Sopenharmony_ci if not self.dry_run: 997db96d56Sopenharmony_ci if not sysconfig.python_build: 1007db96d56Sopenharmony_ci executable = self.executable 1017db96d56Sopenharmony_ci else: 1027db96d56Sopenharmony_ci executable = os.path.join( 1037db96d56Sopenharmony_ci sysconfig.get_config_var("BINDIR"), 1047db96d56Sopenharmony_ci "python%s%s" % (sysconfig.get_config_var("VERSION"), 1057db96d56Sopenharmony_ci sysconfig.get_config_var("EXE"))) 1067db96d56Sopenharmony_ci executable = os.fsencode(executable) 1077db96d56Sopenharmony_ci shebang = b"#!" + executable + post_interp + b"\n" 1087db96d56Sopenharmony_ci # Python parser starts to read a script using UTF-8 until 1097db96d56Sopenharmony_ci # it gets a #coding:xxx cookie. The shebang has to be the 1107db96d56Sopenharmony_ci # first line of a file, the #coding:xxx cookie cannot be 1117db96d56Sopenharmony_ci # written before. So the shebang has to be decodable from 1127db96d56Sopenharmony_ci # UTF-8. 1137db96d56Sopenharmony_ci try: 1147db96d56Sopenharmony_ci shebang.decode('utf-8') 1157db96d56Sopenharmony_ci except UnicodeDecodeError: 1167db96d56Sopenharmony_ci raise ValueError( 1177db96d56Sopenharmony_ci "The shebang ({!r}) is not decodable " 1187db96d56Sopenharmony_ci "from utf-8".format(shebang)) 1197db96d56Sopenharmony_ci # If the script is encoded to a custom encoding (use a 1207db96d56Sopenharmony_ci # #coding:xxx cookie), the shebang has to be decodable from 1217db96d56Sopenharmony_ci # the script encoding too. 1227db96d56Sopenharmony_ci try: 1237db96d56Sopenharmony_ci shebang.decode(encoding) 1247db96d56Sopenharmony_ci except UnicodeDecodeError: 1257db96d56Sopenharmony_ci raise ValueError( 1267db96d56Sopenharmony_ci "The shebang ({!r}) is not decodable " 1277db96d56Sopenharmony_ci "from the script encoding ({})" 1287db96d56Sopenharmony_ci .format(shebang, encoding)) 1297db96d56Sopenharmony_ci with open(outfile, "wb") as outf: 1307db96d56Sopenharmony_ci outf.write(shebang) 1317db96d56Sopenharmony_ci outf.writelines(f.readlines()) 1327db96d56Sopenharmony_ci if f: 1337db96d56Sopenharmony_ci f.close() 1347db96d56Sopenharmony_ci else: 1357db96d56Sopenharmony_ci if f: 1367db96d56Sopenharmony_ci f.close() 1377db96d56Sopenharmony_ci updated_files.append(outfile) 1387db96d56Sopenharmony_ci self.copy_file(script, outfile) 1397db96d56Sopenharmony_ci 1407db96d56Sopenharmony_ci if os.name == 'posix': 1417db96d56Sopenharmony_ci for file in outfiles: 1427db96d56Sopenharmony_ci if self.dry_run: 1437db96d56Sopenharmony_ci log.info("changing mode of %s", file) 1447db96d56Sopenharmony_ci else: 1457db96d56Sopenharmony_ci oldmode = os.stat(file)[ST_MODE] & 0o7777 1467db96d56Sopenharmony_ci newmode = (oldmode | 0o555) & 0o7777 1477db96d56Sopenharmony_ci if newmode != oldmode: 1487db96d56Sopenharmony_ci log.info("changing mode of %s from %o to %o", 1497db96d56Sopenharmony_ci file, oldmode, newmode) 1507db96d56Sopenharmony_ci os.chmod(file, newmode) 1517db96d56Sopenharmony_ci # XXX should we modify self.outfiles? 1527db96d56Sopenharmony_ci return outfiles, updated_files 1537db96d56Sopenharmony_ci 1547db96d56Sopenharmony_ciclass build_scripts_2to3(build_scripts, Mixin2to3): 1557db96d56Sopenharmony_ci 1567db96d56Sopenharmony_ci def copy_scripts(self): 1577db96d56Sopenharmony_ci outfiles, updated_files = build_scripts.copy_scripts(self) 1587db96d56Sopenharmony_ci if not self.dry_run: 1597db96d56Sopenharmony_ci self.run_2to3(updated_files) 1607db96d56Sopenharmony_ci return outfiles, updated_files 161