Merged PR 670983: Enable JS related tests on Linux

Enable JS related tests on Linux

Related work items: #1965668
This commit is contained in:
Serge Mera 2022-07-20 06:09:09 +00:00
Родитель 16998d8927
Коммит 8fc40aae60
23 изменённых файлов: 329 добавлений и 103 удалений

Просмотреть файл

@ -15,6 +15,9 @@ namespace Node {
const nodeExecutablesDir : Directory = d`${getNodeTool().exe.parent}`;
@@public
export const nodePackage = getNodePackage();
/**
* Self-contained node executables. Platform dependent.
*/
@ -22,7 +25,6 @@ namespace Node {
export const nodeExecutables : Deployment.Deployable = getNodeExecutables();
function getNodeExecutables() : Deployment.Deployable {
const nodePackage = getNodePackage();
const relativePath = nodePackage.root.path.getRelative(nodeExecutablesDir.path);
return Deployment.createDeployableOpaqueSubDirectory(
@ -58,7 +60,7 @@ namespace Node {
return Transformer.execute(execArgs);
}
const nodeVersion = "v18.3.0";
const nodeVersion = "v18.6.0";
const nodeWinDir = `node-${nodeVersion}-win-x64`;
const nodeOsxDir = `node-${nodeVersion}-darwin-x64`;
const nodeLinuxDir = `node-${nodeVersion}-linux-x64`;
@ -84,7 +86,49 @@ namespace Node {
Contract.fail(`The current NodeJs package doesn't support the current OS: ${host.os}. Ensure you run on a supported OS -or- update the NodeJs package to have the version embedded.`);
}
return pkgContents;
const outDir = Context.getNewOutputDirectory("executable-npm");
const pkgRoot = d`${pkgContents.root}/${getNodePackageRoot()}`;
// For Windows, we just need to get rid of the root folder (which for node it always contains the version number). Unfortunately there is no
// way to get a nested opaque directory for an exclusive opaque (only for shared opaques)
if (host.os === "win")
{
return Transformer.copyDirectory({sourceDir: pkgRoot, targetDir: outDir, dependencies: [pkgContents], recursive: true});
}
// For linux/mac we need to create an executable npm symlink alongside node
// The npm file does come with the linux package in the form of a symlink, but our current
// targz expander doesn't handle it well
const npmExe = p`${outDir}/bin/npm`;
const result = Transformer.execute({
tool: {
exe: f`/bin/bash`,
dependsOnCurrentHostOSDirectories: true
},
workingDirectory: outDir,
arguments: [
Cmd.argument("-c"),
Cmd.rawArgument('"'),
// Copy the contents to its final destination
Cmd.args([ "rsync", "-arvh", Cmd.join("", [Artifact.none(pkgRoot), '/']), Artifact.none(outDir)]),
Cmd.rawArgument(" && "),
// Create and npm node executable
Cmd.args([ "echo", "'#!/usr/bin/env", "node'", ">", Artifact.none(npmExe) ]),
Cmd.rawArgument(" && "),
Cmd.args([ "echo", "\"require(\'../lib/node_modules/npm/lib/cli.js\')(process)\"", ">>", Artifact.none(npmExe) ]),
Cmd.rawArgument(" && "),
// Set the right permissions
Cmd.args([ "chmod", "u+x", Artifact.none(npmExe) ]),
Cmd.rawArgument('"')
],
dependencies: [pkgContents],
outputs: [outDir]
});
return result.getOutputDirectory(outDir);
}
function getNodeTool() : Transformer.ToolDefinition {
@ -93,17 +137,15 @@ namespace Node {
Contract.assert(host.cpuArchitecture === "x64", "Only 64bit versions supported.");
let executable : RelativePath = undefined;
let pkgContents : OpaqueDirectory = getNodePackage();
let pkgContents : OpaqueDirectory = nodePackage;
switch (host.os) {
case "win":
executable = r`${nodeWinDir}/node.exe`;
executable = r`node.exe`;
break;
case "macOS":
executable = r`${nodeOsxDir}/bin/node`;
break;
case "unix":
executable = r`${nodeLinuxDir}/bin/node`;
executable = r`bin/node`;
break;
default:
Contract.fail(`The current NodeJs package doesn't support the current OS: ${host.os}. Esure you run on a supported OS -or- update the NodeJs package to have the version embdded.`);
@ -126,20 +168,17 @@ namespace Node {
Contract.assert(host.cpuArchitecture === "x64", "Only 64bit versions supported.");
let executable : RelativePath = undefined;
let pkgContents : StaticDirectory = undefined;
let pkgContents : StaticDirectory = nodePackage;
switch (host.os) {
case "win":
pkgContents = importFrom("NodeJs.win-x64").extracted;
executable = r`${nodeWinDir}/node_modules/npm/bin/npm-cli.js`;
executable = r`node_modules/npm/bin/npm-cli.js`;
break;
case "macOS":
pkgContents = importFrom("NodeJs.osx-x64").extracted;
executable = r`${nodeOsxDir}/lib/node_modules/npm/bin/npm-cli.js`;
executable = r`lib/node_modules/npm/bin/npm-cli.js`;
break;
case "unix":
pkgContents = importFrom("NodeJs.linux-x64").extracted;
executable = r`${nodeLinuxDir}/lib/node_modules/npm/bin/npm-cli.js`;
executable = r`lib/node_modules/npm/bin/npm-cli.js`;
break;
default:
Contract.fail(`The current NodeJs package doesn't support the current OS: ${host.os}. Ensure you run on a supported OS -or- update the NodeJs package to have the version embedded.`);
@ -153,24 +192,48 @@ namespace Node {
*/
@@public
export function getNpmTool() : Transformer.ToolDefinition {
return Npm.getNpmTool(nodePackage, getPathToNpm());
}
function getPathToNpm() : RelativePath {
const host = Context.getCurrentHost();
let executable : RelativePath = undefined;
switch (host.os) {
case "win":
executable = r`${nodeWinDir}/npm.cmd`;
executable = r`npm.cmd`;
break;
case "macOS":
executable = r`${nodeOsxDir}/bin/npm`;
executable = r`lib/node_modules/npm/bin/npm`;
break;
case "unix":
executable = r`${nodeLinuxDir}/bin/npm`;
executable = r`lib/node_modules/npm/bin/npm`;
break;
default:
Contract.fail(`The current NodeJs package doesn't support the current OS: ${host.os}. Ensure you run on a supported OS -or- update the NodeJs package to have the version embedded.`);
}
return Npm.getNpmTool(getNodePackage(), executable);
return executable;
}
function getNodePackageRoot(): RelativePath{
const host = Context.getCurrentHost();
let relativePath : RelativePath = undefined;
switch (host.os) {
case "win":
relativePath = r`${nodeWinDir}`;
break;
case "macOS":
relativePath = r`${nodeOsxDir}`;
break;
case "unix":
relativePath = r`${nodeLinuxDir}`;
break;
default:
Contract.fail(`The current NodeJs package doesn't support the current OS: ${host.os}.`);
}
return relativePath;
}
@@public

Просмотреть файл

@ -3,23 +3,40 @@
import {Transformer} from "Sdk.Transformers";
// Yarn tgz sadly includes the version as a top level directory
const yarnVersion = "yarn-v1.22.19";
/**
* Returns a static directory containing a valid Yarn installation.
*/
@@public
export function getYarn() : StaticDirectory {
if (Environment.getFlag("[Sdk.BuildXL]microsoftInternal")) {
// Internally in Microsoft we use a nuget package that contains Yarn.
return Transformer.reSealPartialDirectory(importFrom("NPM.OnCloudbuild").Contents.all, r`tools/Yarn`);
const yarnPackage = importFrom("YarnTool").yarnPackage;
const yarnDir = Context.getNewOutputDirectory("yarn-package");
const isWinOs = Context.getCurrentHost().os === "win";
// 'yarn', the linux executable that comes with the package, doesn't have the execution permissions set
let executableYarn = undefined;
if (!isWinOs) {
const yarnExe = yarnPackage.assertExistence(r`${yarnVersion}/bin/yarn`);
executableYarn = Transformer.makeExecutable(yarnExe, p`${yarnDir}/${yarnVersion}/bin/yarn`);
}
// For the public build, we require Yarn to be installed
const installedYarnLocation = d`${Context.getMount("ProgramFilesX86").path}/Yarn`;
const packageJson = f`${installedYarnLocation}/package.json`;
if (!File.exists(packageJson))
{
Contract.fail(`Could not find Yarn installed. File '${packageJson.toDiagnosticString()}' does not exist.`);
}
const sharedOpaqueYarnPackage = Transformer.copyDirectory({
sourceDir: yarnPackage.root,
targetDir: yarnDir,
dependencies: [yarnPackage, executableYarn],
recursive: true,
excludePattern: isWinOs? undefined : "yarn"});
return Transformer.sealDirectory(installedYarnLocation, globR(installedYarnLocation));
const finalDeployment = Context.getNewOutputDirectory("yarn-package-final");
const result = Transformer.copyDirectory({
sourceDir: d`${sharedOpaqueYarnPackage.root}/${yarnVersion}`,
targetDir: finalDeployment,
dependencies: [sharedOpaqueYarnPackage, executableYarn],
recursive: true});
return result;
}

Просмотреть файл

@ -14,8 +14,10 @@ namespace Transformer {
export interface CopyDirectoryArguments {
sourceDir: Directory;
targetDir: Directory;
dependencies?: StaticDirectory[];
dependencies?: (StaticDirectory | File)[];
pattern?: string;
// Only available on Linux/Mac
excludePattern?: string;
recursive?: boolean;
keepOutputsWritable?: boolean
}
@ -28,6 +30,11 @@ namespace Transformer {
*/
@@public
export function copyDirectory(arguments: CopyDirectoryArguments): SharedOpaqueDirectory {
if (Context.getCurrentHost().os === "win") {
Contract.assert(arguments.excludePattern === undefined, "Exclude pattern is not supported on Windows but was provided.");
}
const args: Transformer.ExecuteArguments = Context.getCurrentHost().os === "win"
? <Transformer.ExecuteArguments>{
tool: {
@ -69,6 +76,7 @@ namespace Transformer {
arguments: [
Cmd.argument(arguments.recursive === false ? "-avh" : "-arvh"),
Cmd.option("--include ", arguments.pattern),
Cmd.option("--exclude ", arguments.excludePattern),
Cmd.argument(Cmd.join("", [ Artifact.none(arguments.sourceDir), '/' ])),
Cmd.argument(Artifact.none(arguments.targetDir)),
],

Просмотреть файл

@ -0,0 +1,46 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Transformer {
/**
* Copies the input to the provided output path setting the result to the given unix-style permissions
* E.g. chmod("u+x", input, output)
* On Windows, it just does a file copy without changing any file permissions
*/
@@public
export function chmod(permissions: string, input: File, output: Path): File {
const currentHost = Context.getCurrentHost();
if (currentHost.os === "macOS" || currentHost.os === "unix") {
const result = Transformer.execute({
tool: {
exe: f`/bin/bash`,
dependsOnCurrentHostOSDirectories: true
},
workingDirectory: d`${output.parent}`,
arguments: [
Cmd.argument("-c"),
Cmd.rawArgument('"'),
Cmd.args([ "cp", Artifact.input(input), Artifact.output(output) ]),
Cmd.rawArgument(" && "),
Cmd.args([ "chmod", permissions, Artifact.none(output) ]),
Cmd.rawArgument('"')
]
});
return result.getOutputFile(output);
}
else {
return Transformer.copyFile(input, output);
}
}
/**
* Calls chmod("u+x", input, output)
*/
@@public
export function makeExecutable(input: File, output: Path): File {
return chmod("u+x", input, output);
}
}

Просмотреть файл

@ -5,6 +5,7 @@ using System.Linq;
using BuildXL.Utilities;
using BuildXL.Utilities.Configuration;
using Test.BuildXL.FrontEnd.Core;
using Test.BuildXL.TestUtilities.XUnit;
using Xunit;
using Xunit.Abstractions;
using LogEventId = global::BuildXL.FrontEnd.JavaScript.Tracing.LogEventId;
@ -46,9 +47,10 @@ namespace Test.BuildXL.FrontEnd.Rush
}
[Theory]
[InlineData(@"[""../output/Dir""]", new[] { "src/output/dir"})]
[InlineData(@"[""../output/Dir"", ""../another/dir""]", new[] { "src/output/dir", "src/another/dir" })]
[InlineData(@"[""<workspaceDir>/output"", ""C:\\foo""]", new[] { "output", "C:\\foo" })]
[InlineData(@"[""../output/dir""]", new[] { "src/output/dir"})]
[InlineData(@"[""../output/dir"", ""../another/dir""]", new[] { "src/output/dir", "src/another/dir" })]
[InlineDataIfSupported(requiresWindowsBasedOperatingSystem: true, data: new object[] {@"[""<workspaceDir>/output"", ""C:\\foo""]", new[] { "output", "C:\\foo" }})]
[InlineDataIfSupported(requiresUnixBasedOperatingSystem: true, data: new object[] {@"[""<workspaceDir>/output"", ""/foo""]", new[] { "output", "/foo" }})]
public void PathPatternsAreHonored(string outputDirectoriesJSON, string[] expectedOutputDirectories)
{
var config = Build()
@ -101,7 +103,7 @@ namespace Test.BuildXL.FrontEnd.Rush
[Theory]
[InlineData(@"undefined")]
[InlineData(@"""invalid|path""")]
[InlineDataIfSupported(requiresWindowsBasedOperatingSystem: true, data: new object[] {@"""invalid|path"""})]
[InlineData(@"invalid {{ json")]
public void MalformedConfigurationFileIsHandled(string outputDirectories)
{

Просмотреть файл

@ -88,8 +88,9 @@ namespace Test.BuildXL.FrontEnd.Rush
[Fact]
public void ComplexCustomCommand()
{
var path = X("/c/absolute/path");
// Exercise custom commands with other types
var config = Build(customRushCommands: "[{command: 'build', extraArguments: ['--test', a`atom`, r`relative/path`, p`C:/absolute/path`]}]")
var config = Build(customRushCommands: $"[{{command: 'build', extraArguments: ['--test', a`atom`, r`relative/path`, p`{path}`]}}]")
.AddJavaScriptProject("@ms/project-A", "src/A", scriptCommands: new[] { ("build", "execute") })
.PersistSpecsAndGetConfiguration();
@ -101,7 +102,7 @@ namespace Test.BuildXL.FrontEnd.Rush
var pip = result.EngineState.RetrieveProcess("@ms/project-A", "build");
Assert.Contains(
$"execute --test atom {RelativePath.Create(StringTable, "relative/path").ToString(StringTable)} {AbsolutePath.Create(PathTable, "C:/absolute/path").ToString(PathTable)}",
$"execute --test atom {RelativePath.Create(StringTable, "relative/path").ToString(StringTable)} {AbsolutePath.Create(PathTable, path).ToString(PathTable)}",
pip.Arguments.ToString(PathTable));
}
}

Просмотреть файл

@ -23,23 +23,22 @@ namespace Test.BuildXL.FrontEnd.Rush
/// <summary>
/// Provides facilities to run the engine adding Rush specific artifacts.
/// </summary>
[TestClassIfSupported(requiresWindowsBasedOperatingSystem: true)]
public abstract class RushIntegrationTestBase : DsTestWithCacheBase
{
/// <summary>
/// Keep in sync with deployment.
/// </summary>
protected string PathToRush => Path.Combine(TestDeploymentDir, "Rush", "node_modules", "@microsoft", "rush", "bin", "rush").Replace("\\", "/");
protected string PathToRush => Path.Combine(TestDeploymentDir, "rush", "node_modules", "@microsoft", "rush", "bin", "rush").Replace("\\", "/");
/// <summary>
/// Keep in sync with deployment.
/// </summary>
protected string PathToNodeModules => Path.Combine(TestDeploymentDir, "Rush", "node_modules").Replace("\\", "/");
protected string PathToNodeModules => Path.Combine(TestDeploymentDir, "rush", "node_modules").Replace("\\", "/");
/// <summary>
/// Keep in sync with deployment.
/// </summary>
protected string PathToNode => Path.Combine(TestDeploymentDir, "Node", OperatingSystemHelper.IsLinuxOS? "bin/node" : "node.exe").Replace("\\", "/");
protected string PathToNode => Path.Combine(TestDeploymentDir, "node", OperatingSystemHelper.IsLinuxOS? "bin/node" : "node.exe").Replace("\\", "/");
/// <nodoc/>
protected string PathToNodeFolder => Path.GetDirectoryName(PathToNode).Replace("\\", "/");
@ -57,7 +56,7 @@ namespace Test.BuildXL.FrontEnd.Rush
// By default the engine runs e2e
protected virtual EnginePhases Phase => EnginePhases.Execute;
private string RushUserProfile => Path.Combine(TestRoot, "USERPROFILE").Replace("\\", "/");
private string RushUserProfile => Path.Combine(TestRoot, OperatingSystemHelper.IsWindowsOS ? "USERPROFILE" : "HOME").Replace("\\", "/");
private string RushTempFolder => Path.Combine(TestRoot, "RUSH_TEMP_FOLDER").Replace("\\", "/");
@ -319,7 +318,7 @@ config({{
var updatedRushJson = rushJson
.Replace(
"\"nodeSupportedVersionRange\": \">=12.13.0 <13.0.0 || >=14.15.0 <15.0.0\"",
"\"nodeSupportedVersionRange\": \">=10.13.0 <=18.3.0\"")
"\"nodeSupportedVersionRange\": \">=10.13.0 <=18.6.0\"")
.Replace(
"\"pnpmVersion\": \"2.15.1\"",
"\"pnpmVersion\": \"5.0.2\"");
@ -354,9 +353,16 @@ config({{
UseShellExecute = false,
};
startInfo.Environment["PATH"] += $";{PathToNodeFolder}";
startInfo.Environment["PATH"] += $"{Path.PathSeparator}{PathToNodeFolder}";
startInfo.Environment["NODE_PATH"] = PathToNodeModules;
startInfo.Environment["USERPROFILE"] = RushUserProfile;
if (OperatingSystemHelper.IsWindowsOS)
{
startInfo.Environment["USERPROFILE"] = RushUserProfile;
}
else
{
startInfo.Environment["HOME"] = RushUserProfile;
}
startInfo.Environment["APPDATA"] = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
startInfo.Environment["RUSH_TEMP_FOLDER"] = RushTempFolder;
startInfo.Environment["RUSH_ABSOLUTE_SYMLINKS"] = "1";

Просмотреть файл

@ -268,11 +268,11 @@ namespace Test.BuildXL.FrontEnd.Rush
.AddJavaScriptProject("@ms/project-B", "src/B", dependencies: new[] { "@ms/project-A" }, scriptCommands: new[] { ("build", "echo HelloB") })
.AddSpec("module.config.dsc", "module({name: 'myModule'});")
.AddSpec(@"
import {Transformer} from 'Sdk.Transformers';
import {Transformer, Artifact, Cmd} from 'Sdk.Transformers';
const tool : Transformer.ToolDefinition = {
exe: Environment.getFileValue('COMSPEC'),
dependsOnWindowsDirectories: true
exe: Context.isWindowsOS()? Environment.getFileValue('COMSPEC') : f`/usr/bin/bash`,
dependsOnCurrentHostOSDirectories: true
};
namespace Test {
@ -281,7 +281,12 @@ namespace Test {
if (project.name === '@ms/project-A') {
return Transformer.execute({
workingDirectory: project.projectFolder,
arguments: [{ value: '/C' }, { value: 'echo HelloA' }, { value: '>' }, { value: 'file.txt' }],
arguments: [
Cmd.argument(Context.isWindowsOS() ? ""/C"" : ""-c""),
Cmd.rawArgument('""'),
Cmd.args([""echo"", ""HelloA"", "">"", ""file.txt""]),
Cmd.rawArgument('""')
],
tool: tool,
outputs: project.outputs.filter(output => typeof output !== 'Path').map(output => <Transformer.DirectoryOutput>{ directory: output, kind: 'shared' })
});

Просмотреть файл

@ -22,7 +22,6 @@ namespace Test.BuildXL.FrontEnd.Rush
/// Base class for tests that programmatically add projects and verify the corresponding scheduled process
/// done by <see cref="RushResolver"/>
/// </summary>
[TestClassIfSupported(requiresWindowsBasedOperatingSystem: true)]
public abstract class RushPipSchedulingTestBase : PipSchedulingTestBase<JavaScriptProject, RushResolverSettings>
{
private AbsolutePath m_commonTempFolder = AbsolutePath.Invalid;

Просмотреть файл

@ -17,7 +17,7 @@ namespace Test.Rush {
const rushlib = Node.runNpmPackageInstall(rushLibTest, [], {name: "@microsoft/rush-lib", version: "5.47.0"});
// TODO: to enable this, we should use an older version of NodeJs for Linux
const isRunningOnSupportedSystem = Context.getCurrentHost().cpuArchitecture === "x64" && !BuildXLSdk.isHostOsLinux;
const isRunningOnSupportedSystem = Context.getCurrentHost().cpuArchitecture === "x64";
// TODO: enable Rush tests for non-internal builds when we address the perf issue that make them timeout
@@public
@ -92,7 +92,7 @@ namespace Test.Rush {
},
{
subfolder: a`node`,
contents: [Node.nodeExecutables]
contents: [Node.nodePackage]
},
],
});

Просмотреть файл

@ -23,7 +23,6 @@ namespace Test.BuildXL.FrontEnd.Yarn
/// <summary>
/// Provides facilities to run the engine adding Yarn specific artifacts.
/// </summary>
[TestClassIfSupported(requiresWindowsBasedOperatingSystem: true)]
public abstract class CustomJavaScriptIntegrationTestBase : DsTestWithCacheBase
{
/// <summary>

Просмотреть файл

@ -6,6 +6,7 @@ using System.Linq;
using BuildXL.Utilities.Configuration;
using Test.BuildXL.FrontEnd.Core;
using Test.BuildXL.TestUtilities.Xunit;
using Test.BuildXL.TestUtilities.XUnit;
using Xunit;
using Xunit.Abstractions;
@ -130,7 +131,7 @@ namespace Test.BuildXL.FrontEnd.Yarn.IntegrationTests
[InlineData(null, true)]
[InlineData("this is not JSON", true)]
[InlineData("{'' : {location: 'a/path', workspaceDependencies: []}}", true)]
[InlineData("{'project1' : {location: 'invalid:path', workspaceDependencies: []}}", true)]
[InlineDataIfSupported(requiresWindowsBasedOperatingSystem: true, data: new object[] { "{'project1' : {location: 'invalid:path', workspaceDependencies: []}}", true})]
[InlineData("{'project2' : {location: 'a/path', workspaceDependencies: null}}", true)]
[InlineData("{'project3' : {location: 'a/path', workspaceDependencies: ['non-existent-proj']}}", false)]
public void CustomGraphFileErrorHandling(string fileContent, bool producesCustomProjectGraphError)
@ -211,8 +212,8 @@ namespace Test.BuildXL.FrontEnd.Yarn.IntegrationTests
[Theory]
[InlineData("LogsDirectory", true)]
[InlineData("ProgramFiles", true)]
[InlineData("UserDefinedMount", false)]
[InlineDataIfSupported(requiresWindowsBasedOperatingSystem: true, data: new object[] {"ProgramFiles", true})]
[InlineDataIfSupported(requiresWindowsBasedOperatingSystem: true, data: new object[] {"UserDefinedMount", false})]
public void MountsAreAccesibleDuringCustomScriptEvaluation(string mountName, bool expectSuccess)
{
string customGraph = @"Map.empty<string, {location: RelativePath, workspaceDependencies: string[]}>()

Просмотреть файл

@ -23,18 +23,17 @@ namespace Test.BuildXL.FrontEnd.Yarn
/// <summary>
/// Provides facilities to run the engine adding Yarn specific artifacts.
/// </summary>
[TestClassIfSupported(requiresWindowsBasedOperatingSystem: true)]
public abstract class YarnIntegrationTestBase : DsTestWithCacheBase
{
/// <summary>
/// Keep in sync with deployment.
/// </summary>
protected string PathToYarn => Path.Combine(TestDeploymentDir, "yarn", "bin", "yarn").Replace("\\", "/");
protected string PathToYarn => Path.Combine(TestDeploymentDir, "yarn", "bin", OperatingSystemHelper.IsWindowsOS ? "yarn.cmd" : "yarn").Replace("\\", "/");
/// <summary>
/// Keep in sync with deployment.
/// </summary>
protected string PathToNode => Path.Combine(TestDeploymentDir, "Node", OperatingSystemHelper.IsLinuxOS? "bin/node" : "node.exe").Replace("\\", "/");
protected string PathToNode => Path.Combine(TestDeploymentDir, "node", OperatingSystemHelper.IsWindowsOS? "node.exe" : "node").Replace("\\", "/");
/// <nodoc/>
protected string PathToNodeFolder => Path.GetDirectoryName(PathToNode).Replace("\\", "/");
@ -78,7 +77,7 @@ namespace Test.BuildXL.FrontEnd.Yarn
bool? enableFullReparsePointResolving = null,
string nodeLocation = "")
{
environment ??= new Dictionary<string, string> {
environment ??= new Dictionary<string, string> {
["PATH"] = PathToNodeFolder,
};
@ -106,6 +105,12 @@ namespace Test.BuildXL.FrontEnd.Yarn
["PATH"] = new DiscriminatingUnion<string, UnitValue>(PathToNodeFolder),
};
// On Linux/Mac, yarn depends on low level tools like sed, readlink, etc. If the value for path is not configured as a passthrough, inject /usr/bin
if (!OperatingSystemHelper.IsWindowsOS && environment.TryGetValue("PATH", out var value) && value.GetValue() is string path)
{
environment["PATH"] = new DiscriminatingUnion<string, UnitValue>(path + Path.PathSeparator + "/usr/bin");
}
// We reserve the null string for a true undefined.
if (yarnLocation == string.Empty)
{
@ -229,7 +234,7 @@ config({{
private bool YarnRun(string workingDirectory, string yarnArgs)
{
string arguments = $"{PathToYarn}.js {yarnArgs}";
string arguments = $"{Path.Combine(Path.GetDirectoryName(PathToYarn), "yarn")}.js {yarnArgs}";
string filename = PathToNode;
// Unfortunately, capturing standard out/err non-deterministically hangs node.exe on exit when

Просмотреть файл

@ -13,7 +13,6 @@ namespace Test.Yarn {
// Yarn is not easily available in the hosted machines that run the public build. So excluding these tests for now outside of the internal build
const isRunningOnSupportedSystem =
Context.getCurrentHost().cpuArchitecture === "x64" &&
!BuildXLSdk.isHostOsLinux &&
Environment.getFlag("[Sdk.BuildXL]microsoftInternal");
@@public

Просмотреть файл

@ -40,6 +40,8 @@ namespace BuildXL.FrontEnd.Yarn
// If the base location was provided at configuration time, we honor it as is
string paths;
var toolNameToFind = OperatingSystemHelper.IsWindowsOS ? new[] { "yarn.cmd" } : new[] { "yarn" };
if (resolverSettings.YarnLocation != null)
{
var value = resolverSettings.YarnLocation.GetValue();
@ -52,7 +54,7 @@ namespace BuildXL.FrontEnd.Yarn
else
{
var pathCollection = ((IReadOnlyList<DirectoryArtifact>) value).Select(dir => dir.Path);
if (!FrontEndUtilities.TryFindToolInPath(m_context, m_host, pathCollection, new[] { "yarn", "yarn.cmd" }, out finalYarnLocation))
if (!FrontEndUtilities.TryFindToolInPath(m_context, m_host, pathCollection, toolNameToFind, out finalYarnLocation))
{
failure = $"'yarn' cannot be found under any of the provided paths '{string.Join(Path.PathSeparator.ToString(), pathCollection.Select(path => path.ToString(m_context.PathTable)))}'.";
return false;
@ -66,7 +68,7 @@ namespace BuildXL.FrontEnd.Yarn
// If the location was not provided, let's try to see if Yarn is under %PATH%
paths = buildParameters["PATH"];
if (!FrontEndUtilities.TryFindToolInPath(m_context, m_host, paths, new[] { "yarn", "yarn.cmd"}, out finalYarnLocation))
if (!FrontEndUtilities.TryFindToolInPath(m_context, m_host, paths, toolNameToFind, out finalYarnLocation))
{
failure = "A location for 'yarn' is not explicitly specified. However, 'yarn' doesn't seem to be part of PATH. You can either specify the location explicitly using 'yarnLocation' field in " +
$"the Yarn resolver configuration, or make sure 'yarn' is part of your PATH. Current PATH is '{paths}'.";

Просмотреть файл

@ -59,11 +59,20 @@ try {
* data: '{ 'workspaceName' : { location: 'some/location', workspaceDependencies: [], mismatchedWorkspaceDependencies: [] } }'
* }
*/
let workspaceJson = JSON.parse(testJson === undefined
? execSync(`"${pathToYarn}" --silent workspaces info --json`).toString()
: testJson
);
let workspaceJson;
if (testJson !== undefined) {
workspaceJson = JSON.parse(testJson);
}
else {
// This yarn execution sometimes non-deterministically makes node non-terminating. Debugging this call shows a dangling pipe
// that seems to be related to stdout/stderr piping to the main process. In order to workaround this issue, output the raw
// report to the output graph file and immediately read it back for post-processing. The final graph (in the format bxl expects)
// will be rewritten into the same file
execSync(`"${pathToYarn}" --silent workspaces info --json > "${outputGraphFile}"`, {stdio: "ignore"});
workspaceJson = JSON.parse(fs.readFileSync(outputGraphFile, "utf8"));
}
// Parse the data key if the old format is found.
if ("type" in workspaceJson && workspaceJson["type"] === "log") {
workspaceJson = JSON.parse(workspaceJson["data"]);
@ -103,6 +112,6 @@ try {
} catch (Error) {
// Standard error from this tool is exposed directly to the user.
// Catch any exceptions and just print out the message.
console.error(Error.message);
console.error(Error.message || Error);
process.exit(1);
}
}

Просмотреть файл

@ -15,8 +15,8 @@ namespace Test.Tool.Javascript
{
public PathTable PathTable;
public string UserProfile => Path.Combine(TestDeploymentDir, "userprofile");
public string NodeTool => Path.Combine(TestDeploymentDir, "node", OperatingSystemHelper.IsUnixOS ? "node" : "node.exe");
public string YarnTool => Path.Combine(TestDeploymentDir, "yarn", "bin", "yarn");
public string NodeTool => Path.Combine(TestDeploymentDir, "node", OperatingSystemHelper.IsWindowsOS ? "node.exe" : "node");
public string YarnTool => Path.Combine(TestDeploymentDir, "yarn", "bin", OperatingSystemHelper.IsWindowsOS ? "yarn.cmd" : "yarn");
public JavascriptGraphBuilderTestBase(ITestOutputHelper output) : base(output)
{
@ -42,8 +42,15 @@ namespace Test.Tool.Javascript
UseShellExecute = false,
};
startInfo.Environment["PATH"] += $";{Path.GetDirectoryName(NodeTool)}";
startInfo.Environment["USERPROFILE"] = UserProfile;
// Low level tools like sed and readlink needs to be in the path for the linux/mac case
var path = $"{Path.PathSeparator}{Path.GetDirectoryName(NodeTool)}";
if (!OperatingSystemHelper.IsWindowsOS)
{
path += Path.PathSeparator + "/usr/bin/";
}
startInfo.Environment["PATH"] += path;
startInfo.Environment[OperatingSystemHelper.IsWindowsOS ? "USERPROFILE" : "HOME"] = UserProfile;
var graphBuilderResult = Process.Start(startInfo);
graphBuilderResult.WaitForExit();

Просмотреть файл

@ -9,7 +9,7 @@ namespace Test.Tool.JavascriptGraphBuilder {
// Yarn is not easily available in the hosted machines that run the public build. So excluding these tests for now outside of the internal build
const isRunningOnSupportedSystem =
Context.getCurrentHost().cpuArchitecture === "x64" &&
!BuildXLSdk.isHostOsLinux && !BuildXLSdk.isHostOsOsx &&
!BuildXLSdk.isHostOsOsx &&
Environment.getFlag("[Sdk.BuildXL]microsoftInternal");
@@public

Просмотреть файл

@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System;
using System.Collections.Generic;
using System.IO;
using BuildXL.FrontEnd.JavaScript.ProjectGraph;
@ -11,9 +11,9 @@ using Test.BuildXL.TestUtilities.Xunit;
using Xunit;
using Xunit.Abstractions;
namespace Test.Tool.Javascript.YarnGraphBuilder
{
public class YarnGraphBuilderTests : JavascriptGraphBuilderTestBase
namespace Test.Tool.Javascript.YarnGraphBuilder
{
public class YarnGraphBuilderTests : JavascriptGraphBuilderTestBase
{
private readonly string m_yarnWorkSpaceRoot;
private readonly string m_yarnGraphBuilderToolRoot;
@ -113,5 +113,5 @@ namespace Test.Tool.Javascript.YarnGraphBuilder
XAssert.IsTrue(projects[0].Name.Equals("workspace-a"));
XAssert.IsTrue(projects[0].ProjectFolder.ToString(PathTable).Equals(Path.Combine(m_yarnWorkSpaceRoot, "workspace-a")));
}
}
}
}

Просмотреть файл

@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Reflection;
using Test.BuildXL.TestUtilities.Xunit;
using Xunit.Sdk;
namespace Test.BuildXL.TestUtilities.XUnit
{
/// <summary>
/// Custom inline data attribute that allows dynamically skipping tests based on what operations are supported
/// </summary>
[TraitDiscoverer(AdminTestDiscoverer.ClassName, AdminTestDiscoverer.AssemblyName)]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class InlineDataIfSupported : DataAttribute, ITraitAttribute
{
private readonly object[] m_data;
private readonly ITestIfSupportedTraitAttribute m_inner;
/// <inheritdoc />
public TestRequirements Requirements => m_inner.Requirements;
/// <nodoc/>
public InlineDataIfSupported(
bool requiresAdmin = false,
bool requiresJournalScan = false,
bool requiresSymlinkPermission = false,
bool requiresWindowsBasedOperatingSystem = false,
bool requiresUnixBasedOperatingSystem = false,
bool requiresHeliumDriversAvailable = false,
bool requiresMacOperatingSystem = false,
bool requiresWindowsOrMacOperatingSystem = false,
bool requiresWindowsOrLinuxOperatingSystem = false,
TestRequirements additionalRequirements = TestRequirements.None,
params object[] data)
{
m_data = data;
m_inner = new FactIfSupportedAttribute(
requiresAdmin: requiresAdmin,
requiresJournalScan: requiresJournalScan,
requiresSymlinkPermission: requiresSymlinkPermission,
requiresWindowsBasedOperatingSystem: requiresWindowsBasedOperatingSystem,
requiresUnixBasedOperatingSystem: requiresUnixBasedOperatingSystem,
requiresHeliumDriversAvailable: requiresHeliumDriversAvailable,
requiresMacOperatingSystem: requiresMacOperatingSystem,
requiresWindowsOrMacOperatingSystem: requiresWindowsOrMacOperatingSystem,
additionalRequirements: additionalRequirements,
requiresWindowsOrLinuxOperatingSystem: requiresWindowsOrLinuxOperatingSystem
);
Skip = m_inner.Skip;
}
/// <nodoc/>
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
{
return new object[1][] { m_data };
}
}
}

Просмотреть файл

@ -2710,15 +2710,6 @@
}
}
},
{
"Component": {
"Type": "NuGet",
"NuGet": {
"Name": "NPM.OnCloudbuild",
"Version": "3.1.0"
}
}
},
{
"Component": {
"Type": "NuGet",

Просмотреть файл

@ -542,22 +542,28 @@ config({
// NodeJs
{
moduleName: "NodeJs.win-x64",
url: "https://nodejs.org/dist/v18.3.0/node-v18.3.0-win-x64.zip",
hash: "VSO0:AEABD74C344D3DE10CB5CD0E2FE49E6210E066F4EB982A86627B8E54156F3C8200",
url: "https://nodejs.org/dist/v18.6.0/node-v18.6.0-win-x64.zip",
hash: "VSO0:EA729EEA528055396523F3F5BD61EDD769C251EB7B4483AABFEB511333E60AA000",
archiveType: "zip",
},
{
moduleName: "NodeJs.osx-x64",
url: "https://nodejs.org/dist/v18.3.0/node-v18.3.0-darwin-x64.tar.gz",
hash: "VSO0:0E2D521D3E250F3969D529303E15D260D6E465FADC2DEA20215DED422CAE2EC600",
url: "https://nodejs.org/dist/v18.6.0/node-v18.6.0-darwin-x64.tar.gz",
hash: "VSO0:653B5954AD06BB6C9B7141853649602790FCB0031B81FDB82241333E2EE1350200",
archiveType: "tgz",
},
{
moduleName: "NodeJs.linux-x64",
url: "https://nodejs.org/dist/v18.3.0/node-v18.3.0-linux-x64.tar.gz",
hash: "VSO0:6CD39D92AC462BC97A2B445A7A1A3ACB5A3851AEF9C63AC7CC9F067DD067D38300",
url: "https://nodejs.org/dist/v18.6.0/node-v18.6.0-linux-x64.tar.gz",
hash: "VSO0:15A59CD4CC7C08A91FDF0C028F1C1129DC4B635749514739E1B2C6224E6420FB00",
archiveType: "tgz",
}
},
{
moduleName: "YarnTool",
extractedValueName: "yarnPackage",
url: 'https://registry.npmjs.org/yarn/-/yarn-1.22.19.tgz',
archiveType: "tgz"
},
],
},
],

Просмотреть файл

@ -61,9 +61,6 @@ export const pkgs = isMicrosoftInternal ? [
// Internal pacakged version to avoid downloading from the web but the trusted stable internal feed:
{ id: "PowerShell.Core", version: "6.1.0", osSkip: [ "macOS", "unix" ] },
// Officially mantained CB package that contains Yarn. Used for Yarn tests.
{ id: "NPM.OnCloudbuild", version: "3.1.0" },
// IcM and dependencies
{ id: "Microsoft.AzureAd.Icm.Types.amd64", version: "2.2.1363.11" },
{ id: "Microsoft.AzureAd.Icm.WebService.Client.amd64", version: "2.2.1363.11" },