From cb142b6df9fa3b43e6905358c880c11f6a9abb45 Mon Sep 17 00:00:00 2001 From: Doug Parker Date: Tue, 7 Jan 2020 15:19:50 -0800 Subject: [PATCH] refactor(compiler): refactor and export Bazel host creation. (#34686) Refactored Bazel host creation to export some of the relevant utility functions. google3 needs to make some modifications to the Bazel host to enable the Ivy migration. The functionality exported here is still needed in g3, but it allows g3 code to create the Bazel host, make necessary modifications, and then provide it to the external codebase here while everything is still compatible. For external users, this is a no-op. PR Close #34686 --- packages/bazel/src/ngc-wrapped/index.ts | 136 +++++++++++++----------- 1 file changed, 72 insertions(+), 64 deletions(-) diff --git a/packages/bazel/src/ngc-wrapped/index.ts b/packages/bazel/src/ngc-wrapped/index.ts index 9d05a986ed..4c49ef1ec8 100644 --- a/packages/bazel/src/ngc-wrapped/index.ts +++ b/packages/bazel/src/ngc-wrapped/index.ts @@ -7,7 +7,7 @@ */ import * as ng from '@angular/compiler-cli'; -import {BazelOptions, CachedFileLoader, CompilerHost, FileCache, FileLoader, UncachedFileLoader, constructManifest, debug, parseTsconfig, resolveNormalizedPath, runAsWorker, runWorkerLoop} from '@bazel/typescript'; +import {BazelOptions, CachedFileLoader, CompilerHost as BazelHost, FileCache, FileLoader, UncachedFileLoader, constructManifest, debug, parseTsconfig, resolveNormalizedPath, runAsWorker, runWorkerLoop} from '@bazel/typescript'; import * as fs from 'fs'; import * as path from 'path'; import * as tsickle from 'tsickle'; @@ -35,9 +35,6 @@ export function main(args) { return 0; } -/** The one FileCache instance used in this process. */ -const fileCache = new FileCache(debug); - export function runOneBuild(args: string[], inputs?: {[path: string]: string}): boolean { if (args[0] === '-p') args.shift(); // Strip leading at-signs, used to indicate a params file @@ -144,8 +141,10 @@ export function relativeToRootDirs(filePath: string, rootDirs: string[]): string return filePath; } -export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost, bazelOpts, files, - inputs, expectedOuts, gatherDiagnostics, bazelHost}: { +export function compile({ + allDepsCompiledWithBazel = true, compilerOpts, tsHost, bazelOpts, files, inputs, expectedOuts, + gatherDiagnostics, bazelHost, +}: { allDepsCompiledWithBazel?: boolean, compilerOpts: ng.CompilerOptions, tsHost: ts.CompilerHost, inputs?: {[path: string]: string}, @@ -153,41 +152,15 @@ export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost, files: string[], expectedOuts: string[], gatherDiagnostics?: (program: ng.Program) => ng.Diagnostics, - bazelHost?: CompilerHost, + bazelHost?: BazelHost, }): {diagnostics: ng.Diagnostics, program: ng.Program} { - let fileLoader: FileLoader; - - if (bazelOpts.maxCacheSizeMb !== undefined) { - const maxCacheSizeBytes = bazelOpts.maxCacheSizeMb * (1 << 20); - fileCache.setMaxCacheSize(maxCacheSizeBytes); - } else { - fileCache.resetMaxCacheSize(); - } - - if (inputs) { - fileLoader = new CachedFileLoader(fileCache); - // Resolve the inputs to absolute paths to match TypeScript internals - const resolvedInputs = new Map(); - const inputKeys = Object.keys(inputs); - for (let i = 0; i < inputKeys.length; i++) { - const key = inputKeys[i]; - resolvedInputs.set(resolveNormalizedPath(key), inputs[key]); - } - fileCache.updateCache(resolvedInputs); - } else { - fileLoader = new UncachedFileLoader(); - } - if (!bazelOpts.es5Mode) { compilerOpts.annotateForClosureCompiler = true; compilerOpts.annotationsAs = 'static fields'; } - // Detect from compilerOpts whether the entrypoint is being invoked in Ivy mode. - const isInIvyMode = compilerOpts.enableIvy === 'ngtsc'; - // Disable downleveling and Closure annotation if in Ivy mode. - if (isInIvyMode) { + if (compilerOpts.enableIvy) { compilerOpts.annotationsAs = 'decorators'; } @@ -212,41 +185,14 @@ export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost, } }; - // Patch fileExists when resolving modules, so that CompilerHost can ask TypeScript to - // resolve non-existing generated files that don't exist on disk, but are - // synthetic and added to the `programWithStubs` based on real inputs. - const generatedFileModuleResolverHost = Object.create(tsHost); - generatedFileModuleResolverHost.fileExists = (fileName: string) => { - const match = NGC_GEN_FILES.exec(fileName); - if (match) { - const [, file, suffix, ext] = match; - // Performance: skip looking for files other than .d.ts or .ts - if (ext !== '.ts' && ext !== '.d.ts') return false; - if (suffix.indexOf('ngstyle') >= 0) { - // Look for foo.css on disk - fileName = file; - } else { - // Look for foo.d.ts or foo.ts on disk - fileName = file + (ext || ''); - } - } - return tsHost.fileExists(fileName); - }; - - function generatedFileModuleResolver( - moduleName: string, containingFile: string, - compilerOptions: ts.CompilerOptions): ts.ResolvedModuleWithFailedLookupLocations { - return ts.resolveModuleName( - moduleName, containingFile, compilerOptions, generatedFileModuleResolverHost); - } - if (!bazelHost) { - bazelHost = new CompilerHost( + const fileLoader = createFileLoader(inputs, bazelOpts); + bazelHost = new BazelHost( files, compilerOpts, bazelOpts, tsHost, fileLoader, generatedFileModuleResolver); } // Also need to disable decorator downleveling in the BazelHost in Ivy mode. - if (isInIvyMode) { + if (compilerOpts.enableIvy) { bazelHost.transformDecorators = false; } @@ -433,6 +379,68 @@ export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost, return {program, diagnostics}; } +/** A module resolver for handling generated files in Bazel. */ +export function generatedFileModuleResolver( + moduleName: string, containingFile: string, compilerOptions: ts.CompilerOptions, + host: ts.ModuleResolutionHost, ): ts.ResolvedModuleWithFailedLookupLocations { + // Patch fileExists when resolving modules, so that CompilerHost can ask + // TypeScript to resolve non-existing generated files that don't exist on + // disk, but are synthetic and added to the `programWithStubs` based on real + // inputs. + const generatedFileModuleResolverHost = Object.assign({}, host, { + fileExists: (...[fileName, ...rest]: Parameters): + ReturnType => { + const match = NGC_GEN_FILES.exec(fileName); + if (match) { + const [, file, suffix, ext] = match; + // Performance: skip looking for files other than .d.ts or .ts + if (ext !== '.ts' && ext !== '.d.ts') return false; + if (suffix.indexOf('ngstyle') >= 0) { + // Look for foo.css on disk + fileName = file; + } else { + // Look for foo.d.ts or foo.ts on disk + fileName = file + (ext || ''); + } + } + + return host.fileExists(fileName, ...rest); + }, + }); + + return ts.resolveModuleName( + moduleName, containingFile, compilerOptions, generatedFileModuleResolverHost); +} + +/** Creates a {@link FileLoader} to cache Bazel inputs.*/ +export function createFileLoader( + inputs: {[key: string]: string} | undefined, bazelOpts: BazelOptions): FileLoader { + /** The one FileCache instance used in this process. */ + const fileCache = new FileCache(debug); + + if (bazelOpts.maxCacheSizeMb !== undefined) { + const maxCacheSizeBytes = bazelOpts.maxCacheSizeMb * (1 << 20); + fileCache.setMaxCacheSize(maxCacheSizeBytes); + } else { + fileCache.resetMaxCacheSize(); + } + + if (inputs) { + const fileLoader = new CachedFileLoader(fileCache); + // Resolve the inputs to absolute paths to match TypeScript internals + const resolvedInputs = new Map(); + const inputKeys = Object.keys(inputs); + for (let i = 0; i < inputKeys.length; i++) { + const key = inputKeys[i]; + resolvedInputs.set(resolveNormalizedPath(key), inputs[key]); + } + fileCache.updateCache(resolvedInputs); + return fileLoader; + } else { + return new UncachedFileLoader(); + } +} + /** * Generate metadata.json for the specified `files`. By default, metadata.json * is only generated by the compiler if --flatModuleOutFile is specified. But