#!/usr/bin/env python import sys, optparse, subprocess, urllib2, os, os.path, errno, zipfile, string, json, platform, shutil, tarfile, urlparse, tempfile # EMSDK_DEV is a developer mode flag, which, if true, the SDK is downloaded from a 'staging' online source, # instead of the public source. New releases are first deployed to the staging source for testing, before # being published to the public. Don't enable this unless you develop EMSDK itself and need to access the # staging source repository instead. EMSDK_DEV = bool(os.getenv('EMSDK_DEV')) if os.getenv('EMSDK_DEV') != None else False if EMSDK_DEV: print 'EMSDK_DEV active.' emsdk_master_server = 'http://clb.demon.fi/emscripten_dev/' else: emsdk_master_server = 'https://s3.amazonaws.com/mozilla-games/emscripten/' emsdk_packages_url = urlparse.urljoin(emsdk_master_server, 'packages/') emscripten_git_repo = 'git@github.com:kripken/emscripten.git' WINDOWS = False if os.name == 'nt': WINDOWS = True ENVPATH_SEPARATOR = ';' LINUX = False if platform.system() == 'Linux': LINUX = True ENVPATH_SEPARATOR = ':' OSX = False if platform.mac_ver()[0] != '': OSX = True ENVPATH_SEPARATOR = ':' def win_get_environment_variable(key, system=True): prev_path = os.environ['PATH'] try: py = find_used_python() if py: py_path = to_native_path(py.expand_vars(py.activated_path)) os.environ['PATH'] = os.environ['PATH'] + ';' + py_path import win32api, win32con if system: # Read globally from ALL USERS section. folder = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment') else: # Register locally from CURRENT USER section. folder = win32api.RegOpenKey(win32con.HKEY_CURRENT_USER, 'Environment') value = str(win32api.RegQueryValueEx(folder, key)[0]) except Exception, e: if e[0] != 2: # 'The system cannot find the file specified.' print >> sys.stderr, 'Failed to read environment variable ' + key + ':' print >> sys.stderr, str(e) win32api.RegCloseKey(folder) os.environ['PATH'] = prev_path return None win32api.RegCloseKey(folder) os.environ['PATH'] = prev_path return value def win_environment_variable_exists(key, system=True): value = win_get_environment_variable(key, system) return value != None and len(value) > 0 def win_get_active_environment_variable(key): value = win_get_environment_variable(key, False) if value != None: return value return win_get_environment_variable(key, True) def win_set_environment_variable(key, value, system=True): # print >> sys.stderr, 'set ' + str(key) + '=' + str(value) + ', in system=' + str(system) previous_value = win_get_environment_variable(key, system) if previous_value == value: return # No need to elevate UAC for nothing to set the same value, skip. if not value: try: if system: value = subprocess.call(['REG', 'DELETE', 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', '/V', key, '/f'], stdout=subprocess.PIPE) else: value = subprocess.call(['REG', 'DELETE', 'HKCU\\Environment', '/V', key, '/f'], stdout=subprocess.PIPE) except Exception, e: return return try: if system: cmd = ['bin/elevate.exe', 'SETX', '/M'] else: cmd = ['SETX'] retcode = subprocess.call(cmd + [key, value], stdout=subprocess.PIPE) if retcode is not 0: print >> sys.stderr, 'ERROR! Failed to set environment variable ' + key + '=' + value + '. You may need to set it manually.' except Exception, e: print >> sys.stderr, 'ERROR! Failed to set environment variable ' + key + '=' + value + ':' print >> sys.stderr, str(e) print >> sys.stderr, 'You may need to set it manually.' def win_delete_environment_variable(key, system=True): win_set_environment_variable(key, None, system) # Returns the absolute pathname to the given path inside the Emscripten SDK. def sdk_path(path): if os.path.isabs(path): return path else: return to_unix_path(os.path.join(os.path.dirname(os.path.realpath(__file__)), path)) def emsdk_path(): return to_unix_path(os.path.dirname(os.path.realpath(__file__))) # Modifies the given file in-place to contain '\r\n' line endings. def file_to_crlf(filename): text = open(filename, 'r').read() text = text.replace('\r\n', '\n').replace('\n', '\r\n') open(filename, 'wb').write(text) # Modifies the given file in-place to contain '\n' line endings. def file_to_lf(filename): text = open(filename, 'r').read() text = text.replace('\r\n', '\n') open(filename, 'wb').write(text) # Removes a single file, suppressing exceptions on failure. def rmfile(filename): try: os.remove(filename) except: pass def fix_lineendings(filename): if WINDOWS: file_to_crlf(filename) else: file_to_lf(filename) # http://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python def mkdir_p(path): if os.path.exists(path): return try: os.makedirs(path) except OSError as exc: # Python >2.5 if exc.errno == errno.EEXIST and os.path.isdir(path): pass else: raise def num_files_in_directory(path): if not os.path.isdir(path): return 0 return len([name for name in os.listdir(path) if os.path.exists(os.path.join(path, name))]) def run(cmd, cwd=None): process = subprocess.Popen(cmd, cwd=cwd, env=os.environ.copy()) process.communicate() if process.returncode != 0: print str(cmd) + ' failed with error code ' + str(process.returncode) + '!' return process.returncode # http://pythonicprose.blogspot.fi/2009/10/python-extract-targz-archive.html def untargz(source_filename, dest_dir, unpack_even_if_exists=False): if not unpack_even_if_exists and num_files_in_directory(dest_dir) > 0: print "File '" + source_filename + "' has already been unpacked, skipping." return True print "Unpacking '" + source_filename + "' to '" + dest_dir + "'" mkdir_p(dest_dir) run(['tar', '-xvf', sdk_path(source_filename), '--strip', '1'], cwd=dest_dir) #tfile = tarfile.open(source_filename, 'r:gz') #tfile.extractall(dest_dir) return True # http://stackoverflow.com/questions/12886768/simple-way-to-unzip-file-in-python-on-all-oses def unzip(source_filename, dest_dir, unpack_even_if_exists=False): if not unpack_even_if_exists and num_files_in_directory(dest_dir) > 0: print "File '" + source_filename + "' has already been unpacked, skipping." return True print "Unpacking '" + source_filename + "' to '" + dest_dir + "'" mkdir_p(dest_dir) common_subdir = None try: with zipfile.ZipFile(source_filename) as zf: # Implement '--strip 1' behavior to unzipping by testing if all the files in the zip reside in a common subdirectory, and if so, # we move the output tree at the end of uncompression step. for member in zf.infolist(): words = member.filename.split('/') if len(words) > 1: # If there is a directory component? if common_subdir == None: common_subdir = words[0] elif common_subdir != words[0]: common_subdir = '' break unzip_to_dir = dest_dir if common_subdir: unzip_to_dir = os.path.join('/'.join(dest_dir.split('/')[:-1]), 'unzip_temp') # Now do the actual decompress. for member in zf.infolist(): # Path traversal defense copied from # http://hg.python.org/cpython/file/tip/Lib/http/server.py#l789 words = member.filename.split('/') path = unzip_to_dir for word in words[:-1]: drive, word = os.path.splitdrive(word) head, word = os.path.split(word) if word in (os.curdir, os.pardir, ''): continue path = os.path.join(path, word) zf.extract(member, unzip_to_dir) if common_subdir: try: if os.path.exists(dest_dir): shutil.rmtree(dest_dir) except: pass shutil.copytree(os.path.join(unzip_to_dir, common_subdir), dest_dir) try: shutil.rmtree(unzip_to_dir) except: pass except zipfile.BadZipfile, e: print "Unzipping file '" + source_filename + "' failed due to reason: " + str(e) + "! Removing the corrupted zip file." rmfile(source_filename) return False except Exception, e: print "Unzipping file '" + source_filename + "' failed due to reason: " + str(e) return False return True # This function interprets whether the given string looks like a path to a directory instead of a file, without looking at the actual filesystem. # 'a/b/c' points to directory, so does 'a/b/c/', but 'a/b/c.x' is parsed as a filename def path_points_to_directory(path): if path == '.': return True last_slash = max(path.rfind('/'), path.rfind('\\')) last_dot = path.rfind('.') no_suffix = last_dot < last_slash or last_dot == -1 if no_suffix: return True suffix = path[last_dot:] if suffix == '.exe' or suffix == '.zip': # Very simple logic for the only file suffixes used by emsdk downloader. Other suffixes, like 'clang-3.2' are treated as dirs. return False else: return True # On success, returns the filename on the disk pointing to the destination file that was produced # On failure, returns None. def download_file(url, dstpath, download_even_if_exists=False): file_name = url.split('/')[-1] if path_points_to_directory(dstpath): file_name = os.path.join(dstpath, file_name) else: file_name = dstpath # Treat all relative destination paths as relative to the SDK root directory, not the current working directory. file_name = sdk_path(file_name) if os.path.exists(file_name) and not download_even_if_exists: print "File '" + file_name + "' already downloaded, skipping." return file_name try: u = urllib2.urlopen(url) mkdir_p(os.path.dirname(file_name)) with open(file_name, 'wb') as f: meta = u.info() file_size = int(meta.getheaders("Content-Length")[0]) print "Downloading: %s from %s Bytes: %s" % (file_name, url, file_size) file_size_dl = 0 block_sz = 8192 while True: buffer = u.read(block_sz) if not buffer: break file_size_dl += len(buffer) f.write(buffer) status = r"%10d [%3.2f%%]" % (file_size_dl, file_size_dl * 100. / file_size) status = status + chr(8)*(len(status)+1) print status, except urllib2.HTTPError, e: print "HTTP error with URL '" + url + "': " + str(e) rmfile(file_name) return None except: print "Error downloading URL '" + url + "'!" rmfile(file_name) return None return file_name def download_text_file(url, dstpath, download_even_if_exists=False): filename = download_file(url, dstpath, download_even_if_exists) fix_lineendings(os.path.join(emsdk_path(), filename)) def run_get_output(cmd, cwd=None): process = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, env=os.environ.copy()) (stdout, stderr) = process.communicate() return (process.returncode, stdout, stderr) warnonce_git_not_found = False def GIT(): global warnonce_git_not_found git = 'git/1.8.3/bin/git.exe' try: (ret, stdout, stderr) = run_get_output([git, '--version']) if ret == 0: return git except: pass git = 'git' try: (ret, stdout, stderr) = run_get_output([git, '--version']) if ret == 0: return git except: pass if not warnonce_git_not_found: if WINDOWS: print "ERROR: git executable was not found. Either install git manually and add it to PATH, or install the git tool via 'emsdk install git-1.8.3'" else: print "ERROR: git executable was not found. Please install git for this operation! This can be done from http://git-scm.com/ , or by installing XCode and then the XCode Command Line Tools (see http://stackoverflow.com/questions/9329243/xcode-4-4-command-line-tools )" warnonce_git_not_found = True sys.exit(1) return 'git' def git_repo_version(repo_path): (returncode, stdout, stderr) = run_get_output([GIT(), 'log', '-n', '1', '--pretty="%aD %H"'], cwd=repo_path) if returncode == 0: return stdout.strip() else: return "" def git_clone(url, dstpath): if os.path.isdir(os.path.join(dstpath, '.git')): print "Repository '" + url + "' already cloned to directory '" + dstpath + "', skipping." return True mkdir_p(dstpath) return run([GIT(), 'clone', url, dstpath]) == 0 def git_checkout_and_pull(repo_path, branch): run([GIT(), 'fetch', 'origin'], repo_path) try: print "Fetching latest changes to the branch '" + branch + "' for '" + repo_path + "'..." run([GIT(), 'fetch', 'origin'], repo_path) # run([GIT, 'checkout', '-b', branch, '--track', 'origin/'+branch], repo_path) run([GIT(), 'checkout', '--quiet', branch], repo_path) # this line assumes that the user has not gone and manually messed with the repo and added new remotes to ambiguate the checkout. run([GIT(), 'merge', '--ff-only', 'origin/'+branch], repo_path) # this line assumes that the user has not gone and made local changes to the repo except: print 'git operation failed!' return False print "Successfully updated and checked out branch '" + branch + "' on repository '" + repo_path + "'" print "Current repository version: " + git_repo_version(repo_path) return True def git_clone_checkout_and_pull(url, dstpath, branch): success = git_clone(url, dstpath) if not success: return False success = git_checkout_and_pull(dstpath, branch) return success # The root directory of the build. def fastcomp_build_dir(tool): build_dir = 'build_' + tool.git_branch build_with_vs2010 = True if WINDOWS: if build_with_vs2010: build_dir += '_vs2010' else: build_dir += '_mingw' if tool.bitness == 32: build_dir += '_32' else: build_dir += '_64' return build_dir # The directory where the binaries are produced. def fastcomp_build_bin_dir(tool): build_dir = fastcomp_build_dir(tool) build_with_vs2010 = True if WINDOWS and build_with_vs2010: return os.path.join(build_dir, 'bin\\RelWithDebInfo') else: return os.path.join(build_dir, 'bin') def build_fastcomp_tool(tool): fastcomp_root = tool.installation_path() fastcomp_src_root = os.path.join(fastcomp_root, 'src') git_clone_checkout_and_pull(tool.url, fastcomp_src_root, tool.git_branch) clang_root = os.path.join(fastcomp_src_root, 'tools/clang') git_clone_checkout_and_pull(tool.clang_url, clang_root, tool.git_branch) build_dir = fastcomp_build_dir(tool) build_with_vs2010 = True if WINDOWS: if build_with_vs2010: if tool.bitness == 64: generator = 'Visual Studio 10 Win64' else: generator = 'Visual Studio 10' make = ['C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\MSBuild.exe', '/t:Build', '/p:Configuration=RelWithDebInfo', '/nologo', '/verbosity:minimal', 'LLVM.sln'] else: generator = 'MinGW Makefiles' make = ['mingw32-make'] else: generator = 'Unix Makefiles' make = ['make'] build_root = os.path.join(fastcomp_root, build_dir) # Configure if not os.path.isdir(build_root): os.mkdir(build_root) # Create build output directory if it doesn't yet exist. try: cmdline = ['cmake', '-G', generator, '-DCMAKE_BUILD_TYPE=RelWithDebInfo', '-DLLVM_TARGETS_TO_BUILD=X86;JSBackend', '-DLLVM_INCLUDE_EXAMPLES=OFF', '-DLLVM_INCLUDE_TESTS=OFF', '-DCLANG_INCLUDE_EXAMPLES=OFF', '-DCLANG_INCLUDE_TESTS=OFF', fastcomp_src_root] print 'Running CMake: ' + str(cmdline) ret = subprocess.check_call(cmdline, cwd=build_root) if ret != 0: print >> sys.stderr, 'CMake invocation failed with exit code ' + ret + '!' print >> sys.stderr, 'Working directory: ' + build_root return False except Exception, e: print >> sys.stderr, 'CMake invocation failed due to exception!' print >> sys.stderr, 'Working directory: ' + build_root print >> sys.stderr, str(e) return False # Build try: print 'Running build: ' + str(make) ret = subprocess.check_call(make, cwd=build_root) if ret != 0: print >> sys.stderr, 'Build failed with exit code ' + ret + '!' print >> sys.stderr, 'Working directory: ' + build_root return False except Exception, e: print >> sys.stderr, 'Build failed due to exception!' print >> sys.stderr, 'Working directory: ' + build_root print >> sys.stderr, str(e) return False return True def download_and_unzip(zipfile, dest_dir, download_even_if_exists=False): if not download_even_if_exists and num_files_in_directory(dest_dir) > 0: print "The contents of file '" + zipfile + "' already exist in destination '" + dest_dir + "', skipping." return True dst_file = download_file(urlparse.urljoin(emsdk_packages_url, zipfile), 'zips/', download_even_if_exists) if not dst_file: return False if zipfile.endswith('.zip'): return unzip(dst_file, dest_dir, unpack_even_if_exists=download_even_if_exists) else: return untargz(dst_file, dest_dir, unpack_even_if_exists=download_even_if_exists) def to_unix_path(p): return p.replace('\\', '/') def to_native_path(p): if WINDOWS: return to_unix_path(p).replace('/', '\\') else: return to_unix_path(p) # Finds and returns a list of the directories that need to be added to PATH for the given set of tools. def get_required_path(active_tools): path_add = [to_native_path(emsdk_path())] for tool in active_tools: if hasattr(tool, 'activated_path'): path_add += [to_native_path(tool.expand_vars(tool.activated_path))] return path_add # Returns the absolute path to the file '.emscripten' for the current user on this system. def dot_emscripten_path(): return os.path.expanduser("~/.emscripten") dot_emscripten = {} def parse_key_value(line): if not line: return ('', '') eq = line.find('=') if eq != -1: key = line[0:eq].strip() value = line[eq+1:].strip() return (key, value) else: return (key, '') def load_dot_emscripten(): global dot_emscripten dot_emscripten = {} lines = [] try: lines = open(dot_emscripten_path(), "r").read().split('\n') except: pass for line in lines: try: (key, value) = parse_key_value(line) if value != '': dot_emscripten[key] = value # print "Got '" + key + "' = '" + value + "'" except: pass def generate_dot_emscripten(active_tools): temp_dir = tempfile.gettempdir().replace('\\', '/') cfg = 'import os\n' cfg += "SPIDERMONKEY_ENGINE = ''\n" for tool in active_tools: tool_cfg = tool.activated_config() if tool_cfg: cfg += tool_cfg + '\n' cfg += '''V8_ENGINE = '' TEMP_DIR = ''' + "'" + temp_dir + "'" + ''' COMPILER_ENGINE = NODE_JS JS_ENGINES = [NODE_JS] ''' with open(dot_emscripten_path(), "w") as text_file: text_file.write(cfg) # Clear old cached emscripten content. try: shutil.rmtree(os.path.expanduser("~/.emscripten_cache"), ignore_errors=True) os.remove(os.path.expanduser("~/.emscripten_sanity")) os.remove(os.path.expanduser("~/.emscripten_cache__last_clear")) except: pass print "The Emscripten configuration file " + os.path.normpath(dot_emscripten_path()) + " has been rewritten with the following contents:" print '' print cfg.strip() print '' path_add = get_required_path(active_tools) if not WINDOWS: print "To conveniently access the selected set of tools from the command line, consider adding the following directories to PATH, or call '" + ('' if WINDOWS else 'source ') + os.path.relpath(sdk_path('emsdk_env')) + "' to do this for you." print '' print ' ' + ENVPATH_SEPARATOR.join(path_add) def find_msbuild_dir(): if 'ProgramFiles' in os.environ and os.environ['ProgramFiles']: program_files = os.environ['ProgramFiles'] else: program_files = 'C:/Program Files' if 'ProgramFiles(x86)' in os.environ and os.environ['ProgramFiles(x86)']: program_files_x86 = os.environ['ProgramFiles(x86)'] else: program_files_x86 = 'C:/Program Files (x86)' MSBUILDX86_DIR = os.path.join(program_files_x86, "MSBuild/Microsoft.Cpp/v4.0/Platforms") MSBUILD_DIR = os.path.join(program_files, "MSBuild/Microsoft.Cpp/v4.0/Platforms") if os.path.exists(MSBUILDX86_DIR): return MSBUILDX86_DIR elif os.path.exists(MSBUILD_DIR): return MSBUILD_DIR else: return '' # No MSbuild installed. def get_installed_vstool_version(installed_path): try: return open(installed_path + "/version.txt", "r").read() except: return None class Tool: def __init__(self, data): # Convert the dictionary representation of the tool in 'data' to members of this class for convenience. for key in data: setattr(self, key, data[key]) def __str__(self): s = self.id + '-' + self.version if hasattr(self, 'bitness'): s += '-' + str(self.bitness) + 'bit' return s def expand_vars(self, str): if WINDOWS and '%MSBuildPlatformsDir%' in str: str = str.replace('%MSBuildPlatformsDir%', find_msbuild_dir()) if '%installation_dir%' in str: str = str.replace('%installation_dir%', sdk_path(self.installation_dir())) str = str.replace('%.exe%', '.exe' if WINDOWS else '') if '%fastcomp_build_dir%' in str: str = str.replace('%fastcomp_build_dir%', fastcomp_build_dir(self)) if '%fastcomp_build_bin_dir%' in str: str = str.replace('%fastcomp_build_bin_dir%', fastcomp_build_bin_dir(self)) return str # Specifies the target path where this tool will be installed to. This could either be a directory or a filename (e.g. in case of node.js) def installation_path(self): if WINDOWS and hasattr(self, 'windows_install_path'): pth = self.expand_vars(self.windows_install_path) return sdk_path(pth) if hasattr(self, 'install_path'): pth = self.expand_vars(self.install_path) return sdk_path(pth) p = self.version if hasattr(self, 'bitness'): p += '_' + str(self.bitness) + 'bit' return sdk_path(os.path.join(self.id, p)) # Specifies the target directory this tool will be installed to. def installation_dir(self): dir = self.installation_path() if path_points_to_directory(dir): return dir else: return os.path.dirname(dir) # Returns the configuration item that needs to be added to .emscripten to make this Tool active for the current user. def activated_config(self): if hasattr(self, 'activated_cfg'): return to_unix_path(self.expand_vars(self.activated_cfg)) else: return '' def activated_environment(self): if hasattr(self, 'activated_env'): return self.expand_vars(self.activated_env) else: return '' def compatible_with_this_os(self): if hasattr(self, 'os'): if self.os == 'all': return True if (WINDOWS and 'win' in self.os) or (LINUX and 'linux' in self.os) or (OSX and 'osx' in self.os): return True else: return False else: if not hasattr(self, 'osx_url') and not hasattr(self, 'windows_url') and not hasattr(self, 'unix_url'): return True if OSX and hasattr(self, 'osx_url'): return True if WINDOWS and (hasattr(self, 'windows_url') or hasattr(self, 'windows_install_path')): return True if (LINUX or OSX) and hasattr(self, 'unix_url'): return True return hasattr(self, 'url') def is_installed(self): # If this tool/sdk depends on other tools, require that all dependencies are installed for this tool to count as being installed. if hasattr(self, 'uses'): for tool_name in self.uses: tool = find_tool(tool_name) if tool == None: print "Manifest error: No tool by name '" + tool_name + "' found! This may indicate an internal SDK error!" return False if not tool.is_installed(): return False if self.download_url() != None: content_exists = os.path.exists(self.installation_path()) and (os.path.isfile(self.installation_path()) or num_files_in_directory(self.installation_path()) > 0) if self.id == 'vs-tool': # vs-tool is a special tool since all versions must be installed to the same dir, so dir name will not differentiate the version. return content_exists and get_installed_vstool_version(self.installation_path()) == self.version else: return content_exists else: return True # This tool does not contain downloadable elements, so it is installed by default. def is_active(self): if not self.is_installed(): return False if self.id == 'vs-tool': return True # vs-tool is a special tool since all versions must be installed to the same dir, which means that if this tool is installed, it is also active. # All dependencies of this tool must be active as well. deps = self.dependencies() for tool in deps: if not tool.is_active(): return False activated_cfg = self.activated_config() if activated_cfg == '': return len(deps) > 0 (key, value) = parse_key_value(activated_cfg) # print 'activated cfg ' + key + ', value: ' + value # if dot_emscripten.has_key(key): # print 'dot_emscripten ' + dot_emscripten[key] if dot_emscripten.has_key(key) and dot_emscripten[key] == value: return True return False # Returns true if the system environment variables requires by this tool are currently active. def is_env_active(self): if hasattr(self, 'activated_env'): (key, value) = parse_key_value(self.activated_environment()) if not key in os.environ or to_unix_path(os.environ[key]) != to_unix_path(value): return False # if WINDOWS: # env_var = win_get_active_environment_variable(key) # if env_var != value: # return False if hasattr(self, 'activated_path'): path = self.expand_vars(self.activated_path).replace('\\', '/') path = path.split(ENVPATH_SEPARATOR) for p in path: path_items = os.environ['PATH'].replace('\\', '/').split(ENVPATH_SEPARATOR) if not normalized_contains(path_items, p): return False return True def win_activate_env_vars(self, permanently_activate): if WINDOWS and hasattr(self, 'activated_env'): (key, value) = parse_key_value(self.activated_environment()) if permanently_activate: win_delete_environment_variable(key, False) # If there is an env var for the LOCAL USER with same name, it will hide the system var, so must remove that first. win_set_environment_variable(key, value, permanently_activate) # If this tool can be installed on this system, this function returns True. # Otherwise, this function returns a string that describes the reason why this tool is not available. def can_be_installed(self): if self.id == 'vs-tool': msbuild_dir = find_msbuild_dir() if len(msbuild_dir) > 0: return True else: return "Visual Studio was not found!" else: return True def download_url(self): if WINDOWS and hasattr(self, 'windows_url'): return self.windows_url elif OSX and hasattr(self, 'osx_url'): return self.osx_url elif (OSX or LINUX) and hasattr(self, 'unix_url'): return self.unix_url elif hasattr(self, 'url'): return self.url else: return None def install(self): if self.can_be_installed() != True: print "The tool '" + str(self) + "' is not available due to the reason: " + self.can_be_installed() return False print "Installing '" + str(self) + "'.." if self.id == 'sdk': for tool_name in self.uses: tool = find_tool(tool_name) if tool == None: print "Manifest error: No tool by name '" + tool_name + "' found! This may indicate an internal SDK error!" success = tool.install() if not success: return False print "Done." return True else: url = self.download_url() if hasattr(self, 'custom_install_script') and self.custom_install_script == 'build_fastcomp': success = build_fastcomp_tool(self) elif hasattr(self, 'git_branch'): success = git_clone_checkout_and_pull(url, self.installation_path(), self.git_branch) elif url.endswith('zip') or url.endswith('.tar') or url.endswith('.gz'): download_even_if_exists = (self.id == 'vs-tool') success = download_and_unzip(url, self.installation_path(), download_even_if_exists) else: dst_file = download_file(urlparse.urljoin(emsdk_packages_url, self.download_url()), self.installation_path()) if dst_file: success = True else: success = False if not success: print "Installation failed!" return False print "Done." # Sanity check that the installation succeeded, and if so, remove unneeded leftover installation files. if self.is_installed(): self.cleanup_temp_install_files() else: print "Warning: The installation of '" + str(self) + "' seems to have failed, but no error was detected. Either something went wrong with the installation, or this may indicate an internal emsdk error." return True def cleanup_temp_install_files(self): url = self.download_url() if url.endswith('zip') or url.endswith('.tar') or url.endswith('.gz'): file_name = url.split('/')[-1] tempzipfile = os.path.join('zips/', file_name) rmfile(tempzipfile) def uninstall(self): if not self.is_installed(): print "Tool '" + str(self) + "' was not installed. No need to uninstall." return print "Uninstalling tool '" + str(self) + "'.." try: print "Deleting path '" + self.installation_path() + "'" shutil.rmtree(self.installation_path(), ignore_errors=True) os.remove(self.installation_path()) except: pass print "Done." def dependencies(self): if not hasattr(self, 'uses'): return [] deps = [] for tool_name in self.uses: tool = find_tool(tool_name) if tool: deps += [tool] return deps def recursive_dependencies(self): if not hasattr(self, 'uses'): return [] deps = [] for tool_name in self.uses: tool = find_tool(tool_name) if tool: deps += [tool] deps += tool.recursive_dependencies() return deps # A global registry of all known Emscripten SDK tools available in the SDK manifest. tools = [] # A global registry of all known SDK toolsets. sdks = [] # N.B. In both tools and sdks list above, we take the convention that the newest items are at the back of the list (ascending chronological order) def find_tool(name): for tool in tools: if str(tool) == name: return tool return None def find_sdk(name): for sdk in sdks: if str(sdk) == name: return sdk return None def is_os_64bit(): # http://stackoverflow.com/questions/2208828/detect-64bit-os-windows-in-python return platform.machine().endswith('64') def find_latest_32bit_sdk(): for sdk in reversed(sdks): # Newest SDK is always at the end of the list. if not hasattr(sdk, 'bitness') or sdk.bitness == 32: # If no 'bitness' field, it means the SDK is universal. return sdk return None def find_latest_64bit_sdk(): for sdk in reversed(sdks): # Newest SDK is always at the end of the list. if not hasattr(sdk, 'bitness') or sdk.bitness == 64: # If no 'bitness' field, it means the SDK is universal. return sdk return None def find_latest_sdk(): if is_os_64bit(): return find_latest_64bit_sdk() else: return find_latest_32bit_sdk() # Finds the best-matching python tool for use. def find_used_python(): for t in reversed(tools): # Find newest tool first - those are always at the end of the list. if t.id == 'python' and t.is_installed() and t.is_active() and t.is_env_active(): return t for t in reversed(tools): if t.id == 'python' and t.is_installed() and t.is_active(): return t for t in reversed(tools): if t.id == 'python' and t.is_installed(): return t return None def update_emsdk(): if WINDOWS: download_and_unzip(urlparse.urljoin(emsdk_packages_url, 'emsdk_windows_update.zip'), emsdk_path(), download_even_if_exists=True) rmfile('zips/emsdk_windows_update.zip') elif OSX: download_and_unzip(urlparse.urljoin(emsdk_packages_url, 'emsdk_osx_update.tar.gz'), emsdk_path(), download_even_if_exists=True) rmfile('zips/emsdk_windows_update.tar.gz') else: print 'Unsupported OS, cannot update!' def load_sdk_manifest(): global tools try: manifest = json.loads(open(sdk_path("emsdk_manifest.json"), "r").read()) except Exception, e: print 'Error parsing emsdk_manifest.json!' print str(e) return for tool in manifest['tools']: t = Tool(tool) if t.compatible_with_this_os(): if not hasattr(t, 'is_old'): t.is_old = False tools.append(t) for sdk_str in manifest['sdks']: sdk = Tool(sdk_str) sdk.id = "sdk" if sdk.compatible_with_this_os(): if not hasattr(sdk, 'is_old'): sdk.is_old = False sdks.append(sdk) # Tests if the two given tools can be active at the same time. # Currently only a simple check for name for same tool with different versions, # possibly adds more logic in the future. def can_simultaneously_activate(tool1, tool2): return tool1.id != tool2.id def remove_nonexisting_tools(tool_list, log_errors=True): i = 0 while i < len(tool_list): tool = tool_list[i] if not tool.is_installed(): if log_errors: print "The SDK/tool '" + str(tool) + "' cannot be activated since it is not installed! Skipping this tool..." tool_list.pop(i) continue i += 1 return tool_list # Expands dependencies for each tool, and removes ones that don't exist. def process_tool_list(tools_to_activate, log_errors=True): i = 0 # Gather dependencies for each tool while i < len(tools_to_activate): tool = tools_to_activate[i] deps = tool.recursive_dependencies() tools_to_activate = tools_to_activate[:i] + deps + tools_to_activate[i:] i += len(deps)+1 tools_to_activate = remove_nonexisting_tools(tools_to_activate, log_errors=log_errors) # Remove conflicting tools i = 0 while i < len(tools_to_activate): j = 0 while j < i: secondary_tool = tools_to_activate[j] primary_tool = tools_to_activate[i] if not can_simultaneously_activate(primary_tool, secondary_tool): tools_to_activate.pop(j) j -= 1 i -= 1 j += 1 i += 1 return tools_to_activate # Reconfigure .emscripten to choose the currently activated toolset, set PATH and other environment variables. # Returns the full list of deduced tools that are now active. def set_active_tools(tools_to_activate, permanently_activate): tools_to_activate = process_tool_list(tools_to_activate, log_errors=True) generate_dot_emscripten(tools_to_activate) # Construct a .bat script that will be invoked to set env. vars and PATH if WINDOWS: env_string = construct_env(tools_to_activate, False) open('emsdk_set_env.bat', 'w').write(env_string) # Apply environment variables to global all users section. if WINDOWS and permanently_activate: # Individual env. vars for tool in tools_to_activate: tool.win_activate_env_vars(permanently_activate=True) # PATH variable newpath, added_items = adjusted_path(tools_to_activate) if newpath != os.environ['PATH']: # Are there any actual changes? if len(newpath) < 1024: win_set_environment_variable('PATH', newpath, system=True) else: print >> sys.stderr, 'ERROR! The new PATH is more than 1024 characters long! A PATH this long cannot be set via command line: please add the PATHs specified above to system PATH manually via Control Panel.' return tools_to_activate def currently_active_sdk(): for sdk in reversed(sdks): if sdk.is_active(): return sdk return None def currently_active_tools(): active_tools = [] for tool in tools: if tool.is_active(): active_tools += [tool] return active_tools # http://stackoverflow.com/questions/480214/how-do-you-remove-duplicates-from-a-list-in-python-whilst-preserving-order def unique_items(seq): seen = set() seen_add = seen.add return [ x for x in seq if x not in seen and not seen_add(x)] # Tests if a path is contained in the given list, but with separators normalized. def normalized_contains(lst, elem): elem = to_unix_path(elem) for e in lst: if elem == to_unix_path(e): return True return False # Looks at the current PATH and adds and removes entries so that the PATH reflects # the set of given active tools. def adjusted_path(tools_to_activate, log_additions=False): # These directories should be added to PATH path_add = get_required_path(tools_to_activate) # These already exist. existing_path = os.environ['PATH'].split(ENVPATH_SEPARATOR) emsdk_root_path = to_unix_path(emsdk_path()) existing_emsdk_tools = [item for item in existing_path if to_unix_path(item).startswith(emsdk_root_path)] new_emsdk_tools = [item for item in path_add if not normalized_contains(existing_emsdk_tools, item)] # Existing non-emsdk tools existing_path = [item for item in existing_path if not to_unix_path(item).startswith(emsdk_root_path)] new_path = [item for item in path_add if not normalized_contains(existing_path, item)] return (ENVPATH_SEPARATOR.join(unique_items(existing_path + new_path)), new_emsdk_tools) def construct_env_windows(tools_to_activate, permanent): env_string = '' newpath, added_path = adjusted_path(tools_to_activate) # Dont permanently add to PATH, since this will break the whole system if there are more than 1024 chars in PATH. # (SETX truncates to set only 1024 chars) # if permanent: # print 'SETX PATH "' + newpath + '"' # else: if os.environ['PATH'] != newpath: # Don't bother setting the path if there are no changes. env_string += 'SET PATH=' + newpath + '\n' if len(added_path) > 0: print 'Adding directories to PATH:' for item in added_path: print 'PATH += ' + item print '' env_vars_to_add = [] for tool in tools_to_activate: if hasattr(tool, 'activated_env'): (key, value) = parse_key_value(tool.activated_env) value = to_native_path(tool.expand_vars(value)) if not key in os.environ or to_unix_path(os.environ[key]) != to_unix_path(value): # Don't set env. vars which are already set to the correct value. env_vars_to_add += [(key, value)] if len(env_vars_to_add) > 0: print 'Setting environment variables:' for key, value in env_vars_to_add: if permanent: env_string += 'SETX ' + key + ' "' + value + '"\n' else: env_string += 'SET ' + key + '=' + value + '\n' print key + ' = ' + value print '' return env_string def construct_env_unix(tools_to_activate): env_string = '' newpath, added_path = adjusted_path(tools_to_activate) if os.environ['PATH'] != newpath: # Don't bother setting the path if there are no changes. env_string += 'export PATH=' + newpath + '\n' if len(added_path) > 0: print 'Adding directories to PATH:' for item in added_path: print 'PATH += ' + item print '' env_vars_to_add = [] for tool in tools_to_activate: if hasattr(tool, 'activated_env'): (key, value) = parse_key_value(tool.activated_env) value = to_native_path(tool.expand_vars(value)) if not key in os.environ or to_unix_path(os.environ[key]) != to_unix_path(value): # Don't set env. vars which are already set to the correct value. env_vars_to_add += [(key, value)] if len(env_vars_to_add) > 0: print 'Setting environment variables:' for key, value in env_vars_to_add: env_string += 'export ' + key + '=' + value + '\n' print key + ' = ' + value print '' return env_string def construct_env(tools_to_activate, permanent): if WINDOWS: return construct_env_windows(tools_to_activate, permanent) else: return construct_env_unix(tools_to_activate) def main(): load_dot_emscripten() load_sdk_manifest() usage_str = "usage: %prog --param1 .. --paramn " # parser = optparse.OptionParser(usage=usage_str) # parser.add_option('update', dest='update', action='store_true', default=False, help='Downloads and installs the latest Emscripten SDK components.') # (options, args) = parser.parse_args(sys.argv) if len(sys.argv) <= 1 or sys.argv[1] == 'help': if len(sys.argv) <= 1: print ' emsdk: No command given. Please call one of the following:' else: print ' emsdk: Available commands:' print ''' emsdk list [--old] - Lists all available SDKs and tools and their current installation status. With the --old parameter, also historical versions are shown. emsdk update - Fetches a list of updates from the net (but does not install them) emsdk install - Downloads and installs the given tool or SDK. emsdk uninstall - Removes the given tool or SDK from disk. emsdk activate - Permanently activates the given tool or SDK as the default tool in the system environment.''' if WINDOWS: print ''' emcmdprompt.bat - Spawns a new command prompt window with the Emscripten environment active.''' return 1 cmd = sys.argv[1] if (cmd == 'update' or cmd == 'install' or cmd == 'activate') and len(sys.argv) >= 3: if sys.argv[2] == 'latest': sys.argv[2] = str(find_latest_sdk()) elif sys.argv[2] == 'latest-32bit': sys.argv[2] = str(find_latest_32bit_sdk()) elif sys.argv[2] == 'latest-64bit': sys.argv[2] = str(find_latest_64bit_sdk()) if cmd == 'list': print '' show_old = len(sys.argv) >= 3 and sys.argv[2] == '--old' has_partially_active_tools = False if len(tools) > 0: print 'The following individual tools exist:' for tool in tools: if tool.is_old and not show_old: continue if tool.can_be_installed() == True: installed = '\tINSTALLED' if tool.is_installed() else '' else: installed = '\tNot available: ' + tool.can_be_installed() tool_is_active = tool.is_active() tool_is_env_active = tool_is_active and tool.is_env_active() if tool_is_env_active: active = ' * ' elif tool_is_active: active = '(*)' has_partially_active_tools = True else: active = ' ' print ' ' + active + ' {0: <25}'.format(str(tool)) + installed print '' else: print "There are no tools available. Run 'emsdk update' to fetch the latest set of tools." print '' if len(sdks) > 0: print 'The following Emscripten SDK versions are available:' for sdk in sdks: if sdk.is_old and not show_old: continue installed = '\tINSTALLED' if sdk.is_installed() else '' active = '*' if sdk.is_active() else ' ' print ' ' + active + ' {0: <25}'.format(str(sdk)) + installed print '' print 'Items marked with * are activated for the current user.' if has_partially_active_tools: env_cmd = 'emsdk_env.bat' if WINDOWS else 'source ./emsdk_env.sh' print 'Items marked with (*) are selected for use, but your current shell environment is not configured to use them. Type "' + env_cmd + '" to set up your current shell to use them, or call "emsdk activate --global " to permanently activate them.' if not show_old: print '' print "To access the historical archived versions, type 'emsdk list --old'" return 0 elif cmd == 'construct_env': tools_to_activate = currently_active_tools() tools_to_activate = process_tool_list(tools_to_activate, log_errors=True) env_string = construct_env(tools_to_activate, len(sys.argv) >= 3 and 'perm' in sys.argv[2]) if WINDOWS: open('emsdk_set_env.bat', 'w').write(env_string) else: open('emsdk_set_env.sh', 'w').write(env_string) os.chmod('emsdk_set_env.sh', 0755) return 0 elif cmd == 'update': update_emsdk() return 0 elif cmd == 'activate': permanently_activate = '--global' in sys.argv if permanently_activate: print 'Registering active Emscripten environment globally for all users.' print '' sys.argv = filter(lambda x: not x.startswith('--'), sys.argv) # If called without arguments, activate latest versions of all tools if len(sys.argv) <= 2: tools_to_activate = list(tools) tools_to_activate = remove_nonexisting_tools(tools_to_activate, log_errors=False) else: tools_to_activate = currently_active_tools() for i in range(2, len(sys.argv)): tool = find_tool(sys.argv[i]) if tool == None: tool = find_sdk(sys.argv[i]) if tool == None: print "Error: No tool or SDK found by name '" + sys.argv[i] + "'." return 1 tools_to_activate += [tool] tools_to_activate = set_active_tools(tools_to_activate, permanently_activate=permanently_activate) return 0 elif cmd == 'install': if len(sys.argv) <= 2: print "Missing parameter. Type 'emsdk install ' to install a tool or an SDK. Type 'emsdk list' to obtain a list of available tools. Type 'emsdk install latest' to automatically install the newest version of the SDK." return 1 tool = find_tool(sys.argv[2]) if tool == None: tool = find_sdk(sys.argv[2]) if tool == None: print "Error: No tool or SDK found by name '" + sys.argv[2] + "'." return 1 tool.install() elif cmd == 'uninstall': if len(sys.argv) <= 2: print "Syntax error. Call 'emsdk uninstall '. Call 'emsdk list' to obtain a list of available tools." return 1 tool = find_tool(sys.argv[2]) if tool == None: print "Error: Tool by name '" + sys.argv[2] + "' was not found." return 1 tool.uninstall() else: print "Unknown command '" + cmd + "' given! Type 'emsdk help' to get a list of commands." return 1 if __name__ == '__main__': sys.exit(main())