#!/usr/bin/env python2.7 # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # This script is used to create and manipulate archives containing # files necessary to build Firefox on Windows (referred to as the # "Windows toolchain"). # # When updating behavior of this script, remember to update the docs # in ``build/docs/toolchains.rst``. from __future__ import absolute_import, unicode_literals import hashlib import os import sys from mozpack.files import ( FileFinder, ) from mozpack.mozjar import ( JarWriter, ) import mozpack.path as mozpath # mozpack.match patterns for files under "Microsoft Visual Studio 14.0". VS_PATTERNS = [ { 'pattern': 'DIA SDK/bin/**', # Various tools don't like spaces in filenames. So remove it. 'rewrite': [('DIA SDK/', 'DIASDK/')], 'ignore': ( 'DIA SDK/bin/arm/**', ), }, { 'pattern': 'DIA SDK/idl/**', 'rewrite': [('DIA SDK/', 'DIASDK/')], }, { 'pattern': 'DIA SDK/include/**', 'rewrite': [('DIA SDK/', 'DIASDK/')], }, { 'pattern': 'DIA SDK/lib/**', 'rewrite': [('DIA SDK/', 'DIASDK/')], 'ignore': ( 'DIA SDK/lib/arm/**', ), }, # ATL is needed by Breakpad. { 'pattern': 'VC/atlmfc/include/**', }, { 'pattern': 'VC/atlmfc/lib/atls.*', }, { 'pattern': 'VC/atlmfc/lib/amd64/atls.*', }, { 'pattern': 'VC/bin/**', # We only care about compiling on amd64 for amd64 or x86 targets. 'ignore': ( 'VC/bin/amd64_arm/**', 'VC/bin/arm/**', 'VC/bin/x86_arm/**', 'VC/bin/x86_amd64/**', ), }, { 'pattern': 'VC/include/**', }, { 'pattern': 'VC/lib/**', 'ignore': ( 'VC/lib/arm/**', 'VC/lib/onecore/**', 'VC/lib/store/**', ), }, { 'pattern': 'VC/redist/x64/Microsoft.VC140.CRT/**', }, { 'pattern': 'VC/redist/x86/Microsoft.VC140.CRT/**', }, ] SDK_RELEASE = '10.0.10586.0' # Files from the Windows 10 SDK to install. SDK_PATTERNS = [ { 'pattern': 'bin/x64/**', }, { 'pattern': 'Include/%s/**' % SDK_RELEASE, }, { 'pattern': 'Lib/%s/ucrt/x64/**' % SDK_RELEASE, }, { 'pattern': 'Lib/%s/ucrt/x86/**' % SDK_RELEASE, }, { 'pattern': 'Lib/%s/um/x64/**' % SDK_RELEASE, }, { 'pattern': 'Lib/%s/um/x86/**' % SDK_RELEASE, }, { 'pattern': 'Redist/D3D/**', }, { 'pattern': 'Redist/ucrt/DLLs/x64/**', }, { 'pattern': 'Redist/ucrt/DLLs/x86/**', }, ] def find_vs_paths(): """Resolve source locations of files. Returns a 2-tuple of (Visual Studio Path, SDK Path). """ pf = os.environ.get('ProgramFiles(x86)') if not pf: raise Exception('No "ProgramFiles(x86)" environment variable. ' 'Not running on 64-bit Windows?') vs_path = os.path.join(pf, 'Microsoft Visual Studio 14.0') if not os.path.exists(vs_path): raise Exception('%s does not exist; Visual Studio 2015 not installed?' % vs_path) sdk_path = os.path.join(pf, 'Windows Kits', '10') if not os.path.exists(sdk_path): raise Exception('%s does not exist; Windows 10 SDK not installed?' % sdk_path) return vs_path, sdk_path def resolve_files(): """Resolve the files that constitute a standalone toolchain. This is a generator of (dest path, file) where the destination path is relative and the file instance is a BaseFile from mozpack. """ vs_path, sdk_path = find_vs_paths() for entry in VS_PATTERNS: finder = FileFinder(vs_path, find_executables=False, ignore=entry.get('ignore', [])) for p, f in finder.find(entry['pattern']): assert p.startswith(('VC/', 'DIA SDK/')) for source, dest in entry.get('rewrite', []): p = p.replace(source, dest) yield p.encode('utf-8'), f for entry in SDK_PATTERNS: finder = FileFinder(sdk_path, find_executables=False, ignore=entry.get('ignore', [])) for p, f in finder.find(entry['pattern']): # We remove the SDK version from the path so we don't have # to update other configs when we change the SDK version. p = p.replace('/%s/' % SDK_RELEASE, '/') relpath = 'SDK/%s' % p yield relpath.encode('utf-8'), f def resolve_files_and_hash(manifest): """Resolve files and hash their data. This is a generator of 3-tuples of (relpath, data, mode). As data is read, the manifest is populated with metadata. Keys are set to the relative file path. Values are 2-tuples of (data length, sha-256). """ assert manifest == {} for p, f in resolve_files(): data = f.read() sha256 = hashlib.sha256() sha256.update(data) manifest[p] = (len(data), sha256.hexdigest()) yield p, data, f.mode def format_manifest(manifest): """Return formatted SHA-256 manifests as a byte strings.""" sha256_lines = [] for path, (length, sha256) in sorted(manifest.items()): sha256_lines.append(b'%s\t%d\t%s' % (sha256, length, path)) # Trailing newline. sha256_lines.append(b'') return b'\n'.join(sha256_lines) def write_zip(zip_path, prefix=None): """Write toolchain data to a zip file.""" if isinstance(prefix, unicode): prefix = prefix.encode('utf-8') with JarWriter(file=zip_path, optimize=False, compress=5) as zip: manifest = {} for p, data, mode in resolve_files_and_hash(manifest): print(p) if prefix: p = mozpath.join(prefix, p) zip.add(p, data, mode=mode) sha256_manifest = format_manifest(manifest) sdk_path = b'SDK_VERSION' sha256_path = b'MANIFEST.SHA256' if prefix: sdk_path = mozpath.join(prefix, sdk_path) sha256_path = mozpath.join(prefix, sha256_path) zip.add(sdk_path, SDK_RELEASE.encode('utf-8')) zip.add(sha256_path, sha256_manifest) if __name__ == '__main__': if len(sys.argv) != 3: print('usage: %s create-zip ' % sys.argv[0]) sys.exit(1) assert sys.argv[1] == 'create-zip' destzip = sys.argv[2] # TODO make prefix a CLI argument write_zip(destzip, prefix='vs2015u1') sha1 = hashlib.sha1() sha256 = hashlib.sha256() sha512 = hashlib.sha512() with open(destzip, 'rb') as fh: data = fh.read() sha1.update(data) sha256.update(data) sha512.update(data) print('Hashes of %s (size=%d)' % (destzip, len(data))) print('SHA-1: %s' % sha1.hexdigest()) print('SHA-256: %s' % sha256.hexdigest()) print('SHA-512: %s' % sha512.hexdigest())