build: support running compiler-cli tests on windows (#28352)
In order to support running "compiler-cli" tests that use the "test_support.ts" utilities on Windows with Bazel, we need to imporve the logic that resolves NPM packages and symlinks them into a temporary directory. A more Bazel idiomatic and windows compatible way of resolving Bazel runfiles is to use the "RUNFILES_MANIFEST" if present. This ensures that the NPM packages can be also symlinked on Windows, and tests can execute properly on Windows. Read more about why this is needed here: * https://github.com/bazelbuild/bazel/issues/3726#issue-257062364 PR Close #28352
This commit is contained in:
parent
b91a25bfb2
commit
c10d86cbc0
|
@ -7,6 +7,7 @@ ts_library(
|
||||||
testonly = True,
|
testonly = True,
|
||||||
srcs = [
|
srcs = [
|
||||||
"mocks.ts",
|
"mocks.ts",
|
||||||
|
"runfile_helpers.ts",
|
||||||
"test_support.ts",
|
"test_support.ts",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {join} from 'path';
|
||||||
const Module = require('module');
|
const Module = require('module');
|
||||||
|
|
||||||
import {mainNgcc} from '../../src/ngcc/src/main';
|
import {mainNgcc} from '../../src/ngcc/src/main';
|
||||||
|
import {getAngularPackagesFromRunfiles} from '../runfile_helpers';
|
||||||
|
|
||||||
describe('ngcc main()', () => {
|
describe('ngcc main()', () => {
|
||||||
beforeEach(createMockFileSystem);
|
beforeEach(createMockFileSystem);
|
||||||
|
@ -40,8 +41,7 @@ describe('ngcc main()', () => {
|
||||||
|
|
||||||
|
|
||||||
function createMockFileSystem() {
|
function createMockFileSystem() {
|
||||||
const packagesPath = join(process.env.TEST_SRCDIR !, 'angular/packages');
|
mockFs({'/node_modules/@angular': loadAngularPackages()});
|
||||||
mockFs({'/node_modules/@angular': loadPackages(packagesPath)});
|
|
||||||
spyOn(Module, '_resolveFilename').and.callFake(mockResolve);
|
spyOn(Module, '_resolveFilename').and.callFake(mockResolve);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,27 +50,16 @@ function restoreRealFileSystem() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** Load the built Angular packages into an in-memory structure. */
|
||||||
* Load the built Angular packages into an in-memory structure.
|
function loadAngularPackages(): Directory {
|
||||||
* @param packagesPath the path to the folder containing the built packages.
|
|
||||||
*/
|
|
||||||
function loadPackages(packagesPath: string): Directory {
|
|
||||||
const packagesDirectory: Directory = {};
|
const packagesDirectory: Directory = {};
|
||||||
readdirSync(packagesPath).forEach(name => {
|
|
||||||
const packagePath = join(packagesPath, name);
|
getAngularPackagesFromRunfiles().forEach(
|
||||||
if (!statSync(packagePath).isDirectory()) {
|
({name, pkgPath}) => { packagesDirectory[name] = loadDirectory(pkgPath); });
|
||||||
return;
|
|
||||||
}
|
|
||||||
const npmPackagePath = join(packagePath, 'npm_package');
|
|
||||||
if (!existsSync(npmPackagePath)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
packagesDirectory[name] = loadDirectory(npmPackagePath);
|
|
||||||
});
|
|
||||||
return packagesDirectory;
|
return packagesDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load real files from the filesystem into an "in-memory" structure,
|
* Load real files from the filesystem into an "in-memory" structure,
|
||||||
* which can be used with `mock-fs`.
|
* which can be used with `mock-fs`.
|
||||||
|
|
|
@ -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)));
|
||||||
|
}
|
|
@ -10,9 +10,10 @@ import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import * as ng from '../index';
|
import * as ng from '../index';
|
||||||
|
import {getAngularPackagesFromRunfiles, resolveNpmTreeArtifact} from './runfile_helpers';
|
||||||
|
|
||||||
// TEST_TMPDIR is always set by Bazel.
|
// TEST_TMPDIR is always set by Bazel.
|
||||||
const tmpdir = process.env.TEST_TMPDIR!;
|
const tmpdir = process.env.TEST_TMPDIR !;
|
||||||
|
|
||||||
export function makeTempDir(): string {
|
export function makeTempDir(): string {
|
||||||
let dir: string;
|
let dir: string;
|
||||||
|
@ -51,6 +52,7 @@ function createTestSupportFor(basePath: string) {
|
||||||
'baseUrl': basePath,
|
'baseUrl': basePath,
|
||||||
'declaration': true,
|
'declaration': true,
|
||||||
'target': ts.ScriptTarget.ES5,
|
'target': ts.ScriptTarget.ES5,
|
||||||
|
'newLine': ts.NewLineKind.LineFeed,
|
||||||
'module': ts.ModuleKind.ES2015,
|
'module': ts.ModuleKind.ES2015,
|
||||||
'moduleResolution': ts.ModuleResolutionKind.NodeJs,
|
'moduleResolution': ts.ModuleResolutionKind.NodeJs,
|
||||||
'lib': Object.freeze([
|
'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) {
|
function write(fileName: string, content: string) {
|
||||||
const dir = path.dirname(fileName);
|
const dir = path.dirname(fileName);
|
||||||
|
@ -95,37 +106,29 @@ function createTestSupportFor(basePath: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupBazelTo(basePath: string) {
|
export function setupBazelTo(tmpDirPath: string) {
|
||||||
if (!process.env.TEST_SRCDIR) {
|
const nodeModulesPath = path.join(tmpDirPath, 'node_modules');
|
||||||
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');
|
|
||||||
const angularDirectory = path.join(nodeModulesPath, '@angular');
|
const angularDirectory = path.join(nodeModulesPath, '@angular');
|
||||||
|
|
||||||
fs.mkdirSync(nodeModulesPath);
|
fs.mkdirSync(nodeModulesPath);
|
||||||
|
|
||||||
// Link the built angular packages
|
|
||||||
fs.mkdirSync(angularDirectory);
|
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
|
getAngularPackagesFromRunfiles().forEach(
|
||||||
const rxjsSource = path.join(sources, 'rxjs');
|
({pkgPath, name}) => { fs.symlinkSync(pkgPath, path.join(angularDirectory, name), 'dir'); });
|
||||||
const rxjsDest = path.join(nodeModulesPath, 'rxjs');
|
|
||||||
if (fs.existsSync(rxjsSource)) {
|
|
||||||
fs.symlinkSync(rxjsSource, rxjsDest);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Link typescript
|
// Link typescript
|
||||||
const typescriptSource = path.join(sources, 'ngdeps/node_modules/typescript');
|
const typeScriptSource = resolveNpmTreeArtifact('ngdeps/node_modules/typescript');
|
||||||
const typescriptDest = path.join(nodeModulesPath, 'typescript');
|
const typescriptDest = path.join(nodeModulesPath, 'typescript');
|
||||||
if (fs.existsSync(typescriptSource)) {
|
fs.symlinkSync(typeScriptSource, typescriptDest, 'dir');
|
||||||
fs.symlinkSync(typescriptSource, typescriptDest);
|
|
||||||
|
// 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()
|
...p.getNgSemanticDiagnostics()
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function normalizeSeparators(path: string): string {
|
||||||
|
return path.replace(/\\/g, '/');
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue