From 50e00de92a7563f39ff50f9a53c7e2ed15e556c6 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Thu, 2 Aug 2012 01:06:31 +0200 Subject: [PATCH] installer: fix cross-compile installs The old installer was a JS script, which didn't work if node had been cross-compiled for another architecture. Replace it with a python script. Fixes #3807. --- Makefile | 4 +- tools/install.py | 214 +++++++++++++++++++++++++++++++++++++++++++++ tools/installer.js | 158 --------------------------------- 3 files changed, 216 insertions(+), 160 deletions(-) create mode 100755 tools/install.py delete mode 100644 tools/installer.js diff --git a/Makefile b/Makefile index 15546d06d5c..795b11a5208 100644 --- a/Makefile +++ b/Makefile @@ -39,10 +39,10 @@ out/Makefile: common.gypi deps/uv/uv.gyp deps/http_parser/http_parser.gyp deps/z $(PYTHON) tools/gyp_node -f make install: all - out/Release/node tools/installer.js install $(DESTDIR) + $(PYTHON) tools/install.py $@ $(DESTDIR) uninstall: - out/Release/node tools/installer.js uninstall + $(PYTHON) tools/install.py $@ $(DESTDIR) clean: -rm -rf out/Makefile node node_g out/$(BUILDTYPE)/node blog.html email.md diff --git a/tools/install.py b/tools/install.py new file mode 100755 index 00000000000..66e51d0ae3b --- /dev/null +++ b/tools/install.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python + +import errno +import json +import os +import re +import shutil +import sys + +# set at init time +dst_dir = None +node_prefix = None # dst_dir without DESTDIR prefix +target_defaults = None +variables = None + +def abspath(*args): + path = os.path.join(*args) + return os.path.abspath(path) + +def load_config(): + s = open('config.gypi').read() + s = re.sub(r'#.*?\n', '', s) # strip comments + s = re.sub(r'\'', '"', s) # convert quotes + return json.loads(s) + +def try_unlink(path): + try: + os.unlink(path) + except OSError, e: + if e.errno != errno.ENOENT: raise + +def try_symlink(source_path, link_path): + print 'symlinking %s -> %s' % (source_path, link_path) + try_unlink(link_path) + os.symlink(source_path, link_path) + +def try_mkdir_r(path): + try: + os.makedirs(path) + except OSError, e: + if e.errno != errno.EEXIST: raise + +def try_rmdir_r(path): + path = abspath(path) + while path.startswith(dst_dir): + try: + os.rmdir(path) + except OSError, e: + if e.errno == errno.ENOTEMPTY: return + if e.errno == errno.ENOENT: return + raise + path = abspath(path, '..') + +def mkpaths(path, dst): + if dst.endswith('/'): + target_path = abspath(dst_dir, dst, os.path.basename(path)) + else: + target_path = abspath(dst_dir, dst) + return path, target_path + +def try_copy(path, dst): + source_path, target_path = mkpaths(path, dst) + print 'installing %s' % target_path + try_mkdir_r(os.path.dirname(target_path)) + return shutil.copy2(source_path, target_path) + +def try_remove(path, dst): + source_path, target_path = mkpaths(path, dst) + print 'removing %s' % target_path + try_unlink(target_path) + try_rmdir_r(os.path.dirname(target_path)) + +def install(paths, dst): map(lambda path: try_copy(path, dst), paths) +def uninstall(paths, dst): map(lambda path: try_remove(path, dst), paths) + +def waf_files(action): + action(['tools/node-waf'], 'bin/node-waf') + action(['tools/wafadmin/ansiterm.py', + 'tools/wafadmin/Build.py', + 'tools/wafadmin/Configure.py', + 'tools/wafadmin/Constants.py', + 'tools/wafadmin/Environment.py', + 'tools/wafadmin/__init__.py', + 'tools/wafadmin/Logs.py', + 'tools/wafadmin/Node.py', + 'tools/wafadmin/Options.py', + 'tools/wafadmin/pproc.py', + 'tools/wafadmin/py3kfixes.py', + 'tools/wafadmin/Runner.py', + 'tools/wafadmin/Scripting.py', + 'tools/wafadmin/TaskGen.py', + 'tools/wafadmin/Task.py', + 'tools/wafadmin/Tools/ar.py', + 'tools/wafadmin/Tools/cc.py', + 'tools/wafadmin/Tools/ccroot.py', + 'tools/wafadmin/Tools/compiler_cc.py', + 'tools/wafadmin/Tools/compiler_cxx.py', + 'tools/wafadmin/Tools/compiler_d.py', + 'tools/wafadmin/Tools/config_c.py', + 'tools/wafadmin/Tools/cxx.py', + 'tools/wafadmin/Tools/dmd.py', + 'tools/wafadmin/Tools/d.py', + 'tools/wafadmin/Tools/gas.py', + 'tools/wafadmin/Tools/gcc.py', + 'tools/wafadmin/Tools/gdc.py', + 'tools/wafadmin/Tools/gnu_dirs.py', + 'tools/wafadmin/Tools/gob2.py', + 'tools/wafadmin/Tools/gxx.py', + 'tools/wafadmin/Tools/icc.py', + 'tools/wafadmin/Tools/icpc.py', + 'tools/wafadmin/Tools/__init__.py', + 'tools/wafadmin/Tools/intltool.py', + 'tools/wafadmin/Tools/libtool.py', + 'tools/wafadmin/Tools/misc.py', + 'tools/wafadmin/Tools/nasm.py', + 'tools/wafadmin/Tools/node_addon.py', + 'tools/wafadmin/Tools/osx.py', + 'tools/wafadmin/Tools/preproc.py', + 'tools/wafadmin/Tools/python.py', + 'tools/wafadmin/Tools/suncc.py', + 'tools/wafadmin/Tools/suncxx.py', + 'tools/wafadmin/Tools/unittestw.py', + 'tools/wafadmin/Tools/winres.py', + 'tools/wafadmin/Tools/xlc.py', + 'tools/wafadmin/Tools/xlcxx.py', + 'tools/wafadmin/Utils.py'], + 'lib/node/') + +def update_shebang(path, shebang): + print 'updating shebang of %s' % path + s = open(path, 'r').read() + s = re.sub(r'#!.*\n', '#!' + shebang + '\n', s) + open(path, 'w').write(s) + +def npm_files(action): + target_path = 'lib/node_modules/npm/' + + # don't install npm if the target path is a symlink, it probably means + # that a dev version of npm is installed there + if os.path.islink(abspath(dst_dir, target_path)): return + + # npm has a *lot* of files and it'd be a pain to maintain a fixed list here + # so we walk its source directory instead... + for dirname, subdirs, basenames in os.walk('deps/npm', topdown=True): + subdirs[:] = filter('test'.__ne__, subdirs) # skip test suites + paths = [os.path.join(dirname, basename) for basename in basenames] + action(paths, target_path + dirname[9:] + '/') + + # create/remove symlink + link_path = abspath(dst_dir, 'bin/npm') + if action == uninstall: + action([link_path], 'bin/npm') + elif action == install: + try_symlink('../lib/node_modules/npm/bin/npm-cli.js', link_path) + update_shebang(link_path, node_prefix + '/bin/node') + else: + assert(0) # unhandled action type + +def files(action): + action(['deps/uv/include/ares.h', + 'deps/uv/include/ares_version.h', + 'deps/uv/include/uv.h', + 'deps/v8/include/v8-debug.h', + 'deps/v8/include/v8-preparser.h', + 'deps/v8/include/v8-profiler.h', + 'deps/v8/include/v8-testing.h', + 'deps/v8/include/v8.h', + 'deps/v8/include/v8stdint.h', + 'src/eio-emul.h', + 'src/ev-emul.h', + 'src/node.h', + 'src/node_buffer.h', + 'src/node_object_wrap.h', + 'src/node_version.h'], + 'include/node/') + action(['deps/uv/include/uv-private/eio.h', + 'deps/uv/include/uv-private/ev.h', + 'deps/uv/include/uv-private/ngx-queue.h', + 'deps/uv/include/uv-private/tree.h', + 'deps/uv/include/uv-private/uv-unix.h', + 'deps/uv/include/uv-private/uv-win.h'], + 'include/node/uv-private/') + action(['doc/node.1'], 'share/man/man1/') + action(['out/Release/node'], 'bin/node') + + # install unconditionally, checking if the platform supports dtrace doesn't + # work when cross-compiling and besides, there's at least one linux flavor + # with dtrace support now (oracle's "unbreakable" linux) + action(['src/node.d'], 'lib/dtrace/') + + if variables.get('node_install_waf'): waf_files(action) + if variables.get('node_install_npm'): npm_files(action) + +def run(args): + global dst_dir, node_prefix, target_defaults, variables + + # chdir to the project's top-level directory + os.chdir(abspath(os.path.dirname(__file__), '..')) + + conf = load_config() + variables = conf['variables'] + target_defaults = conf['target_defaults'] + + # argv[2] is a custom install prefix for packagers (think DESTDIR) + dst_dir = node_prefix = variables.get('node_prefix', '/usr/local') + if len(args) > 2: dst_dir = abspath(args[2] + '/' + dst_dir) + + cmd = args[1] if len(args) > 1 else 'install' + if cmd == 'install': return files(install) + if cmd == 'uninstall': return files(uninstall) + raise RuntimeError('Bad command: %s\n' % cmd) + +if __name__ == '__main__': + run(sys.argv[:]) diff --git a/tools/installer.js b/tools/installer.js deleted file mode 100644 index 46b84d49d27..00000000000 --- a/tools/installer.js +++ /dev/null @@ -1,158 +0,0 @@ -var fs = require('fs'), - path = require('path'), - exec = require('child_process').exec, - cmd = process.argv[2], - dest_dir = process.argv[3] || ''; - -if (cmd !== 'install' && cmd !== 'uninstall') { - console.error('Unknown command: ' + cmd); - process.exit(1); -} - -// Use the built-in config reported by the current process -var variables = process.config.variables, - node_prefix = variables.node_prefix || '/usr/local'; - -// Execution queue -var queue = [], - dirs = []; - -// Copy file from src to dst -function copy(src, dst, callback) { - // If src is array - copy each file separately - if (Array.isArray(src)) { - src.forEach(function(src) { - copy(src, dst, callback); - }); - return; - } - - dst = path.join(dest_dir, node_prefix, dst); - var dir = dst.replace(/\/[^\/]*$/, '/'); - - // Create directory if hasn't done this yet - if (dirs.indexOf(dir) === -1) { - dirs.push(dir); - queue.push('mkdir -p ' + dir); - } - - // Queue file/dir copy - queue.push('cp -rf ' + src + ' ' + dst); -} - -// Remove files -function remove(files) { - files.forEach(function(file) { - file = path.join(dest_dir, node_prefix, file); - queue.push('rm -rf ' + file); - }); -} - -// Add/update shebang (#!) line so that npm uses -// the newly installed node, rather than the first in PATH. -function shebang(line, npmDir) { - var script = JSON.stringify(path.join(npmDir, 'scripts/relocate.sh')); - var bin = JSON.stringify(path.join(npmDir, 'bin/npm-cli.js')); - queue.push('/bin/sh ' + script + ' ' + line); -} - -// Run every command in queue, one-by-one -function run() { - var cmd = queue.shift(); - if (!cmd) return; - - if (Array.isArray(cmd) && cmd[0] instanceof Function) { - var func = cmd[0]; - var args = cmd.slice(1); - console.log.apply(null, [func.name].concat(args)); - func.apply(null, args); - run(); - } else { - console.log(cmd); - exec(cmd, function(err, stdout, stderr) { - if (stderr) console.error(stderr); - if (err) process.exit(1); - - run(); - }); - } -} - -if (cmd === 'install') { - // Copy includes - copy([ - // Node - 'src/node.h', 'src/node_buffer.h', 'src/node_object_wrap.h', - 'src/node_version.h', 'src/ev-emul.h', 'src/eio-emul.h', - // v8 - 'deps/v8/include/v8-debug.h', 'deps/v8/include/v8-preparser.h', - 'deps/v8/include/v8-profiler.h', 'deps/v8/include/v8-testing.h', - 'deps/v8/include/v8.h', 'deps/v8/include/v8stdint.h', - // uv - 'deps/uv/include/uv.h' - ], 'include/node/'); - - // man page - copy(['doc/node.1'], 'share/man/man1/'); - - // dtrace - if (!process.platform.match(/^linux/)) { - copy(['src/node.d'], 'lib/dtrace/'); - } - - // Private uv headers - copy([ - 'deps/uv/include/uv-private/eio.h', 'deps/uv/include/uv-private/ev.h', - 'deps/uv/include/uv-private/ngx-queue.h', - 'deps/uv/include/uv-private/tree.h', - 'deps/uv/include/uv-private/uv-unix.h', - 'deps/uv/include/uv-private/uv-win.h', - ], 'include/node/uv-private/'); - - copy([ - 'deps/uv/include/ares.h', - 'deps/uv/include/ares_version.h' - ], 'include/node/'); - - // Copy binary file - copy('out/Release/node', 'bin/node'); - - // Install node-waf - if (variables.node_install_waf) { - copy('tools/wafadmin', 'lib/node/'); - copy('tools/node-waf', 'bin/node-waf'); - } - - // Install npm (eventually) - if (variables.node_install_npm) { - // Frequently, in development, the installed npm is a symbolic - // link to the development folder, and so installing this is - // a bit annoying. If it's a symlink, skip it. - var isSymlink = false; - var exists = true; - var npmDir = path.resolve(node_prefix, 'lib/node_modules/npm'); - try { - var st = fs.lstatSync(npmDir); - isSymlink = st.isSymbolicLink(); - } catch (e) { - exists = true; - } - - if (!isSymlink) { - if (exists) queue.push('rm -rf ' + npmDir); - copy('deps/npm', 'lib/node_modules/npm'); - queue.push('ln -sf ../lib/node_modules/npm/bin/npm-cli.js ' + - path.join(dest_dir, node_prefix, 'bin/npm')); - shebang(path.join(node_prefix, 'bin/node'), - path.join(dest_dir, node_prefix, 'lib/node_modules/npm')); - } - } -} else { - remove([ - 'bin/node', 'bin/npm', 'bin/node-waf', - 'include/node/*', 'lib/node_modules', 'lib/node', - 'lib/dtrace/node.d', 'share/man/man1/node.1' - ]); -} - -run();