Skip to content
This repository was archived by the owner on Apr 22, 2023. It is now read-only.

Commit 5875492

Browse files
committed
build: i18n: Autodownload ICU, Intl small-icu by default!
This is to support joyent#7676, Intl by default. * make `--with-intl=small-icu` the default * Download, verify (md5), unpack ICU's zip if not there * update docs There's a "list" of URLs being used, but right now only the first is picked up. The logic works something like this: * if there is no directory `deps/icu`, * if no zip file (currently `icu4c-54_1-src.zip`), * download zip file (icu-project.org -> sf.net) * verify the MD5 sum of the zipfile * if bad, print error and exit * unpack the zipfile into `deps/icu` * if `deps/icu` now exists, use it, else fail with help text Also: * refactor some code into tools/configure.d/nodedownload.py * add `intl-none` option for `vcbuild.bat` To rebuild `deps/icu-small` - ``` bash tools/icu/prepare-icu-source.sh ``` Also: Reduce space by about 1MB with ICU 54 (over without this patch). Also trims a few other source files, but only conditional on the exact ICU version used. This is to future-proof - a file that is unneeded now may be needed in future ICUs.
1 parent 6a67823 commit 5875492

File tree

9 files changed

+334
-20
lines changed

9 files changed

+334
-20
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ ipch/
4646
email.md
4747
deps/v8-*
4848
deps/icu
49+
deps/icu*.zip
50+
deps/icu*.tgz
4951
./node_modules
5052
.svn/
5153

README.md

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,20 +83,73 @@ make doc
8383
man doc/node.1
8484
```
8585

86-
### To build `Intl` (ECMA-402) support:
86+
### `Intl` (ECMA-402) support:
8787

88-
*Note:* more docs, including how to reduce disk footprint, are on
88+
#### "small" (English only) support
89+
90+
The default is to build with "small" (English only) support, so the
91+
`small-icu` parameter isn't strictly necessary. It's included here
92+
for documentation.
93+
94+
Unix/Macintosh:
95+
96+
```sh
97+
./configure --with-intl=small-icu
98+
```
99+
100+
Windows:
101+
102+
```sh
103+
vcbuild small-icu
104+
```
105+
106+
The `small-icu` mode builds
107+
with English-only data. You can add full data at runtime.
108+
109+
*Note:* more docs are on
89110
[the wiki](https://github.com/joyent/node/wiki/Intl).
90111

112+
#### Build with full ICU support (all locales supported by ICU):
113+
114+
*Note*, this may download ICU if you don't have an ICU in `deps/icu`
115+
116+
Unix/Macintosh:
117+
118+
```sh
119+
./configure --with-intl=full-icu
120+
```
121+
122+
Windows:
123+
124+
```sh
125+
vcbuild full-icu
126+
```
127+
128+
#### Build with no Intl support `:-(`
129+
130+
The `Intl` object will not be available.
131+
132+
Unix/Macintosh:
133+
134+
```sh
135+
./configure --with-intl=none
136+
```
137+
138+
Windows:
139+
140+
```sh
141+
vcbuild intl-none
142+
```
143+
91144
#### Use existing installed ICU (Unix/Macintosh only):
92145

93146
```sh
94147
pkg-config --modversion icu-i18n && ./configure --with-intl=system-icu
95148
```
96149

97-
#### Build ICU from source:
150+
#### Build with a specific ICU:
98151

99-
First: Unpack latest ICU
152+
First: Unpack latest ICU to `deps/icu`
100153
[icu4c-**##.#**-src.tgz](http://icu-project.org/download) (or `.zip`)
101154
as `deps/icu` (You'll have: `deps/icu/source/...`)
102155

configure

Lines changed: 96 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,21 @@ import re
66
import shlex
77
import subprocess
88
import sys
9+
import urllib
10+
import zipfile
11+
import hashlib
12+
import shutil
913

1014
CC = os.environ.get('CC', 'cc')
1115

1216
root_dir = os.path.dirname(__file__)
1317
sys.path.insert(0, os.path.join(root_dir, 'tools', 'gyp', 'pylib'))
1418
from gyp.common import GetFlavor
1519

20+
# imports in tools/configure.d
21+
sys.path.insert(0, os.path.join(root_dir, 'tools', 'configure.d'))
22+
import nodedownload
23+
1624
# parse our options
1725
parser = optparse.OptionParser()
1826

@@ -244,7 +252,7 @@ parser.add_option('--with-icu-path',
244252
parser.add_option('--with-intl',
245253
action='store',
246254
dest='with_intl',
247-
help='Intl mode: none, full-icu, small-icu (default is none)')
255+
help='Intl mode: none, full-icu, small-icu (default is small-icu)')
248256

249257
parser.add_option('--with-perfctr',
250258
action='store_true',
@@ -712,6 +720,58 @@ def glob_to_var(dir_base, dir_sub):
712720
return list
713721

714722
def configure_intl(o):
723+
icus = [
724+
{
725+
'url': 'http://download.icu-project.org/files/icu4c/54.1/icu4c-54_1-src.zip',
726+
# from https://ssl.icu-project.org/files/icu4c/54.1/icu4c-src-54_1.md5:
727+
'md5': '6b89d60e2f0e140898ae4d7f72323bca',
728+
},
729+
]
730+
class ConfigOpener(urllib.FancyURLopener):
731+
# append to existing version (UA)
732+
version = '%s (node.js/configure)' % urllib.URLopener.version
733+
def icu_download(path):
734+
# download ICU, if needed
735+
def fmtMb(amt):
736+
return "{:.1f}".format(amt / 1024000.)
737+
def reporthook(count, size, total):
738+
sys.stdout.write(' ICU: %c %sMB total, %sMB downloaded \r' %
739+
(nodedownload.spin(count),
740+
nodedownload.formatSize(total),
741+
nodedownload.formatSize(count*size)))
742+
for icu in icus:
743+
url = icu['url']
744+
md5 = icu['md5']
745+
local = url.split('/')[-1]
746+
targetfile = os.path.join(root_dir, 'deps', local)
747+
if not os.path.isfile(targetfile):
748+
try:
749+
sys.stdout.write(' <%s>\nConnecting...\r' % url)
750+
sys.stdout.flush()
751+
msg = urllib.urlretrieve(url, targetfile, reporthook=reporthook)
752+
print '' # clear the line
753+
except:
754+
print ' ** Error occurred while downloading\n <%s>' % url
755+
raise
756+
else:
757+
print ' Re-using existing %s' % targetfile
758+
if os.path.isfile(targetfile):
759+
digest = hashlib.md5()
760+
count = 0
761+
sys.stdout.write(' Checking file integrity with MD5:\r')
762+
with open(targetfile, 'rb') as f:
763+
chunk = f.read(1024)
764+
while chunk != "":
765+
digest.update(chunk)
766+
chunk = f.read(1024)
767+
gotmd5 = digest.hexdigest()
768+
print ' MD5: %s %s' % (gotmd5, targetfile)
769+
if (md5 == gotmd5):
770+
return targetfile
771+
else:
772+
print ' Expected: %s *MISMATCH*' % md5
773+
print '\n ** Corrupted ZIP? Delete %s to retry download.\n' % targetfile
774+
return None
715775
icu_config = {
716776
'variables': {}
717777
}
@@ -723,7 +783,6 @@ def configure_intl(o):
723783
write(icu_config_name, do_not_edit +
724784
pprint.pformat(icu_config, indent=2) + '\n')
725785

726-
# small ICU is off by default.
727786
# always set icu_small, node.gyp depends on it being defined.
728787
o['variables']['icu_small'] = b(False)
729788

@@ -739,6 +798,8 @@ def configure_intl(o):
739798
o['variables']['icu_gyp_path'] = options.with_icu_path
740799
return
741800
# --with-intl=<with_intl>
801+
if with_intl is None:
802+
with_intl = 'small-icu' # The default mode of Intl
742803
if with_intl == 'none' or with_intl is None:
743804
o['variables']['v8_enable_i18n_support'] = 0
744805
return # no Intl
@@ -769,20 +830,47 @@ def configure_intl(o):
769830
# Note: non-ICU implementations could use other 'with_intl'
770831
# values.
771832

833+
icu_parent_path = os.path.join(root_dir, 'deps')
834+
icu_full_path = os.path.join(icu_parent_path, 'icu')
835+
icu_small_path = os.path.join(icu_parent_path, 'icu-small')
836+
icu_small_tag = os.path.join(icu_full_path, 'is-small-icu.txt')
837+
838+
## Use (or not) an embedded small-icu.
839+
if with_intl == 'small-icu':
840+
if not os.path.isdir(icu_full_path) and os.path.isdir(icu_small_path):
841+
# deps/small-icu -> deps/icu
842+
print 'Copying small ICU %s to %s' % (icu_small_path, icu_full_path)
843+
shutil.copytree(icu_small_path, icu_full_path)
844+
#else:
845+
# print 'Not copying %s to %s' % (icu_small_path, icu_full_path)
846+
elif os.path.isfile(icu_small_tag):
847+
print 'deleting small-icu %s for --with-intl=%s' % (icu_full_path, with_intl)
848+
shutil.rmtree(icu_full_path)
849+
772850
# ICU mode. (icu-generic.gyp)
773851
byteorder = sys.byteorder
774852
o['variables']['icu_gyp_path'] = 'tools/icu/icu-generic.gyp'
775853
# ICU source dir relative to root
776-
icu_full_path = os.path.join(root_dir, 'deps/icu')
777854
o['variables']['icu_path'] = icu_full_path
778855
if not os.path.isdir(icu_full_path):
779-
print 'Error: ICU path is not a directory: %s' % (icu_full_path)
856+
print '* ECMA-402 (Intl) support didn\'t find ICU in %s..' % (icu_full_path)
857+
# can we download (or find) a zipfile?
858+
localzip = icu_download(icu_full_path)
859+
if localzip:
860+
with zipfile.ZipFile(localzip, 'r') as icuzip:
861+
print ' Extracting ICU source zip: %s' % localzip
862+
icuzip.extractall(icu_parent_path)
863+
if not os.path.isdir(icu_full_path):
864+
print ' Cannot build Intl without ICU in %s.' % (icu_full_path)
865+
print ' (Fix, or disable with "--with-intl=none" )'
780866
sys.exit(1)
867+
else:
868+
print '* Using ICU in %s' % (icu_full_path)
781869
# Now, what version of ICU is it? We just need the "major", such as 54.
782870
# uvernum.h contains it as a #define.
783871
uvernum_h = os.path.join(icu_full_path, 'source/common/unicode/uvernum.h')
784872
if not os.path.isfile(uvernum_h):
785-
print 'Error: could not load %s - is ICU installed?' % uvernum_h
873+
print ' Error: could not load %s - is ICU installed?' % uvernum_h
786874
sys.exit(1)
787875
icu_ver_major = None
788876
matchVerExp = r'^\s*#define\s+U_ICU_VERSION_SHORT\s+"([^"]*)".*'
@@ -792,7 +880,7 @@ def configure_intl(o):
792880
if m:
793881
icu_ver_major = m.group(1)
794882
if not icu_ver_major:
795-
print 'Could not read U_ICU_VERSION_SHORT version from %s' % uvernum_h
883+
print ' Could not read U_ICU_VERSION_SHORT version from %s' % uvernum_h
796884
sys.exit(1)
797885
icu_endianness = sys.byteorder[0]; # TODO(srl295): EBCDIC should be 'e'
798886
o['variables']['icu_ver_major'] = icu_ver_major
@@ -819,8 +907,8 @@ def configure_intl(o):
819907
# this is the icudt*.dat file which node will be using (platform endianness)
820908
o['variables']['icu_data_file'] = icu_data_file
821909
if not os.path.isfile(icu_data_path):
822-
print 'Error: ICU prebuilt data file %s does not exist.' % icu_data_path
823-
print 'See the README.md.'
910+
print ' Error: ICU prebuilt data file %s does not exist.' % icu_data_path
911+
print ' See the README.md.'
824912
# .. and we're not about to build it from .gyp!
825913
sys.exit(1)
826914
# map from variable name to subdirs

tools/configure.d/nodedownload.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Moved some utilities here from ../../configure
2+
3+
def formatSize(amt):
4+
"""Format a size as a string"""
5+
return "{:.1f}".format(amt / 1024000.)
6+
7+
def spin(c):
8+
"""print out a spinner based on 'c'"""
9+
# spin = "\\|/-"
10+
spin = ".:|'"
11+
return (spin[c % len(spin)])

0 commit comments

Comments
 (0)