build: i18n: add icu config options
authorSteven R. Loomis <srl@icu-project.org>
Thu, 13 Nov 2014 01:13:14 +0000 (17:13 -0800)
committerTrevor Norris <trev.norris@gmail.com>
Sat, 3 Jan 2015 00:51:53 +0000 (16:51 -0800)
Make "--with-intl=none" the default and add "intl-none" option to
vcbuild.bat.

If icu data is missing print a warning unless either --download=all or
--download=icu is set. If set then automatically download, verify (MD5)
and unpack the ICU data if not already available.

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

Add the configuration option "--with-icu-source=..."

Usage:
  * --with-icu-source=/path/to/my/other/icu
  * --with-icu-source=/path/to/icu54.zip
  * --with-icu-source=/path/to/icu54.tgz
  * --with-icu-source=http://example.com/icu54.tar.bz2

Add the configuration option "--with-icu-locals=...".  Allows choosing
which locales are used in the "small-icu" case.

Example:
    configure --with-intl=small-icu --with-icu-locales=tlh,grc,nl

(Also note that as of this writing, neither Klingon nor Ancient Greek
are in upstream CLDR data. Serving suggestion only.)

Don't use hard coded ../../out paths on windows. This was suggested by
@misterdjules as it causes test failures.  With this fix, "out" is no
longer created on windows and the following can run properly:

    python tools/test.py simple

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.

Also:
  * Update distclean to remove icu related files
  * Refactor some code into tools/configure.d/nodedownload.py
  * Update docs
  * Add test

PR-URL: https://github.com/joyent/node/pull/8719
Fixes: https://github.com/joyent/node/issues/7676#issuecomment-64704230
[trev.norris@gmail.com small change to test's whitespace and logic]
Signed-off-by: Trevor Norris <trev.norris@gmail.com>
.gitignore
Makefile
README.md
configure
test/simple/test-intl.js [new file with mode: 0644]
tools/configure.d/nodedownload.py [new file with mode: 0644]
tools/icu/icu-generic.gyp
tools/icu/icu_small.json
tools/icu/icutrim.py
vcbuild.bat

index 6581dee..9dda73b 100644 (file)
@@ -46,6 +46,9 @@ ipch/
 email.md
 deps/v8-*
 deps/icu
+deps/icu*.zip
+deps/icu*.tgz
+deps/icu-tmp
 ./node_modules
 .svn/
 
index f84c213..e463280 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -78,10 +78,12 @@ clean:
 
 distclean:
        -rm -rf out
-       -rm -f config.gypi
+       -rm -f config.gypi icu_config.gypi
        -rm -f config.mk
        -rm -rf node node_g blog.html email.md
        -rm -rf node_modules
+       -rm -rf deps/icu
+       -rm -rf deps/icu4c*.tgz deps/icu4c*.zip deps/icu-tmp
 
 test: all
        $(PYTHON) tools/test.py --mode=release simple message
index 0032c63..acaf24b 100644 (file)
--- a/README.md
+++ b/README.md
@@ -83,32 +83,105 @@ make doc
 man doc/node.1
 ```
 
-### To build `Intl` (ECMA-402) support:
+### `Intl` (ECMA-402) support:
 
-*Note:* more docs, including how to reduce disk footprint, are on
+[Intl](https://github.com/joyent/node/wiki/Intl) support is not
+enabled by default.
+
+#### "small" (English only) support
+
+This option will build with "small" (English only) support, but
+the full `Intl` (ECMA-402) APIs.  With `--download=all` it will
+download the ICU library as needed.
+
+Unix/Macintosh:
+
+```sh
+./configure --with-intl=small-icu --download=all
+```
+
+Windows:
+
+```sh
+vcbuild small-icu download-all
+```
+
+The `small-icu` mode builds
+with English-only data. You can add full data at runtime.
+
+*Note:* more docs are on
 [the wiki](https://github.com/joyent/node/wiki/Intl).
 
-#### Use existing installed ICU (Unix/Macintosh only):
+#### Build with full ICU support (all locales supported by ICU):
+
+With the `--download=all`, this may download ICU if you don't
+have an ICU in `deps/icu`.
+
+Unix/Macintosh:
 
 ```sh
-pkg-config --modversion icu-i18n && ./configure --with-intl=system-icu
+./configure --with-intl=full-icu --download=all
 ```
 
-#### Build ICU from source:
+Windows:
 
-First: Unpack latest ICU
-  [icu4c-**##.#**-src.tgz](http://icu-project.org/download) (or `.zip`)
-  as `deps/icu` (You'll have: `deps/icu/source/...`)
+```sh
+vcbuild full-icu download-all
+```
+
+#### Build with no Intl support `:-(`
+
+The `Intl` object will not be available.
+This is the default at present, so this option is not normally needed.
 
 Unix/Macintosh:
 
 ```sh
-./configure --with-intl=full-icu
+./configure --with-intl=none
 ```
 
 Windows:
 
 ```sh
+vcbuild intl-none
+```
+
+#### Use existing installed ICU (Unix/Macintosh only):
+
+```sh
+pkg-config --modversion icu-i18n && ./configure --with-intl=system-icu
+```
+
+#### Build with a specific ICU:
+
+You can find other ICU releases at
+[the ICU homepage](http://icu-project.org/download).
+Download the file named something like `icu4c-**##.#**-src.tgz` (or
+`.zip`).
+
+Unix/Macintosh: from an already-unpacked ICU
+
+```sh
+./configure --with-intl=[small-icu,full-icu] --with-icu-source=/path/to/icu
+```
+
+Unix/Macintosh: from a local ICU tarball
+
+```sh
+./configure --with-intl=[small-icu,full-icu] --with-icu-source=/path/to/icu.tgz
+```
+
+Unix/Macintosh: from a tarball URL
+
+```sh
+./configure --with-intl=full-icu --with-icu-source=http://url/to/icu.tgz
+```
+
+Windows: first unpack latest ICU to `deps/icu`
+  [icu4c-**##.#**-src.tgz](http://icu-project.org/download) (or `.zip`)
+  as `deps/icu` (You'll have: `deps/icu/source/...`)
+
+```sh
 vcbuild full-icu
 ```
 
index c558f7f..51475f0 100755 (executable)
--- a/configure
+++ b/configure
@@ -6,6 +6,8 @@ import re
 import shlex
 import subprocess
 import sys
+import shutil
+import string
 
 CC = os.environ.get('CC', 'cc')
 
@@ -13,6 +15,10 @@ root_dir = os.path.dirname(__file__)
 sys.path.insert(0, os.path.join(root_dir, 'tools', 'gyp', 'pylib'))
 from gyp.common import GetFlavor
 
+# imports in tools/configure.d
+sys.path.insert(0, os.path.join(root_dir, 'tools', 'configure.d'))
+import nodedownload
+
 # parse our options
 parser = optparse.OptionParser()
 
@@ -236,16 +242,31 @@ parser.add_option('--with-etw',
     dest='with_etw',
     help='build with ETW (default is true on Windows)')
 
+parser.add_option('--download',
+    action='store',
+    dest='download_list',
+    help=nodedownload.help())
+
 parser.add_option('--with-icu-path',
     action='store',
     dest='with_icu_path',
     help='Path to icu.gyp (ICU i18n, Chromium version only.)')
 
+parser.add_option('--with-icu-locales',
+    action='store',
+    dest='with_icu_locales',
+    help='Comma-separated list of locales for "small-icu". Default: "root,en". "root" is assumed.')
+
 parser.add_option('--with-intl',
     action='store',
     dest='with_intl',
     help='Intl mode: none, full-icu, small-icu (default is none)')
 
+parser.add_option('--with-icu-source',
+    action='store',
+    dest='with_icu_source',
+    help='Intl mode: optional local path to icu/ dir, or path/URL of icu source archive.')
+
 parser.add_option('--with-perfctr',
     action='store_true',
     dest='with_perfctr',
@@ -294,6 +315,8 @@ parser.add_option('--xcode',
 
 (options, args) = parser.parse_args()
 
+# set up auto-download list
+auto_downloads = nodedownload.parse(options.download_list)
 
 def b(value):
   """Returns the string 'true' if value is truthy, 'false' otherwise."""
@@ -712,6 +735,35 @@ def glob_to_var(dir_base, dir_sub):
   return list
 
 def configure_intl(o):
+  icus = [
+    {
+      'url': 'http://download.icu-project.org/files/icu4c/54.1/icu4c-54_1-src.zip',
+      # from https://ssl.icu-project.org/files/icu4c/54.1/icu4c-src-54_1.md5:
+      'md5': '6b89d60e2f0e140898ae4d7f72323bca',
+    },
+  ]
+  def icu_download(path):
+    # download ICU, if needed
+    for icu in icus:
+      url = icu['url']
+      md5 = icu['md5']
+      local = url.split('/')[-1]
+      targetfile = os.path.join(root_dir, 'deps', local)
+      if not os.path.isfile(targetfile):
+        if nodedownload.candownload(auto_downloads, "icu"):
+          nodedownload.retrievefile(url, targetfile)
+      else:
+        print ' Re-using existing %s' % targetfile
+      if os.path.isfile(targetfile):
+        sys.stdout.write(' Checking file integrity with MD5:\r')
+        gotmd5 = nodedownload.md5sum(targetfile)
+        print ' MD5:      %s  %s' % (gotmd5, targetfile)
+        if (md5 == gotmd5):
+          return targetfile
+        else:
+          print ' Expected: %s      *MISMATCH*' % md5
+          print '\n ** Corrupted ZIP? Delete %s to retry download.\n' % targetfile
+    return None
   icu_config = {
     'variables': {}
   }
@@ -723,11 +775,11 @@ def configure_intl(o):
   write(icu_config_name, do_not_edit +
         pprint.pformat(icu_config, indent=2) + '\n')
 
-  # small ICU is off by default.
   # always set icu_small, node.gyp depends on it being defined.
   o['variables']['icu_small'] = b(False)
 
   with_intl = options.with_intl
+  with_icu_source = options.with_icu_source
   have_icu_path = bool(options.with_icu_path)
   if have_icu_path and with_intl:
     print 'Error: Cannot specify both --with-icu-path and --with-intl'
@@ -739,6 +791,13 @@ def configure_intl(o):
     o['variables']['icu_gyp_path'] = options.with_icu_path
     return
   # --with-intl=<with_intl>
+  # set the default
+  if with_intl is None:
+    with_intl = 'none'  # The default mode of Intl
+  # sanity check localelist
+  if options.with_icu_locales and (with_intl != 'small-icu'):
+    print 'Error: --with-icu-locales only makes sense with --with-intl=small-icu'
+    sys.exit(1)
   if with_intl == 'none' or with_intl is None:
     o['variables']['v8_enable_i18n_support'] = 0
     return  # no Intl
@@ -746,6 +805,12 @@ def configure_intl(o):
     # small ICU (English only)
     o['variables']['v8_enable_i18n_support'] = 1
     o['variables']['icu_small'] = b(True)
+    with_icu_locales = options.with_icu_locales
+    if not with_icu_locales:
+      with_icu_locales = 'root,en'
+    locs = set(with_icu_locales.split(','))
+    locs.add('root')  # must have root
+    o['variables']['icu_locales'] = string.join(locs,',')
   elif with_intl == 'full-icu':
     # full ICU
     o['variables']['v8_enable_i18n_support'] = 1
@@ -769,20 +834,78 @@ def configure_intl(o):
   # Note: non-ICU implementations could use other 'with_intl'
   # values.
 
+  # this is just the 'deps' dir. Used for unpacking.
+  icu_parent_path = os.path.join(root_dir, 'deps')
+
+  # The full path to the ICU source directory.
+  icu_full_path = os.path.join(icu_parent_path, 'icu')
+
+  # icu-tmp is used to download and unpack the ICU tarball.
+  icu_tmp_path = os.path.join(icu_parent_path, 'icu-tmp')
+
+  # --with-icu-source processing
+  # first, check that they didn't pass --with-icu-source=deps/icu
+  if with_icu_source and os.path.abspath(icu_full_path) == os.path.abspath(with_icu_source):
+    print 'Ignoring redundant --with-icu-source=%s' % (with_icu_source)
+    with_icu_source = None
+  # if with_icu_source is still set, try to use it.
+  if with_icu_source:
+    if os.path.isdir(icu_full_path):
+      print 'Deleting old ICU source: %s' % (icu_full_path)
+      shutil.rmtree(icu_full_path)
+    # now, what path was given?
+    if os.path.isdir(with_icu_source):
+      # it's a path. Copy it.
+      print '%s -> %s' % (with_icu_source, icu_full_path)
+      shutil.copytree(with_icu_source, icu_full_path)
+    else:
+      # could be file or URL.
+      # Set up temporary area
+      if os.path.isdir(icu_tmp_path):
+        shutil.rmtree(icu_tmp_path)
+      os.mkdir(icu_tmp_path)
+      icu_tarball = None
+      if os.path.isfile(with_icu_source):
+        # it's a file. Try to unpack it.
+        icu_tarball = with_icu_source
+      else:
+        # Can we download it?
+        local = os.path.join(icu_tmp_path, with_icu_source.split('/')[-1])  # local part
+        icu_tarball = nodedownload.retrievefile(with_icu_source, local)
+      # continue with "icu_tarball"
+      nodedownload.unpack(icu_tarball, icu_tmp_path)
+      # Did it unpack correctly? Should contain 'icu'
+      tmp_icu = os.path.join(icu_tmp_path, 'icu')
+      if os.path.isdir(tmp_icu):
+        os.rename(tmp_icu, icu_full_path)
+        shutil.rmtree(icu_tmp_path)
+      else:
+        print ' Error: --with-icu-source=%s did not result in an "icu" dir.' % with_icu_source
+        shutil.rmtree(icu_tmp_path)
+        sys.exit(1)
+
   # ICU mode. (icu-generic.gyp)
   byteorder = sys.byteorder
   o['variables']['icu_gyp_path'] = 'tools/icu/icu-generic.gyp'
   # ICU source dir relative to root
-  icu_full_path = os.path.join(root_dir, 'deps/icu')
   o['variables']['icu_path'] = icu_full_path
   if not os.path.isdir(icu_full_path):
-    print 'Error: ICU path is not a directory: %s' % (icu_full_path)
+    print '* ECMA-402 (Intl) support didn\'t find ICU in %s..' % (icu_full_path)
+    # can we download (or find) a zipfile?
+    localzip = icu_download(icu_full_path)
+    if localzip:
+      nodedownload.unpack(localzip, icu_parent_path)
+  if not os.path.isdir(icu_full_path):
+    print ' Cannot build Intl without ICU in %s.' % (icu_full_path)
+    print ' (Fix, or disable with "--with-intl=none" )'
     sys.exit(1)
+  else:
+    print '* Using ICU in %s' % (icu_full_path)
   # Now, what version of ICU is it? We just need the "major", such as 54.
   # uvernum.h contains it as a #define.
   uvernum_h = os.path.join(icu_full_path, 'source/common/unicode/uvernum.h')
   if not os.path.isfile(uvernum_h):
-    print 'Error: could not load %s - is ICU installed?' % uvernum_h
+    print ' Error: could not load %s - is ICU installed?' % uvernum_h
     sys.exit(1)
   icu_ver_major = None
   matchVerExp = r'^\s*#define\s+U_ICU_VERSION_SHORT\s+"([^"]*)".*'
@@ -792,7 +915,7 @@ def configure_intl(o):
     if m:
       icu_ver_major = m.group(1)
   if not icu_ver_major:
-    print 'Could not read U_ICU_VERSION_SHORT version from %s' % uvernum_h
+    print ' Could not read U_ICU_VERSION_SHORT version from %s' % uvernum_h
     sys.exit(1)
   icu_endianness = sys.byteorder[0];  # TODO(srl295): EBCDIC should be 'e'
   o['variables']['icu_ver_major'] = icu_ver_major
@@ -819,8 +942,8 @@ def configure_intl(o):
   # this is the icudt*.dat file which node will be using (platform endianness)
   o['variables']['icu_data_file'] = icu_data_file
   if not os.path.isfile(icu_data_path):
-    print 'Error: ICU prebuilt data file %s does not exist.' % icu_data_path
-    print 'See the README.md.'
+    print ' Error: ICU prebuilt data file %s does not exist.' % icu_data_path
+    print ' See the README.md.'
     # .. and we're not about to build it from .gyp!
     sys.exit(1)
   # map from variable name to subdirs
diff --git a/test/simple/test-intl.js b/test/simple/test-intl.js
new file mode 100644 (file)
index 0000000..841239a
--- /dev/null
@@ -0,0 +1,103 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+var common = require('../common');
+var assert = require('assert');
+
+// does node think that i18n was enabled?
+var enablei18n = process.config.variables.v8_enable_i18n_support;
+if (enablei18n === undefined) {
+    enablei18n = false;
+}
+
+// is the Intl object present?
+var haveIntl = (global.Intl != undefined);
+
+// Returns true if no specific locale ids were configured (i.e. "all")
+// Else, returns true if loc is in the configured list
+// Else, returns false
+function haveLocale(loc) {
+  var locs = process.config.variables.icu_locales.split(',');
+  return locs.indexOf(loc) !== -1;
+}
+
+if (!haveIntl) {
+  var erMsg =
+      '"Intl" object is NOT present but v8_enable_i18n_support is ' +
+      enablei18n;
+  assert.equal(enablei18n, false, erMsg);
+  console.log('Skipping Intl tests because Intl object not present.');
+
+} else {
+  var erMsg =
+    '"Intl" object is present but v8_enable_i18n_support is ' +
+    enablei18n +
+    '. Is this test out of date?';
+  assert.equal(enablei18n, true, erMsg);
+
+  // Construct a new date at the beginning of Unix time
+  var date0 = new Date(0);
+
+  // Use the GMT time zone
+  var GMT = 'Etc/GMT';
+
+  // Construct an English formatter. Should format to "Jan 70"
+  var dtf =
+      new Intl.DateTimeFormat(['en'],
+                              {timeZone: GMT, month: 'short', year: '2-digit'});
+
+  // If list is specified and doesn't contain 'en' then return.
+  if (process.config.variables.icu_locales && !haveLocale('en')) {
+    console.log('Skipping detailed Intl tests because English is not listed ' +
+                'as supported.');
+    // Smoke test. Does it format anything, or fail?
+    console.log('Date(0) formatted to: ' + dtf.format(date0));
+    return;
+  }
+
+  // Check with toLocaleString
+  var localeString = dtf.format(date0);
+  assert.equal(localeString, 'Jan 70');
+
+  // Options to request GMT
+  var optsGMT = {timeZone: GMT};
+
+  // Test format
+  localeString = date0.toLocaleString(['en'], optsGMT);
+  assert.equal(localeString, '1/1/1970, 12:00:00 AM');
+
+  // number format
+  assert.equal(new Intl.NumberFormat(['en']).format(12345.67890), '12,345.679');
+
+  var collOpts = { sensitivity: 'base', ignorePunctuation: true };
+  var coll = new Intl.Collator(['en'], collOpts);
+
+  assert.equal(coll.compare('blackbird', 'black-bird'), 0,
+               'ignore punctuation failed');
+  assert.equal(coll.compare('blackbird', 'red-bird'), -1,
+               'compare less failed');
+  assert.equal(coll.compare('bluebird', 'blackbird'), 1,
+               'compare greater failed');
+  assert.equal(coll.compare('Bluebird', 'bluebird'), 0,
+               'ignore case failed');
+  assert.equal(coll.compare('\ufb03', 'ffi'), 0,
+               'ffi ligature (contraction) failed');
+}
diff --git a/tools/configure.d/nodedownload.py b/tools/configure.d/nodedownload.py
new file mode 100644 (file)
index 0000000..e24efd8
--- /dev/null
@@ -0,0 +1,127 @@
+#!/usr/bin/env python
+# Moved some utilities here from ../../configure
+
+import urllib
+import hashlib
+import sys
+import zipfile
+import tarfile
+import fpformat
+import contextlib
+
+def formatSize(amt):
+    """Format a size as a string in MB"""
+    return fpformat.fix(amt / 1024000., 1)
+
+def spin(c):
+    """print out an ASCII 'spinner' based on the value of counter 'c'"""
+    spin = ".:|'"
+    return (spin[c % len(spin)])
+
+class ConfigOpener(urllib.FancyURLopener):
+    """fancy opener used by retrievefile. Set a UA"""
+    # append to existing version (UA)
+    version = '%s node.js/configure' % urllib.URLopener.version
+
+def reporthook(count, size, total):
+    """internal hook used by retrievefile"""
+    sys.stdout.write(' Fetch: %c %sMB total, %sMB downloaded   \r' %
+                     (spin(count),
+                      formatSize(total),
+                      formatSize(count*size)))
+
+def retrievefile(url, targetfile):
+    """fetch file 'url' as 'targetfile'. Return targetfile or throw."""
+    try:
+        sys.stdout.write(' <%s>\nConnecting...\r' % url)
+        sys.stdout.flush()
+        msg = ConfigOpener().retrieve(url, targetfile, reporthook=reporthook)
+        print ''  # clear the line
+        return targetfile
+    except:
+        print ' ** Error occurred while downloading\n <%s>' % url
+        raise
+
+def md5sum(targetfile):
+    """md5sum a file. Return the hex digest."""
+    digest = hashlib.md5()
+    with open(targetfile, 'rb') as f:
+      chunk = f.read(1024)
+      while chunk !=  "":
+        digest.update(chunk)
+        chunk = f.read(1024)
+    return digest.hexdigest()
+
+def unpack(packedfile, parent_path):
+    """Unpacks packedfile into parent_path. Assumes .zip. Returns parent_path"""
+    if zipfile.is_zipfile(packedfile):
+        with contextlib.closing(zipfile.ZipFile(packedfile, 'r')) as icuzip:
+            print ' Extracting zipfile: %s' % packedfile
+            icuzip.extractall(parent_path)
+            return parent_path
+    elif tarfile.is_tarfile(packedfile):
+        with tarfile.TarFile.open(packedfile, 'r') as icuzip:
+            print ' Extracting tarfile: %s' % packedfile
+            icuzip.extractall(parent_path)
+            return parent_path
+    else:
+        packedsuffix = packedfile.lower().split('.')[-1]  # .zip, .tgz etc
+        raise Exception('Error: Don\'t know how to unpack %s with extension %s' % (packedfile, packedsuffix))
+
+# List of possible "--download=" types.
+download_types = set(['icu'])
+
+# Default options for --download.
+download_default = "none"
+
+def help():
+  """This function calculates the '--help' text for '--download'."""
+  return """Select which packages may be auto-downloaded.
+valid values are: none, all, %s. (default is "%s").""" % (", ".join(download_types), download_default)
+
+def set2dict(keys, value=None):
+  """Convert some keys (iterable) to a dict."""
+  return dict((key, value) for (key) in keys)
+
+def parse(opt):
+  """This function parses the options to --download and returns a set such as { icu: true }, etc. """
+  if not opt:
+    opt = download_default
+
+  theOpts = set(opt.split(','))
+
+  if 'all' in theOpts:
+    # all on
+    return set2dict(download_types, True)
+  elif 'none' in theOpts:
+    # all off
+    return set2dict(download_types, False)
+
+  # OK. Now, process each of the opts.
+  theRet = set2dict(download_types, False)
+  for anOpt in opt.split(','):
+    if not anOpt or anOpt == "":
+      # ignore stray commas, etc.
+      continue
+    elif anOpt is 'all':
+      # all on
+      theRet = dict((key, True) for (key) in download_types)
+    else:
+      # turn this one on
+      if anOpt in download_types:
+        theRet[anOpt] = True
+      else:
+        # future proof: ignore unknown types
+        print 'Warning: ignoring unknown --download= type "%s"' % anOpt
+  # all done
+  return theRet
+
+def candownload(auto_downloads, package):
+  if not (package in auto_downloads.keys()):
+    raise Exception('Internal error: "%s" is not in the --downloads list. Check nodedownload.py' % package)
+  if auto_downloads[package]:
+    return True
+  else:
+    print """Warning: Not downloading package "%s". You could pass "--download=all"
+    (Windows: "download-all") to try auto-downloading it.""" % package
+    return False
index 220d2c1..bb2b5e5 100644 (file)
   'includes': [ '../../icu_config.gypi' ],
   'targets': [
     {
+      # a target for additional uconfig defines, target only
+      'target_name': 'icu_uconfig_target',
+      'type': 'none',
+      'toolsets': [ 'target' ],
+      'direct_dependent_settings': {
+        'defines': [
+          'UCONFIG_NO_CONVERSION=1',
+        ]
+      },
+    },
+    {
       # a target to hold uconfig defines.
       # for now these are hard coded, but could be defined.
       'target_name': 'icu_uconfig',
     },
     {
       'target_name': 'icui18n',
-      'type': '<(library)',
-      'toolsets': [ 'target' ],
-      'sources': [
-        '<@(icu_src_i18n)'
-      ],
-      'include_dirs': [
-        '../../deps/icu/source/i18n',
-      ],
-      'defines': [
-        'U_I18N_IMPLEMENTATION=1',
+      'toolsets': [ 'target', 'host' ],
+      'conditions' : [
+        ['_toolset=="target"', {
+          'type': '<(library)',
+          'sources': [
+            '<@(icu_src_i18n)'
+          ],
+          'conditions': [
+            [ 'icu_ver_major == 54', { 'sources!': [
+              ## Strip out the following for ICU 54 only.
+              ## add more conditions in the future?
+              ## if your compiler can dead-strip, this will
+              ## make ZERO difference to binary size.
+              ## Made ICU-specific for future-proofing.
+
+              # alphabetic index
+              '../../deps/icu/source/i18n/alphaindex.cpp',
+              # BOCSU
+              # misc
+              '../../deps/icu/source/i18n/regexcmp.cpp',
+              '../../deps/icu/source/i18n/regexcmp.h',
+              '../../deps/icu/source/i18n/regexcst.h',
+              '../../deps/icu/source/i18n/regeximp.cpp',
+              '../../deps/icu/source/i18n/regeximp.h',
+              '../../deps/icu/source/i18n/regexst.cpp',
+              '../../deps/icu/source/i18n/regexst.h',
+              '../../deps/icu/source/i18n/regextxt.cpp',
+              '../../deps/icu/source/i18n/regextxt.h',
+              '../../deps/icu/source/i18n/region.cpp',
+              '../../deps/icu/source/i18n/region_impl.h',
+              '../../deps/icu/source/i18n/reldatefmt.cpp',
+              '../../deps/icu/source/i18n/reldatefmt.h'
+              '../../deps/icu/source/i18n/scientificformathelper.cpp',
+              '../../deps/icu/source/i18n/tmunit.cpp',
+              '../../deps/icu/source/i18n/tmutamt.cpp',
+              '../../deps/icu/source/i18n/tmutfmt.cpp',
+              '../../deps/icu/source/i18n/uregex.cpp',
+              '../../deps/icu/source/i18n/uregexc.cpp',
+              '../../deps/icu/source/i18n/uregion.cpp',
+              '../../deps/icu/source/i18n/uspoof.cpp',
+              '../../deps/icu/source/i18n/uspoof_build.cpp',
+              '../../deps/icu/source/i18n/uspoof_conf.cpp',
+              '../../deps/icu/source/i18n/uspoof_conf.h',
+              '../../deps/icu/source/i18n/uspoof_impl.cpp',
+              '../../deps/icu/source/i18n/uspoof_impl.h',
+              '../../deps/icu/source/i18n/uspoof_wsconf.cpp',
+              '../../deps/icu/source/i18n/uspoof_wsconf.h',
+            ]}]],
+          'include_dirs': [
+            '../../deps/icu/source/i18n',
+          ],
+          'defines': [
+            'U_I18N_IMPLEMENTATION=1',
+          ],
+          'dependencies': [ 'icuucx', 'icu_implementation', 'icu_uconfig', 'icu_uconfig_target' ],
+          'direct_dependent_settings': {
+            'include_dirs': [
+              '../../deps/icu/source/i18n',
+            ],
+          },
+          'export_dependent_settings': [ 'icuucx', 'icu_uconfig_target' ],
+        }],
+        ['_toolset=="host"', {
+          'type': 'none',
+          'dependencies': [ 'icutools' ],
+          'export_dependent_settings': [ 'icutools' ],
+        }],
       ],
-      'dependencies': [ 'icuucx', 'icu_implementation', 'icu_uconfig' ],
-      'direct_dependent_settings': {
-        'include_dirs': [
-          '../../deps/icu/source/i18n',
-        ],
-      },
-      'export_dependent_settings': [ 'icuucx' ],
     },
     # This exports actual ICU data
     {
                   # trim down ICU
                   'action_name': 'icutrim',
                   'inputs': [ '<(icu_data_in)', 'icu_small.json' ],
-                  'outputs': [ '../../out/icutmp/icudt<(icu_ver_major)<(icu_endianness).dat' ],
+                  'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/icutmp/icudt<(icu_ver_major)<(icu_endianness).dat' ],
                   'action': [ 'python',
                               'icutrim.py',
                               '-P', '../../<(CONFIGURATION_NAME)',
                               '-D', '<(icu_data_in)',
                               '--delete-tmp',
-                              '-T', '../../out/icutmp',
+                              '-T', '<(SHARED_INTERMEDIATE_DIR)/icutmp',
                               '-F', 'icu_small.json',
                               '-O', 'icudt<(icu_ver_major)<(icu_endianness).dat',
-                              '-v' ],
+                              '-v',
+                              '-L', '<(icu_locales)'],
                 },
                 {
                   # build final .dat -> .obj
                   'action_name': 'genccode',
-                  'inputs': [ '../../out/icutmp/icudt<(icu_ver_major)<(icu_endianness).dat' ],
-                  'outputs': [ '../../out/icudt<(icu_ver_major)<(icu_endianness)_dat.obj' ],
+                  'inputs': [ '<(SHARED_INTERMEDIATE_DIR)/icutmp/icudt<(icu_ver_major)<(icu_endianness).dat' ],
+                  'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/icudt<(icu_ver_major)<(icu_endianness)_dat.obj' ],
                   'action': [ '../../<(CONFIGURATION_NAME)/genccode',
                               '-o',
-                              '-d', '../../out/',
+                              '-d', '<(SHARED_INTERMEDIATE_DIR)/',
                               '-n', 'icudata',
                               '-e', 'icusmdt<(icu_ver_major)',
                               '<@(_inputs)' ],
                 },
               ],
               # This file contains the small ICU data.
-              'sources': [ '../../out/icudt<(icu_ver_major)<(icu_endianness)_dat.obj' ],
+              'sources': [ '<(SHARED_INTERMEDIATE_DIR)/icudt<(icu_ver_major)<(icu_endianness)_dat.obj' ],
             } ] ], #end of OS==win and icu_small == true
         }, { # OS != win
           'conditions': [
                               '-T', '<(SHARED_INTERMEDIATE_DIR)/icutmp',
                               '-F', 'icu_small.json',
                               '-O', 'icudt<(icu_ver_major)<(icu_endianness).dat',
-                              '-v' ],
+                              '-v',
+                              '-L', '<(icu_locales)'],
                 }, {
                   # rename to get the final entrypoint name right
                    'action_name': 'rename',
     {
       'target_name': 'icuuc',
       'type': 'none',
-      'toolsets': [ 'target' ],
-      'dependencies': [ 'icuucx', 'icudata' ],
-      'export_dependent_settings': [ 'icuucx', 'icudata' ],
+      'toolsets': [ 'target', 'host' ],
+      'conditions' : [
+        ['_toolset=="host"', {
+          'dependencies': [ 'icutools' ],
+          'export_dependent_settings': [ 'icutools' ],
+        }],
+        ['_toolset=="target"', {
+          'dependencies': [ 'icuucx', 'icudata' ],
+          'export_dependent_settings': [ 'icuucx', 'icudata' ],
+        }],
+      ],
     },
     # This is the 'real' icuuc.
-    # tools can depend on 'icuuc + stubdata'
     {
       'target_name': 'icuucx',
       'type': '<(library)',
-      'dependencies': [ 'icu_implementation', 'icu_uconfig' ],
+      'dependencies': [ 'icu_implementation', 'icu_uconfig', 'icu_uconfig_target' ],
       'toolsets': [ 'target' ],
       'sources': [
-        '<@(icu_src_common)'
+        '<@(icu_src_common)',
+      ],
+      'conditions': [
+        [ 'icu_ver_major == 54', { 'sources!': [
+          ## Strip out the following for ICU 54 only.
+          ## add more conditions in the future?
+          ## if your compiler can dead-strip, this will
+          ## make ZERO difference to binary size.
+          ## Made ICU-specific for future-proofing.
+
+          # bidi- not needed (yet!)
+          '../../deps/icu/source/common/ubidi.c',
+          '../../deps/icu/source/common/ubidiimp.h',
+          '../../deps/icu/source/common/ubidiln.c',
+          '../../deps/icu/source/common/ubidiwrt.c',
+          #'../../deps/icu/source/common/ubidi_props.c',
+          #'../../deps/icu/source/common/ubidi_props.h',
+          #'../../deps/icu/source/common/ubidi_props_data.h',
+          # and the callers
+          '../../deps/icu/source/common/ushape.cpp',
+          '../../deps/icu/source/common/usprep.cpp',
+          '../../deps/icu/source/common/uts46.cpp',
+        ]}],
+        [ 'OS == "solaris"', { 'defines': [
+          '_XOPEN_SOURCE_EXTENDED=0',
+        ]}],
       ],
       'include_dirs': [
         '../../deps/icu/source/common',
       'defines': [
         'U_COMMON_IMPLEMENTATION=1',
       ],
-      'export_dependent_settings': [ 'icu_uconfig' ],
+      'cflags_c': ['-std=c99'],
+      'export_dependent_settings': [ 'icu_uconfig', 'icu_uconfig_target' ],
       'direct_dependent_settings': {
         'include_dirs': [
           '../../deps/icu/source/common',
         '<@(icu_src_io)',
         '<@(icu_src_stubdata)',
       ],
+      'sources!': [
+        '../../deps/icu/source/tools/toolutil/udbgutil.cpp',
+        '../../deps/icu/source/tools/toolutil/udbgutil.h',
+        '../../deps/icu/source/tools/toolutil/dbgutil.cpp',
+        '../../deps/icu/source/tools/toolutil/dbgutil.h',
+      ],
       'include_dirs': [
         '../../deps/icu/source/common',
         '../../deps/icu/source/i18n',
         'U_TOOLUTIL_IMPLEMENTATION=1',
         #'DEBUG=0', # http://bugs.icu-project.org/trac/ticket/10977
       ],
+      'cflags_c': ['-std=c99'],
+      'conditions': [
+        ['OS == "solaris"', {
+          'defines': [ '_XOPEN_SOURCE_EXTENDED=0' ]
+        }]
+      ],
       'direct_dependent_settings': {
         'include_dirs': [
           '../../deps/icu/source/common',
           }],
         ],
       },
-      'export_dependent_settings': [ 'icu_implementation', 'icu_uconfig' ],
+      'export_dependent_settings': [ 'icu_uconfig' ],
     },
     # This tool is needed to rebuild .res files from .txt,
     # or to build index (res_index.txt) files for small-icu
index ddf7d12..e434794 100644 (file)
@@ -1,11 +1,11 @@
 {
   "copyright": "Copyright (c) 2014 IBM Corporation and Others. All Rights Reserved.",
-  "comment": "icutrim.py config: Trim down ICU to just English, needed for node.js use.",
+  "comment": "icutrim.py config: Trim down ICU to just a certain locale set, needed for node.js use.",
   "variables": {
     "none": {
       "only": []
     },
-    "en_only": {
+    "locales": {
       "only": [
         "root",
         "en"
     }
   },
   "trees": {
-    "ROOT": "en_only",
+    "ROOT": "locales",
     "brkitr": "none",
-    "coll": "en_only",
-    "curr": "en_only",
+    "coll": "locales",
+    "curr": "locales",
     "lang": "none",
     "rbnf": "none",
     "region": "none",
-    "zone": "en_only",
+    "zone": "locales",
     "converters": "none",
     "stringprep": "none",
     "translit": "none",
     "brkfiles": "none",
     "brkdict": "none",
-    "confusables": "none"
+    "confusables": "none",
+    "unit": "none"
   },
   "remove": [
     "cnvalias.icu",
index 7f0fb37..517bf39 100755 (executable)
@@ -65,6 +65,12 @@ parser.add_option("-v","--verbose",
                     action="count",
                     default=0)
 
+parser.add_option('-L',"--locales",
+                  action="store",
+                  dest="locales",
+                  help="sets the 'locales.only' variable",
+                  default=None)
+
 parser.add_option('-e', '--endian', action='store', dest='endian', help='endian, big, little or host, your default is "%s".' % endian, default=endian, metavar='endianness')
 
 (options, args) = parser.parse_args()
@@ -147,6 +153,13 @@ fi= open(options.filterfile, "rb")
 config=json.load(fi)
 fi.close()
 
+if (options.locales):
+  if not config.has_key("variables"):
+    config["variables"] = {}
+  if not config["variables"].has_key("locales"):
+    config["variables"]["locales"] = {}
+  config["variables"]["locales"]["only"] = options.locales.split(',')
+
 if (options.verbose > 6):
     print config
 
index 616b5bb..39c656f 100644 (file)
@@ -36,6 +36,7 @@ set noperfctr=
 set noperfctr_arg=
 set noperfctr_msi_arg=
 set i18n_arg=
+set download_arg=
 
 :next-arg
 if "%1"=="" goto args-done
@@ -65,6 +66,8 @@ if /i "%1"=="upload"        set upload=1&goto arg-ok
 if /i "%1"=="jslint"        set jslint=1&goto arg-ok
 if /i "%1"=="small-icu"     set i18n_arg=%1&goto arg-ok
 if /i "%1"=="full-icu"      set i18n_arg=%1&goto arg-ok
+if /i "%1"=="intl-none"     set i18n_arg=%1&goto arg-ok
+if /i "%1"=="download-all"  set download_arg="--download=all"&goto arg-ok
 
 echo Warning: ignoring invalid command line option `%1`.
 
@@ -85,6 +88,7 @@ if defined noperfctr set noperfctr_arg=--without-perfctr& set noperfctr_msi_arg=
 
 if "%i18n_arg%"=="full-icu" set i18n_arg=--with-intl=full-icu
 if "%i18n_arg%"=="small-icu" set i18n_arg=--with-intl=small-icu
+if "%i18n_arg%"=="intl-none" set i18n_arg=--with-intl=none
 
 :project-gen
 @rem Skip project generation if requested.
@@ -95,7 +99,7 @@ if defined NIGHTLY set TAG=nightly-%NIGHTLY%
 @rem Generate the VS project.
 SETLOCAL
   if defined VS100COMNTOOLS call "%VS100COMNTOOLS%\VCVarsQueryRegistry.bat"
-  python configure %i18n_arg% %debug_arg% %nosnapshot_arg% %noetw_arg% %noperfctr_arg% --dest-cpu=%target_arch% --tag=%TAG%
+  python configure %download_arg% %i18n_arg% %debug_arg% %nosnapshot_arg% %noetw_arg% %noperfctr_arg% --dest-cpu=%target_arch% --tag=%TAG%
   if errorlevel 1 goto create-msvs-files-failed
   if not exist node.sln goto create-msvs-files-failed
   echo Project files generated.
@@ -232,7 +236,7 @@ python tools/closure_linter/closure_linter/gjslint.py --unix_mode --strict --noj
 goto exit
 
 :help
-echo vcbuild.bat [debug/release] [msi] [test-all/test-uv/test-internet/test-pummel/test-simple/test-message] [clean] [noprojgen] [nobuild] [nosign] [x86/x64]
+echo vcbuild.bat [debug/release] [msi] [test-all/test-uv/test-internet/test-pummel/test-simple/test-message] [clean] [noprojgen] [small-icu/full-icu/intl-none] [nobuild] [nosign] [x86/x64] [download-all]
 echo Examples:
 echo   vcbuild.bat                : builds release build
 echo   vcbuild.bat debug          : builds debug build