diff --git a/README.md b/README.md
index e5af50d..5c71e29 100644
--- a/README.md
+++ b/README.md
@@ -42,6 +42,8 @@ KoreBuild can be configured by adding a 'korebuild.json' file into the root fold
Example:
```js
+// NB: Don't actually use comments in JSON files. PowerShell's ConvertFrom-Json will throw an error.
+
{
// add this for editor auto-completion :)
"$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/dev/tools/korebuild.schema.json",
@@ -66,6 +68,11 @@ Example:
// This tool is only required on Windows.
"required": [ "windows" ]
+ },
+
+ "nodejs": {
+ "required": true,
+ "minVersion": "8.0"
}
}
}
diff --git a/modules/KoreBuild.Tasks/GetToolsets.cs b/modules/KoreBuild.Tasks/GetToolsets.cs
index 60583f7..54886d5 100644
--- a/modules/KoreBuild.Tasks/GetToolsets.cs
+++ b/modules/KoreBuild.Tasks/GetToolsets.cs
@@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
+using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using KoreBuild.Tasks.Utilities;
@@ -20,17 +22,26 @@ namespace KoreBuild.Tasks
public string ConfigFile { get; set; }
///
- /// The path to MSBuild.exe (x86), if the 'visualstudio' toolset was specified. It will be empty if this file does not exist.
+ /// The path to MSBuild.exe (x86) if the 'visualstudio' toolset was specified in korebuild.json.
+ /// It will be empty if not specified or if this file does not exist.
///
[Output]
public string VisualStudioMSBuildx86Path { get; set; }
///
- /// The path to MSBuild.exe (x64), if the 'visualstudio' toolset was specified. It will be empty if this file does not exist.
+ /// The path to MSBuild.exe (x64) if the 'visualstudio' toolset was specified in korebuild.json.
+ /// It will be empty if not specified or if this file does not exist.
///
[Output]
public string VisualStudioMSBuildx64Path { get; set; }
+ ///
+ /// The path to NodeJS.exe if the 'nodejs' toolset was specified in korebuild.json.
+ /// It will be empty if not specified.
+ ///
+ [Output]
+ public string NodeJSPath { get; set; }
+
public override bool Execute()
{
if (!File.Exists(ConfigFile))
@@ -54,6 +65,9 @@ namespace KoreBuild.Tasks
case KoreBuildSettings.VisualStudioToolset vs:
GetVisualStudio(vs);
break;
+ case KoreBuildSettings.NodeJSToolset node:
+ GetNode(node);
+ break;
default:
Log.LogWarning("Toolset checks not implemented for " + toolset.GetType().Name);
break;
@@ -93,5 +107,94 @@ namespace KoreBuild.Tasks
VisualStudioMSBuildx86Path = vs.GetMSBuildx86SubPath();
VisualStudioMSBuildx64Path = vs.GetMSBuildx64SubPath();
}
+
+ private void GetNode(KoreBuildSettings.NodeJSToolset nodeToolset)
+ {
+ var nodePath = EnvironmentHelper.GetCommandOnPath("nodejs") ?? EnvironmentHelper.GetCommandOnPath("node");
+
+ var required = IsRequiredOnThisPlatform(nodeToolset.Required);
+
+ if (string.IsNullOrEmpty(nodePath))
+ {
+ LogFailure(
+ isError: required,
+ message: $"Could not find NodeJS on PATH.");
+ return;
+ }
+
+ Log.LogMessage(MessageImportance.Low, "Found NodeJS in " + nodePath);
+
+ if (nodeToolset.MinVersion == null)
+ {
+ NodeJSPath = nodePath;
+ return;
+ }
+
+ var process = Process.Start(new ProcessStartInfo
+ {
+ FileName = nodePath,
+ Arguments = "--version",
+ RedirectStandardOutput = true,
+ });
+ process.WaitForExit();
+
+ if (process.ExitCode != 0)
+ {
+ LogFailure(
+ isError: required,
+ message: $"Found NodeJS in '{nodePath}', but could not determine the version of NodeJS installed. 'node --version' failed.");
+ return;
+ }
+
+ var nodeVersionString = process.StandardOutput.ReadToEnd()?.Trim()?.TrimStart('v');
+ if (!Version.TryParse(nodeVersionString, out var nodeVersion))
+ {
+ LogFailure(
+ isError: required,
+ message: $"Found NodeJS in '{nodePath}', but could not determine the version of NodeJS installed. 'node --version' returned '{nodeVersionString}'.");
+ return;
+ }
+
+ if (nodeVersion < nodeToolset.MinVersion)
+ {
+ LogFailure(
+ isError: required,
+ message: $"Found NodeJS in '{nodePath}', but its version '{nodeVersionString}' did not meet the required minimum version '{nodeToolset.MinVersion}' as specified in '{ConfigFile}'");
+ return;
+ }
+
+ Log.LogMessage(MessageImportance.High, "Using NodeJS {0} from {1}", nodeVersionString, nodePath);
+ NodeJSPath = nodePath;
+ }
+
+ private void LogFailure(bool isError, string message)
+ {
+ if (isError)
+ {
+ Log.LogError(message);
+ }
+ else
+ {
+ Log.LogWarning(message);
+ }
+ }
+
+ private bool IsRequiredOnThisPlatform(KoreBuildSettings.RequiredPlatforms platforms)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return (platforms & KoreBuildSettings.RequiredPlatforms.Windows) != 0;
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ return (platforms & KoreBuildSettings.RequiredPlatforms.Linux) != 0;
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ return (platforms & KoreBuildSettings.RequiredPlatforms.MacOS) != 0;
+ }
+
+ return platforms != KoreBuildSettings.RequiredPlatforms.None;
+ }
}
}
diff --git a/modules/KoreBuild.Tasks/Utilities/EnvironmentHelper.cs b/modules/KoreBuild.Tasks/Utilities/EnvironmentHelper.cs
new file mode 100644
index 0000000..c0c55b3
--- /dev/null
+++ b/modules/KoreBuild.Tasks/Utilities/EnvironmentHelper.cs
@@ -0,0 +1,38 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace KoreBuild.Tasks.Utilities
+{
+ internal class EnvironmentHelper
+ {
+ private static string[] _searchPaths;
+ private static string[] _executableExtensions;
+
+ static EnvironmentHelper()
+ {
+ _searchPaths = Environment.GetEnvironmentVariable("PATH")
+ .Split(Path.PathSeparator)
+ .Select(p => p.Trim('"'))
+ .ToArray();
+
+ _executableExtensions = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
+ ? Environment.GetEnvironmentVariable("PATHEXT").Split(';').Select(e => e.ToLower().Trim('"')).ToArray()
+ : Array.Empty();
+ }
+
+ public static string GetCommandOnPath(string exeName)
+ {
+ return _searchPaths.Join(
+ _executableExtensions,
+ p => true,
+ e => true,
+ (p, e) => Path.Combine(p, exeName + e))
+ .FirstOrDefault(File.Exists);
+ }
+ }
+}
diff --git a/modules/KoreBuild.Tasks/module.targets b/modules/KoreBuild.Tasks/module.targets
index cedab28..ff62edb 100644
--- a/modules/KoreBuild.Tasks/module.targets
+++ b/modules/KoreBuild.Tasks/module.targets
@@ -115,6 +115,7 @@
+
diff --git a/test/KoreBuild.Tasks.Tests/KoreBuildSettingsTest.cs b/test/KoreBuild.Tasks.Tests/KoreBuildSettingsTest.cs
index 2e7e9fe..ebae7bd 100644
--- a/test/KoreBuild.Tasks.Tests/KoreBuildSettingsTest.cs
+++ b/test/KoreBuild.Tasks.Tests/KoreBuildSettingsTest.cs
@@ -3,6 +3,7 @@
using System;
using System.IO;
+using Newtonsoft.Json;
using Xunit;
namespace KoreBuild.Tasks.Tests
@@ -96,5 +97,37 @@ namespace KoreBuild.Tasks.Tests
var vs = Assert.IsType(toolset);
Assert.Equal(platforms, vs.Required);
}
+
+ [Fact]
+ public void ItDeserializesNodeJSToolsetWithVersion()
+ {
+ File.WriteAllText(_configFile, @"
+{
+ ""toolsets"": {
+ ""nodejs"": {
+ ""minVersion"": ""8.0""
+ }
+ }
+}");
+ var settings = KoreBuildSettings.Load(_configFile);
+ var toolset = Assert.Single(settings.Toolsets);
+
+ var node = Assert.IsType(toolset);
+ Assert.Equal(new Version(8, 0), node.MinVersion);
+ }
+
+ [Fact]
+ public void ItFailsIfVersionIsNotValid()
+ {
+ File.WriteAllText(_configFile, @"
+{
+ ""toolsets"": {
+ ""nodejs"": {
+ ""minVersion"": ""banana""
+ }
+ }
+}");
+ Assert.Throws(() => KoreBuildSettings.Load(_configFile));
+ }
}
}
diff --git a/tools/KoreBuildSettings.cs b/tools/KoreBuildSettings.cs
index 89139ce..201eccc 100644
--- a/tools/KoreBuildSettings.cs
+++ b/tools/KoreBuildSettings.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
namespace KoreBuild
@@ -42,6 +43,13 @@ namespace KoreBuild
public string MinVersion { get; set; }
public string[] RequiredWorkloads { get; set; } = Array.Empty();
}
+
+ public class NodeJSToolset : KoreBuildToolset
+ {
+ [JsonConverter(typeof(VersionConverter))]
+ public Version MinVersion { get; set; }
+ }
+
public static KoreBuildSettings Load(string filePath)
{
using (var file = File.OpenText(filePath))
@@ -65,14 +73,22 @@ namespace KoreBuild
foreach (var prop in obj.Properties())
{
+ KoreBuildToolset toolset;
switch (prop.Name.ToLowerInvariant())
{
case "visualstudio":
- var vs = prop.Value.ToObject();
- toolsets.Add(vs);
+ toolset = prop.Value.ToObject();
+ break;
+ case "nodejs":
+ toolset = prop.Value.ToObject();
break;
default:
- break;
+ continue;
+ }
+
+ if (toolset != null)
+ {
+ toolsets.Add(toolset);
}
}
diff --git a/tools/korebuild.schema.json b/tools/korebuild.schema.json
index 9147ba6..1ee9ca8 100644
--- a/tools/korebuild.schema.json
+++ b/tools/korebuild.schema.json
@@ -8,7 +8,7 @@
{
"properties": {
"required": {
- "description": "Visual Studio is required to build this repo. Defaults to true if not specified.",
+ "description": "This toolset is required to build this repo. Defaults to true if not specified.",
"type": "boolean",
"default": true
}
@@ -17,7 +17,7 @@
{
"properties": {
"required": {
- "description": "Visual Studio is required to build this repo. Defaults to true if not specified.",
+ "description": "This toolset is required to build this repo. Defaults to true if not specified.",
"type": "array",
"default": [
"windows",
@@ -37,6 +37,23 @@
}
]
},
+ "nodejs": {
+ "type": "object",
+ "description": "Defines the requirements for a NodeJS installation.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/platforms"
+ },
+ {
+ "properties": {
+ "minVersion": {
+ "type": "string",
+ "description": "The minimum version of NodeJS required. Must contain at least ., but can also be .."
+ }
+ }
+ }
+ ]
+ },
"visualstudio": {
"type": "object",
"description": "Defines the requirements for Visual Studio installation.",
@@ -89,6 +106,9 @@
"properties": {
"visualstudio": {
"$ref": "#/definitions/visualstudio"
+ },
+ "nodejs": {
+ "$ref": "#/definitions/nodejs"
}
}
}