Skip to content

Commit c42790b

Browse files
encukoumaurycy
andauthored
gh-155292: Skip updating unicodedata with mismatched interpreter (GH-157066)
mkstringprep uses things like str.lower(), so it generates the wrong result if run in an interpreter with a different Unicode data version than the target. This means that updating the Unicode version is a two-step process: run makeunicodedata.py, then compile, then run mkstringprep.py. The two steps can (and should) be combined when re-running regen-unicodedata to verify that the data is up to date. The GH-155292 fix only considered that case. Change makeunicodedata.py to only run mkstringprep.py when the current interpreter is up to it. Otherwise, show a reminder. As an extra complication, download the input (RFC 3454) in the "first step", since an out-of-date stringprep.py's freshness assertion may prevent downloads. Co-authored-by: Maurycy Pawłowski-Wieroński <maurycy@maurycy.com>
1 parent 4de00a4 commit c42790b

3 files changed

Lines changed: 36 additions & 18 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
In ``makeunicodedata.py``, the script for updating Unicode data, skip
2+
generating ``stringprep.py`` if the current interpreter's Unicode data
3+
version does not match the target version.

Tools/unicode/makeunicodedata.py

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
SCRIPT = os.path.normpath(sys.argv[0])
4141
VERSION = "3.3"
4242

43+
# Local cache location
44+
DATA_DIR = os.path.join('Tools', 'unicode', 'data')
45+
4346
# The Unicode Database
4447
# --------------------
4548
# When changing UCD version please update
@@ -816,10 +819,31 @@ def makeunicodename(unicode, trace):
816819
def makestringprep():
817820
FILE = "Lib/stringprep.py"
818821

822+
RFC_LOCAL = os.path.join(DATA_DIR, "rfc3454.txt")
823+
RFC_URL = "https://www.rfc-editor.org/rfc/rfc3454.txt"
824+
819825
print("--- Preparing", FILE, "...")
820826

827+
# mkstringprep expects a local copy of RFC 3454. Download it now.
828+
# (stringprep is used for URL handling, and if it's not matched
829+
# with the compiled unicodedata, downloads would fail.)
830+
if not os.path.exists(RFC_LOCAL):
831+
download_data(RFC_LOCAL, RFC_URL)
832+
821833
MKSTRINGPREP = "Tools/unicode/mkstringprep.py"
822834

835+
# mkstringprep needs to be run with a Python version that has "its"
836+
# unicode data, since it uses str.lower() and similar.
837+
import unicodedata
838+
if unicodedata.unidata_version != UNIDATA_VERSION:
839+
print()
840+
print("!! Skipping mkstringprep -- mismatched Unicode version !!")
841+
print()
842+
print("Please compile CPython with the updated Unicode database,")
843+
print("then use that interpreter to run:")
844+
print(f" python {MKSTRINGPREP} > {FILE}")
845+
return
846+
823847
with open(FILE, "w") as f:
824848
f.truncate()
825849
subprocess.check_call([sys.executable, MKSTRINGPREP], stdout=f)
@@ -929,26 +953,29 @@ class Difference(Exception):pass
929953
normalization_changes))
930954

931955

932-
DATA_DIR = os.path.join('Tools', 'unicode', 'data')
933-
934956
def open_data(template, version):
935957
local = os.path.join(DATA_DIR, template % ('-'+version,))
936958
if not os.path.exists(local):
937-
import urllib.request
938959
if version == '3.2.0':
939960
# irregular url structure
940961
url = ('https://www.unicode.org/Public/3.2-Update/'+template) % ('-'+version,)
941962
else:
942963
url = ('https://www.unicode.org/Public/%s/ucd/'+template) % (version, '')
943-
os.makedirs(os.path.dirname(local), exist_ok=True)
944-
urllib.request.urlretrieve(url, filename=local)
964+
download_data(local, url)
945965
if local.endswith('.txt'):
946966
return open(local, encoding='utf-8')
947967
else:
948968
# Unihan.zip
949969
return open(local, 'rb')
950970

951971

972+
def download_data(local, url):
973+
import urllib.request
974+
os.makedirs(os.path.dirname(local), exist_ok=True)
975+
print(f'Downloading {url} to {local}')
976+
urllib.request.urlretrieve(url, filename=local)
977+
978+
952979
def expand_range(char_range: str) -> Iterator[int]:
953980
'''
954981
Parses ranges of code points, as described in UAX #44:

Tools/unicode/mkstringprep.py

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import re
2-
import os
32
import unicodedata as unicodedata_current
43
from unicodedata import ucd_3_2_0 as unicodedata_320
54

6-
FILENAME = "Tools/unicode/data/rfc3454.txt"
7-
URL = "https://www.rfc-editor.org/rfc/rfc3454.txt"
8-
95
def gen_category(cats):
106
for i in range(0, 0x110000):
117
if unicodedata_320.category(chr(i)) in cats:
@@ -52,15 +48,7 @@ def compact_set(l):
5248

5349
############## Read the tables in the RFC #######################
5450

55-
try:
56-
data_file = open(FILENAME, encoding='utf-8')
57-
except FileNotFoundError:
58-
import urllib.request
59-
os.makedirs(os.path.dirname(FILENAME), exist_ok=True)
60-
urllib.request.urlretrieve(URL, filename=FILENAME)
61-
data_file = open(FILENAME, encoding='utf-8')
62-
63-
with data_file:
51+
with open("Tools/unicode/data/rfc3454.txt", encoding='utf-8') as data_file:
6452
data = data_file.readlines()
6553

6654
tables = []

0 commit comments

Comments
 (0)