Skip to content

Commit

Permalink
Switch from yarn to npm (#2545)
Browse files Browse the repository at this point in the history
* Switch from yarn to npm

* Download node + npm

* Refactor `SpellCheck` and `InstallNode` logic to a custom `NpmTasks` implementation

Co-authored-by: ITaluone <44049228+ITaluone@users.noreply.github.com>

* Cache local node installation

* Add cache step for CI builds

* Add `CompressionExtension` for unpacking .tar.xz

* Keep all previous defined env vars on windows

* Remove unused tool `Node`

* Update _build.csproj

Co-authored-by: Jonas Nyrup <jnyrup@users.noreply.github.com>

* Update CustomNpmTasks.cs

* Update build.yml

* Revert spellcheck tests

* Add custom logger which ignores `wslpath` errors

---------

Co-authored-by: ITaluone <44049228+ITaluone@users.noreply.github.com>
Co-authored-by: Jonas Nyrup <jnyrup@users.noreply.github.com>
  • Loading branch information
3 people committed Jan 15, 2024
1 parent 8c5045c commit 3ce7286
Show file tree
Hide file tree
Showing 9 changed files with 2,069 additions and 1,139 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ jobs:
7.0.x
8.0.x
- name: Cache .nuke/temp
uses: actions/cache@v3
with:
path: |
.nuke/temp
key: ${{ runner.os }}-${{ hashFiles('NodeVersion') }}

- name: Run NUKE
run: ./build.ps1
env:
Expand Down
2 changes: 2 additions & 0 deletions .nuke/build.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"Clean",
"CodeCoverage",
"Compile",
"InstallNode",
"Pack",
"Push",
"Restore",
Expand All @@ -112,6 +113,7 @@
"Clean",
"CodeCoverage",
"Compile",
"InstallNode",
"Pack",
"Push",
"Restore",
Expand Down
57 changes: 21 additions & 36 deletions Build/Build.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using LibGit2Sharp;
using Microsoft.Build.Tasks;
using Nuke.Common;
using Nuke.Common.CI.GitHubActions;
using Nuke.Common.Execution;
Expand All @@ -18,6 +22,7 @@
using static Nuke.Common.Tools.ReportGenerator.ReportGeneratorTasks;
using static Nuke.Common.Tools.Xunit.XunitTasks;
using static Serilog.Log;
using static CustomNpmTasks;

[UnsetVisualStudioEnvironmentVariables]
[DotNetVerbosityMapping]
Expand Down Expand Up @@ -61,18 +66,6 @@ class Build : NukeBuild
[Required]
[GitRepository]
readonly GitRepository GitRepository;

#if OS_WINDOWS
[NuGetPackage("Node.js.redist", "node.exe", Version = "20.9.0", Framework = "win-x64")]
#elif OS_MAC
[NuGetPackage("Node.js.redist", "node", Version = "20.9.0", Framework = "osx-x64")]
#else
[NuGetPackage("Node.js.redist", "node", Version = "20.9.0", Framework = "linux-x64")]
#endif
Tool Node;

string YarnCli => $"{NuGetToolPathResolver.GetPackageExecutable("Yarn.MSBuild", "yarn.js", "1.22.19")}";

AbsolutePath ArtifactsDirectory => RootDirectory / "Artifacts";

AbsolutePath TestResultsDirectory => RootDirectory / "TestResults";
Expand Down Expand Up @@ -351,36 +344,27 @@ void ReportTestOutcome(params string[] globFilters)

Target SpellCheck => _ => _
.OnlyWhenDynamic(() => RunAllTargets || HasDocumentationChanges)
.DependsOn(InstallNode)
.ProceedAfterFailure()
.Executes(() =>
{
Node($"{YarnCli} --silent install", workingDirectory: RootDirectory,
logger: YarnInstallLogger);
Node($"{YarnCli} --silent run cspell", workingDirectory: RootDirectory,
logger: (_, msg) => Error(msg));
NpmInstall(silent: true, workingDirectory: RootDirectory);
NpmRun("cspell", silent: true);
});

Action<OutputType, string> YarnInstallLogger = (_, msg) =>
{
// See this PR here: https://github.com/fluentassertions/fluentassertions/pull/2537
// and several comments and references.
// This seems to be a bug in yarn and the package 'Yarn.MSBuild' is no longer maintained;
// So: ignore 'yarn install' errors when the same cache directory is defined in yarn.lock.
// This errors have the pattern like: 'Error: warning Pattern [packagename@version]...'
if (!msg.Contains("is trying to unpack in the same destination"))
Target InstallNode => _ => _
.OnlyWhenDynamic(() => RunAllTargets || HasDocumentationChanges)
.ProceedAfterFailure()
.Executes(() =>
{
if (msg.StartsWith("warning"))
{
Warning(msg);
}
else
{
Error(msg);
}
}
};
Initialize(RootDirectory);
NpmFetchRuntime();
ReportSummary(s => s
.When(HasCachedNodeModules, conf => conf
.AddPair("Skipped", "Downloading and extracting")));
});

bool HasDocumentationChanges => Changes.Any(x => IsDocumentation(x));

Expand All @@ -392,6 +376,7 @@ void ReportTestOutcome(params string[] globFilters)
x.StartsWith("cSpell.json") ||
x.StartsWith("LICENSE") ||
x.StartsWith("package.json") ||
x.StartsWith("package-lock.json") ||
x.StartsWith("README.md");

string[] Changes =>
Expand Down
28 changes: 28 additions & 0 deletions Build/CompressionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Nuke.Common.IO;
using SharpCompress.Common;
using SharpCompress.Readers;
using System.IO;

public static class CompressionExtensions
{
public static void UnTarXzTo(this AbsolutePath archive, AbsolutePath directory)
{
using Stream stream = File.OpenRead(archive);

using var reader = ReaderFactory.Open(stream);

while (reader.MoveToNextEntry())
{
if (reader.Entry.IsDirectory)
{
continue;
}

reader.WriteEntryToDirectory(directory, new ExtractionOptions
{
ExtractFullPath = true,
Overwrite = true
});
}
}
}
200 changes: 200 additions & 0 deletions Build/CustomNpmTasks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Nuke.Common;
using Nuke.Common.IO;
using Nuke.Common.Tooling;
using Nuke.Common.Utilities;
using Nuke.Common.Utilities.Collections;
using static Serilog.Log;

public static class CustomNpmTasks
{
static AbsolutePath RootDirectory;
static AbsolutePath TempDir;
static AbsolutePath NodeDir;
static AbsolutePath NodeDirPerOs;
static AbsolutePath WorkingDirectory;

static IReadOnlyDictionary<string, string> NpmEnvironmentVariables = null;

static Tool Npm;

static string Version;

public static bool HasCachedNodeModules;

public static void Initialize(AbsolutePath root)
{
RootDirectory = root;
NodeDir = RootDirectory / ".nuke" / "temp";

Version = (RootDirectory / "NodeVersion").ReadAllText().Trim();
HasCachedNodeModules = NodeDir.GlobFiles($"node*{Version}*/**/node*", $"node*{Version}*/**/npm*").Count != 0;
}

public static void NpmFetchRuntime()
{
DownloadNodeArchive().ExtractNodeArchive();

LinkTools();
}

static AbsolutePath DownloadNodeArchive()
{
AbsolutePath archive = NodeDir;
string os = null;
string archiveType = null;

if (EnvironmentInfo.IsWin)
{
os = "win-x64";
archiveType = ".zip";
}
else if (EnvironmentInfo.IsOsx)
{
os = "darwin-x64";
archiveType = ".tar.gz";
}
else if (EnvironmentInfo.IsLinux)
{
os = "linux-x64";
archiveType = ".tar.xz";
}

os.NotNull();
archiveType.NotNull();

os = EnvironmentInfo.IsArm64 ? os!.Replace("x64", "arm64") : os;

if (!HasCachedNodeModules)
{
Information($"Fetching node.js ({Version}) for {os}");

string downloadUrl = $"https://nodejs.org/dist/v{Version}/node-v{Version}-{os}{archiveType}";
archive = NodeDir / $"node{archiveType}";

HttpTasks.HttpDownloadFile(downloadUrl, archive, clientConfigurator: c =>
{
c.Timeout = TimeSpan.FromSeconds(50);
return c;
});
}
else
{
Information("Skipping archive download due to cache");
}

NodeDirPerOs = NodeDir / $"node-v{Version}-{os}";
WorkingDirectory = NodeDirPerOs;

return archive;
}

static void ExtractNodeArchive(this AbsolutePath archive)
{
if (HasCachedNodeModules)
{
Information("Skipping archive extraction due to cache");

return;
}

Information($"Extracting node.js binary archive ({archive}) to {NodeDir}");

if (EnvironmentInfo.IsWin)
{
archive.UnZipTo(NodeDir);
}
else if (EnvironmentInfo.IsOsx)
{
archive.UnTarGZipTo(NodeDir);
}
else if (EnvironmentInfo.IsLinux)
{
archive.UnTarXzTo(NodeDir);
}
}

static void LinkTools()
{
if (EnvironmentInfo.IsWin)
{
Information("Resolve tool npm...");
Npm = ToolResolver.GetTool(NodeDirPerOs / "npm.cmd");
NpmVersion();
}
else
{
WorkingDirectory /= "bin";

var nodeExecutable = WorkingDirectory / "node";
var npmNodeModules = NodeDirPerOs / "lib" / "node_modules";
var npmExecutable = npmNodeModules / "npm" / "bin" / "npm";
var npmSymlink = WorkingDirectory / "npm";

Information($"Set execution permissions for {nodeExecutable}...");
nodeExecutable.SetExecutable();

Information($"Set execution permissions for {npmExecutable}...");
npmExecutable.SetExecutable();

Information("Linking binaries...");
Tool ln = ToolResolver.GetPathTool("ln");
ln($"-sf {npmExecutable} npm", workingDirectory: WorkingDirectory);
ln($"-sf {npmNodeModules} node_modules", workingDirectory: WorkingDirectory);

Information("Resolve tool npm...");
Npm = ToolResolver.GetTool(npmSymlink);
NpmVersion();
}

SetEnvVars();
}

static void SetEnvVars()
{
NpmEnvironmentVariables = EnvironmentInfo.Variables
.ToDictionary(x => x.Key, x => x.Value)
.SetKeyValue("path", WorkingDirectory).AsReadOnly();
}

public static void NpmInstall(bool silent = false, string workingDirectory = null)
{
Npm($"install {(silent ? "--silent" : "")}",
workingDirectory,
logger: NpmLogger);
}

public static void NpmRun(string args, bool silent = false)
{
Npm($"run {(silent ? "--silent" : "")} {args}".TrimMatchingDoubleQuotes(),
environmentVariables: NpmEnvironmentVariables,
logger: NpmLogger);
}

static void NpmVersion()
{
Npm("--version",
workingDirectory: WorkingDirectory,
logInvocation: false,
logger: NpmLogger,
environmentVariables: NpmEnvironmentVariables);
}

static Action<OutputType, string> NpmLogger = (outputType, msg) =>
{
if (EnvironmentInfo.IsWsl && msg.Contains("wslpath"))
return;
if (outputType == OutputType.Err)
{
Error(msg);
return;
}
Information(msg);
};
}
3 changes: 1 addition & 2 deletions Build/_build.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@
<PackageDownload Include="GitVersion.Tool" Version="[5.10.0]" />
<PackageDownload Include="ReportGenerator" Version="[5.2.0]" />
<PackageDownload Include="xunit.runner.console" Version="[2.6.5]" />
<PackageDownload Include="Node.js.redist" Version="[20.9.0]" />
<PackageReference Include="LibGit2Sharp" Version="0.29.0" />
<PackageReference Include="Nuke.Common" Version="7.0.6" />
<PackageReference Include="Nuke.Components" Version="7.0.6" />
<PackageDownload Include="Yarn.MSBuild" Version="[1.22.19]" />
<PackageReference Include="SharpCompress" Version="0.35.0" />
</ItemGroup>
</Project>
1 change: 1 addition & 0 deletions NodeVersion
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20.11.0

0 comments on commit 3ce7286

Please sign in to comment.