diff --git a/packages/compiler-cli/test/BUILD.bazel b/packages/compiler-cli/test/BUILD.bazel index a747f2d8e4..8bb948fec1 100644 --- a/packages/compiler-cli/test/BUILD.bazel +++ b/packages/compiler-cli/test/BUILD.bazel @@ -7,6 +7,7 @@ ts_library( testonly = True, srcs = [ "mocks.ts", + "runfile_helpers.ts", "test_support.ts", ], visibility = [ diff --git a/packages/compiler-cli/test/ngcc/ngcc_spec.ts b/packages/compiler-cli/test/ngcc/ngcc_spec.ts index 532ac2566e..be09793aca 100644 --- a/packages/compiler-cli/test/ngcc/ngcc_spec.ts +++ b/packages/compiler-cli/test/ngcc/ngcc_spec.ts @@ -12,6 +12,7 @@ import {join} from 'path'; const Module = require('module'); import {mainNgcc} from '../../src/ngcc/src/main'; +import {getAngularPackagesFromRunfiles} from '../runfile_helpers'; describe('ngcc main()', () => { beforeEach(createMockFileSystem); @@ -40,8 +41,7 @@ describe('ngcc main()', () => { function createMockFileSystem() { - const packagesPath = join(process.env.TEST_SRCDIR !, 'angular/packages'); - mockFs({'/node_modules/@angular': loadPackages(packagesPath)}); + mockFs({'/node_modules/@angular': loadAngularPackages()}); spyOn(Module, '_resolveFilename').and.callFake(mockResolve); } @@ -50,27 +50,16 @@ function restoreRealFileSystem() { } -/** - * Load the built Angular packages into an in-memory structure. - * @param packagesPath the path to the folder containing the built packages. - */ -function loadPackages(packagesPath: string): Directory { +/** Load the built Angular packages into an in-memory structure. */ +function loadAngularPackages(): Directory { const packagesDirectory: Directory = {}; - readdirSync(packagesPath).forEach(name => { - const packagePath = join(packagesPath, name); - if (!statSync(packagePath).isDirectory()) { - return; - } - const npmPackagePath = join(packagePath, 'npm_package'); - if (!existsSync(npmPackagePath)) { - return; - } - packagesDirectory[name] = loadDirectory(npmPackagePath); - }); + + getAngularPackagesFromRunfiles().forEach( + ({name, pkgPath}) => { packagesDirectory[name] = loadDirectory(pkgPath); }); + return packagesDirectory; } - /** * Load real files from the filesystem into an "in-memory" structure, * which can be used with `mock-fs`. diff --git a/packages/compiler-cli/test/runfile_helpers.ts b/packages/compiler-cli/test/runfile_helpers.ts new file mode 100644 index 0000000000..7842adb289 --- /dev/null +++ b/packages/compiler-cli/test/runfile_helpers.ts @@ -0,0 +1,48 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Gets all built Angular NPM package artifacts by querying the Bazel runfiles. + * In case there is a runfiles manifest (e.g. on Windows), the packages are resolved + * through the manifest because the runfiles are not symlinked and cannot be searched + * within the real filesystem. + */ +export function getAngularPackagesFromRunfiles() { + // Path to the Bazel runfiles manifest if present. This file is present if runfiles are + // not symlinked into the runfiles directory. + const runfilesManifestPath = process.env.RUNFILES_MANIFEST_FILE; + + if (!runfilesManifestPath) { + const packageRunfilesDir = path.join(process.env.RUNFILES !, 'angular/packages'); + + return fs.readdirSync(packageRunfilesDir) + .map(name => ({name, pkgPath: path.join(packageRunfilesDir, name, 'npm_package/')})) + .filter(({pkgPath}) => fs.existsSync(pkgPath)); + } + + return fs.readFileSync(runfilesManifestPath, 'utf8') + .split('\n') + .map(mapping => mapping.split(' ')) + .filter(([runfilePath]) => runfilePath.match(/^angular\/packages\/[\w-]+\/npm_package$/)) + .map(([runfilePath, realPath]) => ({ + name: path.relative('angular/packages', runfilePath).split(path.sep)[0], + pkgPath: realPath, + })); +} + +/** + * Resolves a NPM package from the Bazel runfiles. We need to resolve the Bazel tree + * artifacts using a "resolve file" because the NodeJS module resolution does not allow + * resolving to directory paths. + */ +export function resolveNpmTreeArtifact(manifestPath: string, resolveFile = 'package.json') { + return path.dirname(require.resolve(path.posix.join(manifestPath, resolveFile))); +} diff --git a/packages/compiler-cli/test/test_support.ts b/packages/compiler-cli/test/test_support.ts index 45af4c6dea..23f071132f 100644 --- a/packages/compiler-cli/test/test_support.ts +++ b/packages/compiler-cli/test/test_support.ts @@ -10,9 +10,10 @@ import * as fs from 'fs'; import * as path from 'path'; import * as ts from 'typescript'; import * as ng from '../index'; +import {getAngularPackagesFromRunfiles, resolveNpmTreeArtifact} from './runfile_helpers'; // TEST_TMPDIR is always set by Bazel. -const tmpdir = process.env.TEST_TMPDIR!; +const tmpdir = process.env.TEST_TMPDIR !; export function makeTempDir(): string { let dir: string; @@ -51,6 +52,7 @@ function createTestSupportFor(basePath: string) { 'baseUrl': basePath, 'declaration': true, 'target': ts.ScriptTarget.ES5, + 'newLine': ts.NewLineKind.LineFeed, 'module': ts.ModuleKind.ES2015, 'moduleResolution': ts.ModuleResolutionKind.NodeJs, 'lib': Object.freeze([ @@ -62,7 +64,16 @@ function createTestSupportFor(basePath: string) { }; - return {basePath, write, writeFiles, createCompilerOptions, shouldExist, shouldNotExist}; + return { + // We normalize the basePath into a posix path, so that multiple assertions which compare + // paths don't need to normalize the path separators each time. + basePath: normalizeSeparators(basePath), + write, + writeFiles, + createCompilerOptions, + shouldExist, + shouldNotExist + }; function write(fileName: string, content: string) { const dir = path.dirname(fileName); @@ -95,37 +106,29 @@ function createTestSupportFor(basePath: string) { } } -export function setupBazelTo(basePath: string) { - if (!process.env.TEST_SRCDIR) { - throw new Error('`setupBazelTo()` must only be called from in a Bazel job.'); - } - const sources = process.env.TEST_SRCDIR; - const packages = path.join(sources, 'angular/packages'); - const nodeModulesPath = path.join(basePath, 'node_modules'); +export function setupBazelTo(tmpDirPath: string) { + const nodeModulesPath = path.join(tmpDirPath, 'node_modules'); const angularDirectory = path.join(nodeModulesPath, '@angular'); + fs.mkdirSync(nodeModulesPath); - - // Link the built angular packages fs.mkdirSync(angularDirectory); - const packageNames = fs.readdirSync(packages).filter( - name => fs.statSync(path.join(packages, name)).isDirectory() && - fs.existsSync(path.join(packages, name, 'npm_package'))); - for (const pkg of packageNames) { - fs.symlinkSync(path.join(packages, `${pkg}/npm_package`), path.join(angularDirectory, pkg)); - } - // Link rxjs - const rxjsSource = path.join(sources, 'rxjs'); - const rxjsDest = path.join(nodeModulesPath, 'rxjs'); - if (fs.existsSync(rxjsSource)) { - fs.symlinkSync(rxjsSource, rxjsDest); - } + getAngularPackagesFromRunfiles().forEach( + ({pkgPath, name}) => { fs.symlinkSync(pkgPath, path.join(angularDirectory, name), 'dir'); }); // Link typescript - const typescriptSource = path.join(sources, 'ngdeps/node_modules/typescript'); + const typeScriptSource = resolveNpmTreeArtifact('ngdeps/node_modules/typescript'); const typescriptDest = path.join(nodeModulesPath, 'typescript'); - if (fs.existsSync(typescriptSource)) { - fs.symlinkSync(typescriptSource, typescriptDest); + fs.symlinkSync(typeScriptSource, typescriptDest, 'dir'); + + // Link "rxjs" if it has been set up as a runfile. "rxjs" is linked optionally because + // not all compiler-cli tests need "rxjs" set up. + try { + const rxjsSource = resolveNpmTreeArtifact('rxjs', 'index.js'); + const rxjsDest = path.join(nodeModulesPath, 'rxjs'); + fs.symlinkSync(rxjsSource, rxjsDest, 'dir'); + } catch (e) { + if (e.code !== 'MODULE_NOT_FOUND') throw e; } } @@ -148,3 +151,7 @@ export function expectNoDiagnosticsInProgram(options: ng.CompilerOptions, p: ng. ...p.getNgSemanticDiagnostics() ]); } + +export function normalizeSeparators(path: string): string { + return path.replace(/\\/g, '/'); +}