First version of dnx watch

This commit is contained in:
Victor Hurdugaci 2015-09-17 14:05:33 -07:00
parent 62caf09ac4
commit 3f40980d02
48 changed files with 3218 additions and 0 deletions

50
.gitattributes vendored Normal file
View File

@ -0,0 +1,50 @@
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
*.jpg binary
*.png binary
*.gif binary
*.cs text=auto diff=csharp
*.vb text=auto
*.resx text=auto
*.c text=auto
*.cpp text=auto
*.cxx text=auto
*.h text=auto
*.hxx text=auto
*.py text=auto
*.rb text=auto
*.java text=auto
*.html text=auto
*.htm text=auto
*.css text=auto
*.scss text=auto
*.sass text=auto
*.less text=auto
*.js text=auto
*.lisp text=auto
*.clj text=auto
*.sql text=auto
*.php text=auto
*.lua text=auto
*.m text=auto
*.asm text=auto
*.erl text=auto
*.fs text=auto
*.fsx text=auto
*.hs text=auto
*.csproj text=auto
*.vbproj text=auto
*.fsproj text=auto
*.dbproj text=auto
*.sln text=auto eol=crlf

27
.gitignore vendored Normal file
View File

@ -0,0 +1,27 @@
[Oo]bj/
[Bb]in/
TestResults/
.nuget/
_ReSharper.*/
packages/
artifacts/
PublishProfiles/
*.user
*.suo
*.cache
*.docstates
_ReSharper.*
nuget.exe
*net45.csproj
*net451.csproj
*k10.csproj
*.psess
*.vsp
*.pidb
*.userprefs
*DS_Store
*.ncrunchsolution
*.*sdf
*.ipch
*.sln.ide
project.lock.json

9
.travis.yml Normal file
View File

@ -0,0 +1,9 @@
language: csharp
sudo: false
env:
- MONO_THREADS_PER_CPU=2000
os:
- linux
- osx
script:
- ./build.sh --quiet verify

7
NuGet.config Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="AspNetVNext" value="https://www.myget.org/F/aspnetcidev/api/v3/index.json" />
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

7
appveyor.yml Normal file
View File

@ -0,0 +1,7 @@
init:
- git config --global core.autocrlf true
build_script:
- build.cmd --quiet verify
clone_depth: 1
test: off
deploy: off

39
build.cmd Normal file
View File

@ -0,0 +1,39 @@
@echo off
cd %~dp0
SETLOCAL
SET NUGET_VERSION=latest
SET CACHED_NUGET=%LocalAppData%\NuGet\nuget.%NUGET_VERSION%.exe
SET BUILDCMD_KOREBUILD_VERSION=""
SET BUILDCMD_DNX_VERSION=""
IF EXIST %CACHED_NUGET% goto copynuget
echo Downloading latest version of NuGet.exe...
IF NOT EXIST %LocalAppData%\NuGet md %LocalAppData%\NuGet
@powershell -NoProfile -ExecutionPolicy unrestricted -Command "$ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest 'https://dist.nuget.org/win-x86-commandline/%NUGET_VERSION%/nuget.exe' -OutFile '%CACHED_NUGET%'"
:copynuget
IF EXIST .nuget\nuget.exe goto restore
md .nuget
copy %CACHED_NUGET% .nuget\nuget.exe > nul
:restore
IF EXIST packages\KoreBuild goto run
IF %BUILDCMD_KOREBUILD_VERSION%=="" (
.nuget\nuget.exe install KoreBuild -ExcludeVersion -o packages -nocache -pre
) ELSE (
.nuget\nuget.exe install KoreBuild -version %BUILDCMD_KOREBUILD_VERSION% -ExcludeVersion -o packages -nocache -pre
)
.nuget\nuget.exe install Sake -ExcludeVersion -Out packages
IF "%SKIP_DNX_INSTALL%"=="1" goto run
IF %BUILDCMD_DNX_VERSION%=="" (
CALL packages\KoreBuild\build\dnvm upgrade -runtime CLR -arch x86
) ELSE (
CALL packages\KoreBuild\build\dnvm install %BUILDCMD_DNX_VERSION% -runtime CLR -arch x86 -a default
)
CALL packages\KoreBuild\build\dnvm install default -runtime CoreCLR -arch x86
:run
CALL packages\KoreBuild\build\dnvm use default -runtime CLR -arch x86
packages\Sake\tools\Sake.exe -I packages\KoreBuild\build -f makefile.shade %*

41
build.sh Executable file
View File

@ -0,0 +1,41 @@
#!/usr/bin/env bash
if test `uname` = Darwin; then
cachedir=~/Library/Caches/KBuild
else
if [ -z $XDG_DATA_HOME ]; then
cachedir=$HOME/.local/share
else
cachedir=$XDG_DATA_HOME;
fi
fi
mkdir -p $cachedir
nugetVersion=latest
cachePath=$cachedir/nuget.$nugetVersion.exe
url=https://dist.nuget.org/win-x86-commandline/$nugetVersion/nuget.exe
if test ! -f $cachePath; then
wget -O $cachePath $url 2>/dev/null || curl -o $cachePath --location $url /dev/null
fi
if test ! -e .nuget; then
mkdir .nuget
cp $cachePath .nuget/nuget.exe
fi
if test ! -d packages/KoreBuild; then
mono .nuget/nuget.exe install KoreBuild -ExcludeVersion -o packages -nocache -pre
mono .nuget/nuget.exe install Sake -ExcludeVersion -Out packages
fi
if ! type dnvm > /dev/null 2>&1; then
source packages/KoreBuild/build/dnvm.sh
fi
if ! type dnx > /dev/null 2>&1; then
dnvm upgrade
fi
mono packages/Sake/tools/Sake.exe -I packages/KoreBuild/build -f makefile.shade "$@"

49
dnx-watch.sln Normal file
View File

@ -0,0 +1,49 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.23107.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{66517987-2A5A-4330-B130-207039378FD4}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Dnx.Watcher", "src\Microsoft.Dnx.Watcher\Microsoft.Dnx.Watcher.xproj", "{8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Dnx.Watcher.Core", "src\Microsoft.Dnx.Watcher.Core\Microsoft.Dnx.Watcher.Core.xproj", "{D3DA3BBB-E206-404F-AEE6-17FB9B6F1221}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8321E0D1-9A47-4D2F-AED8-3AE636D44E35}"
ProjectSection(SolutionItems) = preProject
global.json = global.json
NuGet.Config = NuGet.Config
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{442A6A17-4C5A-4E11-B547-A554063FD338}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Dnx.Watcher.Tests", "test\Microsoft.Dnx.Watcher.Tests\Microsoft.Dnx.Watcher.Tests.xproj", "{640D190B-26DB-4DDE-88EE-55814C86C43E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8A8CEABC-AC47-43FF-A5DF-69224F7E1F46}.Release|Any CPU.Build.0 = Release|Any CPU
{D3DA3BBB-E206-404F-AEE6-17FB9B6F1221}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D3DA3BBB-E206-404F-AEE6-17FB9B6F1221}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D3DA3BBB-E206-404F-AEE6-17FB9B6F1221}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D3DA3BBB-E206-404F-AEE6-17FB9B6F1221}.Release|Any CPU.Build.0 = Release|Any CPU
{640D190B-26DB-4DDE-88EE-55814C86C43E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{640D190B-26DB-4DDE-88EE-55814C86C43E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{640D190B-26DB-4DDE-88EE-55814C86C43E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{640D190B-26DB-4DDE-88EE-55814C86C43E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{8A8CEABC-AC47-43FF-A5DF-69224F7E1F46} = {66517987-2A5A-4330-B130-207039378FD4}
{D3DA3BBB-E206-404F-AEE6-17FB9B6F1221} = {66517987-2A5A-4330-B130-207039378FD4}
{640D190B-26DB-4DDE-88EE-55814C86C43E} = {442A6A17-4C5A-4E11-B547-A554063FD338}
EndGlobalSection
EndGlobal

3
global.json Normal file
View File

@ -0,0 +1,3 @@
{
"projects": [ "src"]
}

7
makefile.shade Normal file
View File

@ -0,0 +1,7 @@
var VERSION='0.1'
var FULL_VERSION='0.1'
var AUTHORS='Microsoft Open Technologies, Inc.'
use-standard-lifecycle
k-standard-goals

View File

@ -0,0 +1,18 @@
// 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;
namespace Microsoft.Dnx.Watcher.Core
{
public interface IFileWatcher : IDisposable
{
event Action<string> OnChanged;
void WatchDirectory(string path, string extension);
bool WatchFile(string path);
void WatchProject(string path);
}
}

View File

@ -0,0 +1,15 @@
// 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.Threading;
using System.Threading.Tasks;
namespace Microsoft.Dnx.Watcher.Core
{
public interface IProcessWatcher
{
int Start(string executable, string arguments, string workingDir);
Task<int> WaitForExitAsync(CancellationToken cancellationToken);
}
}

View File

@ -0,0 +1,16 @@
// 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.Collections.Generic;
namespace Microsoft.Dnx.Watcher.Core
{
public interface IProject
{
string ProjectFile { get; }
IEnumerable<string> Files { get; }
IEnumerable<string> ProjectDependencies { get; }
}
}

View File

@ -0,0 +1,10 @@
// 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.
namespace Microsoft.Dnx.Watcher.Core
{
public interface IProjectProvider
{
bool TryReadProject(string projectFile, out IProject project, out string errors);
}
}

View File

@ -0,0 +1,23 @@
// 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.
namespace System.Collections.Generic
{
internal static class DictionaryExtensions
{
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, Func<TKey, TValue> factory)
{
lock (dictionary)
{
TValue value;
if (!dictionary.TryGetValue(key, out value))
{
value = factory(key);
dictionary[key] = value;
}
return value;
}
}
}
}

View File

@ -0,0 +1,225 @@
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.Framework.Logging;
namespace Microsoft.Dnx.Watcher.Core
{
public class DnxWatcher
{
private readonly Func<string, IFileWatcher> _fileWatcherFactory;
private readonly Func<IProcessWatcher> _processWatcherFactory;
private readonly IProjectProvider _projectProvider;
private readonly ILoggerFactory _loggerFactory;
private readonly ILogger _logger;
public DnxWatcher(
Func<string, IFileWatcher> fileWatcherFactory,
Func<IProcessWatcher> processWatcherFactory,
IProjectProvider projectProvider,
ILoggerFactory loggerFactory)
{
_fileWatcherFactory = fileWatcherFactory;
_processWatcherFactory = processWatcherFactory;
_projectProvider = projectProvider;
_loggerFactory = loggerFactory;
_logger = _loggerFactory.CreateLogger(nameof(DnxWatcher));
}
public async Task WatchAsync(string projectFile, string[] dnxArguments, string workingDir, CancellationToken cancellationToken)
{
dnxArguments = new string[] { "--project", projectFile }
.Concat(dnxArguments)
.Select(arg =>
{
// If the argument has spaces, make sure we quote it
if (arg.Contains(" ") || arg.Contains("\t"))
{
return $"\"{arg}\"";
}
return arg;
})
.ToArray();
var dnxArgumentsAsString = string.Join(" ", dnxArguments);
while (true)
{
var project = await WaitForValidProjectJsonAsync(projectFile, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
using (var currentRunCancellationSource = new CancellationTokenSource())
using (var combinedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(
cancellationToken,
currentRunCancellationSource.Token))
{
var fileWatchingTask = WaitForProjectFileToChangeAsync(project, combinedCancellationSource.Token);
var dnxTask = WaitForDnxToExitAsync(dnxArgumentsAsString, workingDir, combinedCancellationSource.Token);
var tasksToWait = new Task[] { dnxTask, fileWatchingTask };
int finishedTaskIndex = Task.WaitAny(tasksToWait, cancellationToken);
// Regardless of the outcome, make sure everything is cancelled
// and wait for dnx to exit. We don't want orphan processes
currentRunCancellationSource.Cancel();
Task.WaitAll(tasksToWait);
cancellationToken.ThrowIfCancellationRequested();
if (finishedTaskIndex == 0)
{
// This is the dnx task
var dnxExitCode = dnxTask.Result;
if (dnxExitCode == 0)
{
_logger.LogInformation($"dnx exit code: {dnxExitCode}");
}
else
{
_logger.LogError($"dnx exit code: {dnxExitCode}");
}
_logger.LogInformation("Waiting for a file to change before restarting dnx...");
// Now wait for a file to change before restarting dnx
await WaitForProjectFileToChangeAsync(project, cancellationToken);
}
else
{
// This is a file watcher task
string changedFile = fileWatchingTask.Result;
_logger.LogInformation($"File changed: {fileWatchingTask.Result}");
}
}
}
}
private async Task<string> WaitForProjectFileToChangeAsync(IProject project, CancellationToken cancellationToken)
{
using (var fileWatcher = _fileWatcherFactory(Path.GetDirectoryName(project.ProjectFile)))
{
AddProjectAndDependeciesToWatcher(project, fileWatcher);
return await WatchForFileChangeAsync(fileWatcher, cancellationToken);
}
}
private Task<int> WaitForDnxToExitAsync(string dnxArguments, string workingDir, CancellationToken cancellationToken)
{
_logger.LogInformation($"Running dnx with the following arguments: {dnxArguments}");
var dnxWatcher = _processWatcherFactory();
int dnxProcessId = dnxWatcher.Start("dnx", dnxArguments, workingDir);
_logger.LogInformation($"dnx process id: {dnxProcessId}");
return dnxWatcher.WaitForExitAsync(cancellationToken);
}
private async Task<IProject> WaitForValidProjectJsonAsync(string projectFile, CancellationToken cancellationToken)
{
IProject project = null;
while (true)
{
string errors;
if (_projectProvider.TryReadProject(projectFile, out project, out errors))
{
return project;
}
_logger.LogError($"Error(s) reading project file '{projectFile}': ");
_logger.LogError(errors);
_logger.LogInformation("Fix the error to continue.");
using (var fileWatcher = _fileWatcherFactory(Path.GetDirectoryName(projectFile)))
{
fileWatcher.WatchFile(projectFile);
fileWatcher.WatchProject(projectFile);
await WatchForFileChangeAsync(fileWatcher, cancellationToken);
if (cancellationToken.IsCancellationRequested)
{
return null;
}
_logger.LogInformation($"File changed: {projectFile}");
}
}
}
private void AddProjectAndDependeciesToWatcher(string projectFile, IFileWatcher fileWatcher)
{
IProject project;
string errors;
if (_projectProvider.TryReadProject(projectFile, out project, out errors))
{
AddProjectAndDependeciesToWatcher(project, fileWatcher);
}
}
private void AddProjectAndDependeciesToWatcher(IProject project, IFileWatcher fileWatcher)
{
foreach (var file in project.Files)
{
if (!string.IsNullOrEmpty(file))
{
fileWatcher.WatchDirectory(
Path.GetDirectoryName(file),
Path.GetExtension(file));
}
}
fileWatcher.WatchProject(project.ProjectFile);
foreach (var projFile in project.ProjectDependencies)
{
//var fullProjectFilePath = Path.Combine(Path.GetDirectoryName(project.ProjectFile), projFile);
AddProjectAndDependeciesToWatcher(projFile, fileWatcher);
}
}
private Task<string> WatchForFileChangeAsync(IFileWatcher fileWatcher, CancellationToken cancellationToken)
{
return Task.Run(() =>
{
using (var fileChangeEvent = new ManualResetEvent(false))
{
string changedFile = null;
fileWatcher.OnChanged += path =>
{
changedFile = path;
fileChangeEvent.Set();
};
while (!cancellationToken.IsCancellationRequested &&
!fileChangeEvent.WaitOne(500))
{
}
return changedFile;
}
});
}
public static DnxWatcher CreateDefault(ILoggerFactory loggerFactory)
{
return new DnxWatcher(
fileWatcherFactory: root => { return new FileWatcher(root); },
processWatcherFactory: () => { return new ProcessWatcher(); },
projectProvider: new ProjectProvider(),
loggerFactory: loggerFactory);
}
}
}

View File

@ -0,0 +1,34 @@
// 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;
namespace Microsoft.Dnx.Runtime
{
internal static class Constants
{
public const string BootstrapperExeName = "dnx";
public const string BootstrapperFullName = "Microsoft .NET Execution environment";
public const string DefaultLocalRuntimeHomeDir = ".dnx";
public const string RuntimeShortName = "dnx";
public const string RuntimeLongName = "Microsoft DNX";
public const string RuntimeNamePrefix = RuntimeShortName + "-";
public const string WebConfigRuntimeVersion = RuntimeNamePrefix + "version";
public const string WebConfigRuntimeFlavor = RuntimeNamePrefix + "clr";
public const string WebConfigRuntimeAppBase = RuntimeNamePrefix + "app-base";
public const string WebConfigBootstrapperVersion = "bootstrapper-version";
public const string WebConfigRuntimePath = "runtime-path";
public const string BootstrapperHostName = RuntimeShortName + ".host";
public const string BootstrapperClrName = RuntimeShortName + ".clr";
public const int LockFileVersion = 2;
public static readonly TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(10);
public static readonly string AppConfigurationFileName = "app.config";
public static readonly Version Version35 = new Version(3, 5);
public static readonly Version Version40 = new Version(4, 0);
public static readonly Version Version50 = new Version(5, 0);
public static readonly Version Version10_0 = new Version(10, 0);
}
}

View File

@ -0,0 +1,123 @@
// 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 Microsoft.Dnx.Runtime.Json;
namespace Microsoft.Dnx.Runtime
{
internal sealed class FileFormatException : Exception
{
private FileFormatException(string message) :
base(message)
{
}
private FileFormatException(string message, Exception innerException) :
base(message, innerException)
{
}
public string Path { get; private set; }
public int Line { get; private set; }
public int Column { get; private set; }
public override string ToString()
{
return $"{Path}({Line},{Column}): Error: {base.ToString()}";
}
internal static FileFormatException Create(Exception exception, string filePath)
{
if (exception is JsonDeserializerException)
{
return new FileFormatException(exception.Message, exception)
.WithFilePath(filePath)
.WithLineInfo((JsonDeserializerException)exception);
}
else
{
return new FileFormatException(exception.Message, exception)
.WithFilePath(filePath);
}
}
internal static FileFormatException Create(Exception exception, JsonValue jsonValue, string filePath)
{
var result = Create(exception, jsonValue)
.WithFilePath(filePath);
return result;
}
internal static FileFormatException Create(Exception exception, JsonValue jsonValue)
{
var result = new FileFormatException(exception.Message, exception)
.WithLineInfo(jsonValue);
return result;
}
internal static FileFormatException Create(string message, JsonValue jsonValue, string filePath)
{
var result = Create(message, jsonValue)
.WithFilePath(filePath);
return result;
}
internal static FileFormatException Create(string message, string filePath)
{
var result = new FileFormatException(message)
.WithFilePath(filePath);
return result;
}
internal static FileFormatException Create(string message, JsonValue jsonValue)
{
var result = new FileFormatException(message)
.WithLineInfo(jsonValue);
return result;
}
internal FileFormatException WithFilePath(string path)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
Path = path;
return this;
}
private FileFormatException WithLineInfo(JsonValue value)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
Line = value.Line;
Column = value.Column;
return this;
}
private FileFormatException WithLineInfo(JsonDeserializerException exception)
{
if (exception == null)
{
throw new ArgumentNullException(nameof(exception));
}
Line = exception.Line;
Column = exception.Column;
return this;
}
}
}

View File

@ -0,0 +1,16 @@
// 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.Collections.Generic;
using System.Linq;
namespace Microsoft.Dnx.Runtime
{
public class LockFile
{
public int Version { get; set; }
public IList<LockFileProjectLibrary> ProjectLibraries { get; set; } = new List<LockFileProjectLibrary>();
}
}

View File

@ -0,0 +1,12 @@
// 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.
namespace Microsoft.Dnx.Runtime
{
public class LockFileProjectLibrary
{
public string Name { get; set; }
public string Path { get; set; }
}
}

View File

@ -0,0 +1,220 @@
// 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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;
using System.Threading;
using Microsoft.Dnx.Runtime.Json;
using NuGet;
namespace Microsoft.Dnx.Runtime
{
internal class LockFileReader
{
public const string LockFileName = "project.lock.json";
public LockFile Read(string filePath)
{
using (var stream = OpenFileStream(filePath))
{
try
{
return Read(stream);
}
catch (FileFormatException ex)
{
throw ex.WithFilePath(filePath);
}
catch (Exception ex)
{
throw FileFormatException.Create(ex, filePath);
}
}
}
private static FileStream OpenFileStream(string filePath)
{
// Retry 3 times before re-throw the exception.
// It mitigates the race condition when DTH read lock file while VS is restoring projects.
int retry = 3;
while (true)
{
try
{
return new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
catch (Exception)
{
if (retry > 0)
{
retry--;
Thread.Sleep(100);
}
else
{
throw;
}
}
}
}
internal LockFile Read(Stream stream)
{
try
{
var reader = new StreamReader(stream);
var jobject = JsonDeserializer.Deserialize(reader) as JsonObject;
if (jobject != null)
{
return ReadLockFile(jobject);
}
else
{
throw new InvalidDataException();
}
}
catch
{
// Ran into parsing errors, mark it as unlocked and out-of-date
return new LockFile
{
Version = int.MinValue
};
}
}
private LockFile ReadLockFile(JsonObject cursor)
{
var lockFile = new LockFile();
lockFile.Version = ReadInt(cursor, "version", defaultValue: int.MinValue);
ReadLibrary(cursor.ValueAsJsonObject("libraries"), lockFile);
return lockFile;
}
private void ReadLibrary(JsonObject json, LockFile lockFile)
{
if (json == null)
{
return;
}
foreach (var key in json.Keys)
{
var value = json.ValueAsJsonObject(key);
if (value == null)
{
throw FileFormatException.Create("The value type is not object.", json.Value(key));
}
var parts = key.Split(new[] { '/' }, 2);
var name = parts[0];
var version = parts.Length == 2 ? SemanticVersion.Parse(parts[1]) : null;
var type = value.ValueAsString("type")?.Value;
if (type == "project")
{
lockFile.ProjectLibraries.Add(new LockFileProjectLibrary
{
Name = name,
Path = ReadString(value.Value("path"))
});
}
}
}
private string ReadFrameworkAssemblyReference(JsonValue json)
{
return ReadString(json);
}
private IList<TItem> ReadArray<TItem>(JsonValue json, Func<JsonValue, TItem> readItem)
{
if (json == null)
{
return new List<TItem>();
}
var jarray = json as JsonArray;
if (jarray == null)
{
throw FileFormatException.Create("The value type is not array.", json);
}
var items = new List<TItem>();
for (int i = 0; i < jarray.Length; ++i)
{
items.Add(readItem(jarray[i]));
}
return items;
}
private IList<TItem> ReadObject<TItem>(JsonObject json, Func<string, JsonValue, TItem> readItem)
{
if (json == null)
{
return new List<TItem>();
}
var items = new List<TItem>();
foreach (var childKey in json.Keys)
{
items.Add(readItem(childKey, json.Value(childKey)));
}
return items;
}
private bool ReadBool(JsonObject cursor, string property, bool defaultValue)
{
var valueToken = cursor.Value(property) as JsonBoolean;
if (valueToken == null)
{
return defaultValue;
}
return valueToken.Value;
}
private int ReadInt(JsonObject cursor, string property, int defaultValue)
{
var number = cursor.Value(property) as JsonNumber;
if (number == null)
{
return defaultValue;
}
try
{
var resultInInt = Convert.ToInt32(number.Raw);
return resultInInt;
}
catch (Exception ex)
{
// FormatException or OverflowException
throw FileFormatException.Create(ex, cursor);
}
}
private string ReadString(JsonValue json)
{
if (json is JsonString)
{
return (json as JsonString).Value;
}
else if (json is JsonNull)
{
return null;
}
else
{
throw FileFormatException.Create("The value type is not string.", json);
}
}
}
}

View File

@ -0,0 +1,75 @@
// 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.Collections.Generic;
using System.IO;
using Microsoft.Dnx.Runtime.Json;
namespace Microsoft.Dnx.Runtime
{
internal static class NamedResourceReader
{
public static IDictionary<string, string> ReadNamedResources(JsonObject rawProject, string projectFilePath)
{
if (!rawProject.Keys.Contains("namedResource"))
{
return new Dictionary<string, string>();
}
var namedResourceToken = rawProject.ValueAsJsonObject("namedResource");
if (namedResourceToken == null)
{
throw FileFormatException.Create("Value must be object.", rawProject.Value("namedResource"), projectFilePath);
}
var namedResources = new Dictionary<string, string>();
foreach (var namedResourceKey in namedResourceToken.Keys)
{
var resourcePath = namedResourceToken.ValueAsString(namedResourceKey);
if (resourcePath == null)
{
throw FileFormatException.Create("Value must be string.", namedResourceToken.Value(namedResourceKey), projectFilePath);
}
if (resourcePath.Value.Contains("*"))
{
throw FileFormatException.Create("Value cannot contain wildcards.", resourcePath, projectFilePath);
}
var resourceFileFullPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(projectFilePath), resourcePath));
if (namedResources.ContainsKey(namedResourceKey))
{
throw FileFormatException.Create(
string.Format("The named resource {0} already exists.", namedResourceKey),
resourcePath,
projectFilePath);
}
namedResources.Add(
namedResourceKey,
resourceFileFullPath);
}
return namedResources;
}
public static void ApplyNamedResources(IDictionary<string, string> namedResources, IDictionary<string, string> resources)
{
foreach (var namedResource in namedResources)
{
// The named resources dictionary is like the project file
// key = name, value = path to resource
if (resources.ContainsKey(namedResource.Value))
{
resources[namedResource.Value] = namedResource.Key;
}
else
{
resources.Add(namedResource.Value, namedResource.Key);
}
}
}
}
}

View File

@ -0,0 +1,45 @@
// 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.Linq;
using Microsoft.Dnx.Runtime.Json;
namespace Microsoft.Dnx.Runtime
{
public class PackIncludeEntry
{
public string Target { get; }
public string[] SourceGlobs { get; }
public int Line { get; }
public int Column { get; }
internal PackIncludeEntry(string target, JsonValue json)
: this(target, ExtractValues(json), json.Line, json.Column)
{
}
public PackIncludeEntry(string target, string[] sourceGlobs, int line, int column)
{
Target = target;
SourceGlobs = sourceGlobs;
Line = line;
Column = column;
}
private static string[] ExtractValues(JsonValue json)
{
var valueAsString = json as JsonString;
if (valueAsString != null)
{
return new string[] { valueAsString.Value };
}
var valueAsArray = json as JsonArray;
if(valueAsArray != null)
{
return valueAsArray.Values.Select(v => v.ToString()).ToArray();
}
return new string[0];
}
}
}

View File

@ -0,0 +1,196 @@
// 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;
namespace NuGet
{
internal static class PathUtility
{
public static bool IsChildOfDirectory(string dir, string candidate)
{
if (dir == null)
{
throw new ArgumentNullException(nameof(dir));
}
if (candidate == null)
{
throw new ArgumentNullException(nameof(candidate));
}
dir = Path.GetFullPath(dir);
dir = EnsureTrailingSlash(dir);
candidate = Path.GetFullPath(candidate);
return candidate.StartsWith(dir, StringComparison.OrdinalIgnoreCase);
}
public static string EnsureTrailingSlash(string path)
{
return EnsureTrailingCharacter(path, Path.DirectorySeparatorChar);
}
public static string EnsureTrailingForwardSlash(string path)
{
return EnsureTrailingCharacter(path, '/');
}
private static string EnsureTrailingCharacter(string path, char trailingCharacter)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
// if the path is empty, we want to return the original string instead of a single trailing character.
if (path.Length == 0 || path[path.Length - 1] == trailingCharacter)
{
return path;
}
return path + trailingCharacter;
}
public static void EnsureParentDirectory(string filePath)
{
string directory = Path.GetDirectoryName(filePath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
}
/// <summary>
/// Returns path2 relative to path1, with Path.DirectorySeparatorChar as separator
/// </summary>
public static string GetRelativePath(string path1, string path2)
{
return GetRelativePath(path1, path2, Path.DirectorySeparatorChar);
}
/// <summary>
/// Returns path2 relative to path1, with given path separator
/// </summary>
public static string GetRelativePath(string path1, string path2, char separator)
{
if (string.IsNullOrEmpty(path1))
{
throw new ArgumentException("Path must have a value", nameof(path1));
}
if (string.IsNullOrEmpty(path2))
{
throw new ArgumentException("Path must have a value", nameof(path2));
}
StringComparison compare;
if (Microsoft.Dnx.Runtime.RuntimeEnvironmentHelper.IsWindows)
{
compare = StringComparison.OrdinalIgnoreCase;
// check if paths are on the same volume
if (!string.Equals(Path.GetPathRoot(path1), Path.GetPathRoot(path2)))
{
// on different volumes, "relative" path is just path2
return path2;
}
}
else
{
compare = StringComparison.Ordinal;
}
var index = 0;
var path1Segments = path1.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
var path2Segments = path2.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
// if path1 does not end with / it is assumed the end is not a directory
// we will assume that is isn't a directory by ignoring the last split
var len1 = path1Segments.Length - 1;
var len2 = path2Segments.Length;
// find largest common absolute path between both paths
var min = Math.Min(len1, len2);
while (min > index)
{
if (!string.Equals(path1Segments[index], path2Segments[index], compare))
{
break;
}
// Handle scenarios where folder and file have same name (only if os supports same name for file and directory)
// e.g. /file/name /file/name/app
else if ((len1 == index && len2 > index + 1) || (len1 > index && len2 == index + 1))
{
break;
}
++index;
}
var path = "";
// check if path2 ends with a non-directory separator and if path1 has the same non-directory at the end
if (len1 + 1 == len2 && !string.IsNullOrEmpty(path1Segments[index]) &&
string.Equals(path1Segments[index], path2Segments[index], compare))
{
return path;
}
for (var i = index; len1 > i; ++i)
{
path += ".." + separator;
}
for (var i = index; len2 - 1 > i; ++i)
{
path += path2Segments[i] + separator;
}
// if path2 doesn't end with an empty string it means it ended with a non-directory name, so we add it back
if (!string.IsNullOrEmpty(path2Segments[len2 - 1]))
{
path += path2Segments[len2 - 1];
}
return path;
}
public static string GetAbsolutePath(string basePath, string relativePath)
{
if (basePath == null)
{
throw new ArgumentNullException(nameof(basePath));
}
if (relativePath == null)
{
throw new ArgumentNullException(nameof(relativePath));
}
Uri resultUri = new Uri(new Uri(basePath), new Uri(relativePath, UriKind.Relative));
return resultUri.LocalPath;
}
public static string GetDirectoryName(string path)
{
path = path.TrimEnd(Path.DirectorySeparatorChar);
return path.Substring(Path.GetDirectoryName(path).Length).Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
}
public static string GetPathWithForwardSlashes(string path)
{
return path.Replace('\\', '/');
}
public static string GetPathWithBackSlashes(string path)
{
return path.Replace('/', '\\');
}
public static string GetPathWithDirectorySeparator(string path)
{
if (Path.DirectorySeparatorChar == '/')
{
return GetPathWithForwardSlashes(path);
}
else
{
return GetPathWithBackSlashes(path);
}
}
}
}

View File

@ -0,0 +1,123 @@
// 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.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Framework.FileSystemGlobbing;
using Microsoft.Dnx.Runtime.Json;
namespace Microsoft.Dnx.Runtime
{
public class PatternGroup
{
private readonly List<PatternGroup> _excludeGroups = new List<PatternGroup>();
private readonly Matcher _matcher = new Matcher();
internal PatternGroup(IEnumerable<string> includePatterns)
{
IncludeLiterals = Enumerable.Empty<string>();
IncludePatterns = includePatterns;
ExcludePatterns = Enumerable.Empty<string>();
_matcher.AddIncludePatterns(IncludePatterns);
}
internal PatternGroup(IEnumerable<string> includePatterns, IEnumerable<string> excludePatterns, IEnumerable<string> includeLiterals)
{
IncludeLiterals = includeLiterals;
IncludePatterns = includePatterns;
ExcludePatterns = excludePatterns;
_matcher.AddIncludePatterns(IncludePatterns);
_matcher.AddExcludePatterns(ExcludePatterns);
}
internal static PatternGroup Build(JsonObject rawProject,
string projectDirectory,
string projectFilePath,
string name,
IEnumerable<string> fallbackIncluding = null,
IEnumerable<string> additionalIncluding = null,
IEnumerable<string> additionalExcluding = null,
bool includePatternsOnly = false,
ICollection<DiagnosticMessage> warnings = null)
{
string includePropertyName = name;
additionalIncluding = additionalIncluding ?? Enumerable.Empty<string>();
var includePatterns = PatternsCollectionHelper.GetPatternsCollection(rawProject, projectDirectory, projectFilePath, includePropertyName, defaultPatterns: fallbackIncluding)
.Concat(additionalIncluding)
.Distinct();
if (includePatternsOnly)
{
return new PatternGroup(includePatterns);
}
additionalExcluding = additionalExcluding ?? Enumerable.Empty<string>();
var excludePatterns = PatternsCollectionHelper.GetPatternsCollection(rawProject, projectDirectory, projectFilePath, propertyName: name + "Exclude")
.Concat(additionalExcluding)
.Distinct();
var includeLiterals = PatternsCollectionHelper.GetPatternsCollection(rawProject, projectDirectory, projectFilePath, propertyName: name + "Files", literalPath: true)
.Distinct();
return new PatternGroup(includePatterns, excludePatterns, includeLiterals);
}
public IEnumerable<string> IncludeLiterals { get; }
public IEnumerable<string> IncludePatterns { get; }
public IEnumerable<string> ExcludePatterns { get; }
public IEnumerable<PatternGroup> ExcludePatternsGroup { get { return _excludeGroups; } }
public PatternGroup ExcludeGroup(PatternGroup group)
{
_excludeGroups.Add(group);
return this;
}
public IEnumerable<string> SearchFiles(string rootPath)
{
// literal included files are added at the last, but the search happens early
// so as to make the process fail early in case there is missing file. fail early
// helps to avoid unnecessary globing for performance optimization
var literalIncludedFiles = new List<string>();
foreach (var literalRelativePath in IncludeLiterals)
{
var fullPath = Path.GetFullPath(Path.Combine(rootPath, literalRelativePath));
if (!File.Exists(fullPath))
{
throw new InvalidOperationException(string.Format("Can't find file {0}", literalRelativePath));
}
// TODO: extract utility like NuGet.PathUtility.GetPathWithForwardSlashes()
literalIncludedFiles.Add(fullPath.Replace('\\', '/'));
}
// globing files
var globbingResults = _matcher.GetResultsInFullPath(rootPath);
// if there is no results generated in globing, skip excluding other groups
// for performance optimization.
if (globbingResults.Any())
{
foreach (var group in _excludeGroups)
{
globbingResults = globbingResults.Except(group.SearchFiles(rootPath));
}
}
return globbingResults.Concat(literalIncludedFiles).Distinct();
}
public override string ToString()
{
return string.Format("Pattern group: Literals [{0}] Includes [{1}] Excludes [{2}]", string.Join(", ", IncludeLiterals), string.Join(", ", IncludePatterns), string.Join(", ", ExcludePatterns));
}
}
}

View File

@ -0,0 +1,106 @@
// 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.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Dnx.Runtime.Json;
namespace Microsoft.Dnx.Runtime
{
internal static class PatternsCollectionHelper
{
private static readonly char[] PatternSeparator = new[] { ';' };
public static IEnumerable<string> GetPatternsCollection(JsonObject rawProject,
string projectDirectory,
string projectFilePath,
string propertyName,
IEnumerable<string> defaultPatterns = null,
bool literalPath = false)
{
defaultPatterns = defaultPatterns ?? Enumerable.Empty<string>();
try
{
if (!rawProject.Keys.Contains(propertyName))
{
return CreateCollection(projectDirectory, propertyName, defaultPatterns, literalPath);
}
var valueInString = rawProject.ValueAsString(propertyName);
if (valueInString != null)
{
return CreateCollection(projectDirectory, propertyName, new string[] { valueInString }, literalPath);
}
var valuesInArray = rawProject.ValueAsStringArray(propertyName);
if (valuesInArray != null)
{
return CreateCollection(projectDirectory, propertyName, valuesInArray.Select(s => s.ToString()), literalPath);
}
}
catch (Exception ex)
{
throw FileFormatException.Create(ex, rawProject.Value(propertyName), projectFilePath);
}
throw FileFormatException.Create("Value must be either string or array.", rawProject.Value(propertyName), projectFilePath);
}
private static IEnumerable<string> CreateCollection(string projectDirectory, string propertyName, IEnumerable<string> patternsStrings, bool literalPath)
{
var patterns = patternsStrings.SelectMany(patternsString => GetSourcesSplit(patternsString))
.Select(patternString => patternString.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar));
foreach (var pattern in patterns)
{
if (Path.IsPathRooted(pattern))
{
throw new InvalidOperationException($"The '{propertyName}' property cannot be a rooted path.");
}
if (literalPath && pattern.Contains('*'))
{
throw new InvalidOperationException($"The '{propertyName}' property cannot contain wildcard characters.");
}
}
return new List<string>(patterns.Select(pattern => FolderToPattern(pattern, projectDirectory)));
}
private static IEnumerable<string> GetSourcesSplit(string sourceDescription)
{
if (string.IsNullOrEmpty(sourceDescription))
{
return Enumerable.Empty<string>();
}
return sourceDescription.Split(PatternSeparator, StringSplitOptions.RemoveEmptyEntries);
}
private static string FolderToPattern(string candidate, string projectDir)
{
// This conversion is needed to support current template
// If it's already a pattern, no change is needed
if (candidate.Contains('*'))
{
return candidate;
}
// If the given string ends with a path separator, or it is an existing directory
// we convert this folder name to a pattern matching all files in the folder
if (candidate.EndsWith(@"\") ||
candidate.EndsWith("/") ||
Directory.Exists(Path.Combine(projectDir, candidate)))
{
return Path.Combine(candidate, "**", "*");
}
// Otherwise, it represents a single file
return candidate;
}
}
}

View File

@ -0,0 +1,126 @@
// 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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;
using NuGet;
namespace Microsoft.Dnx.Runtime
{
public class Project
{
public const string ProjectFileName = "project.json";
public Project()
{
}
public string ProjectFilePath { get; set; }
public string ProjectDirectory
{
get
{
return Path.GetDirectoryName(ProjectFilePath);
}
}
public string Name { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Copyright { get; set; }
public string Summary { get; set; }
public string Language { get; set; }
public string ReleaseNotes { get; set; }
public string[] Authors { get; set; }
public string[] Owners { get; set; }
public bool EmbedInteropTypes { get; set; }
public Version AssemblyFileVersion { get; set; }
public string WebRoot { get; set; }
public string EntryPoint { get; set; }
public string ProjectUrl { get; set; }
public string LicenseUrl { get; set; }
public string IconUrl { get; set; }
public bool RequireLicenseAcceptance { get; set; }
public string[] Tags { get; set; }
public bool IsLoadable { get; set; }
public ProjectFilesCollection Files { get; set; }
public IDictionary<string, string> Commands { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public IDictionary<string, IEnumerable<string>> Scripts { get; } = new Dictionary<string, IEnumerable<string>>(StringComparer.OrdinalIgnoreCase);
public static bool HasProjectFile(string path)
{
string projectPath = Path.Combine(path, ProjectFileName);
return File.Exists(projectPath);
}
public static bool TryGetProject(string path, out Project project, ICollection<DiagnosticMessage> diagnostics = null)
{
project = null;
string projectPath = null;
if (string.Equals(Path.GetFileName(path), ProjectFileName, StringComparison.OrdinalIgnoreCase))
{
projectPath = path;
path = Path.GetDirectoryName(path);
}
else if (!HasProjectFile(path))
{
return false;
}
else
{
projectPath = Path.Combine(path, ProjectFileName);
}
// Assume the directory name is the project name if none was specified
var projectName = PathUtility.GetDirectoryName(path);
projectPath = Path.GetFullPath(projectPath);
if (!File.Exists(projectPath))
{
return false;
}
try
{
using (var stream = File.OpenRead(projectPath))
{
var reader = new ProjectReader();
project = reader.ReadProject(stream, projectName, projectPath, diagnostics);
}
}
catch (Exception ex)
{
throw FileFormatException.Create(ex, projectPath);
}
return true;
}
}
}

View File

@ -0,0 +1,202 @@
// 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.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.Dnx.Runtime.Json;
namespace Microsoft.Dnx.Runtime
{
public class ProjectFilesCollection
{
public static readonly string[] DefaultCompileBuiltInPatterns = new[] { @"**/*.cs" };
public static readonly string[] DefaultPublishExcludePatterns = new[] { @"obj/**/*.*", @"bin/**/*.*", @"**/.*/**", @"**/global.json" };
public static readonly string[] DefaultPreprocessPatterns = new[] { @"compiler/preprocess/**/*.cs" };
public static readonly string[] DefaultSharedPatterns = new[] { @"compiler/shared/**/*.cs" };
public static readonly string[] DefaultResourcesBuiltInPatterns = new[] { @"compiler/resources/**/*", "**/*.resx" };
public static readonly string[] DefaultContentsBuiltInPatterns = new[] { @"**/*" };
public static readonly string[] DefaultBuiltInExcludePatterns = new[] { "bin/**", "obj/**", "**/*.xproj" };
public static readonly string PackIncludePropertyName = "packInclude";
private PatternGroup _sharedPatternsGroup;
private PatternGroup _resourcePatternsGroup;
private PatternGroup _preprocessPatternsGroup;
private PatternGroup _compilePatternsGroup;
private PatternGroup _contentPatternsGroup;
private IDictionary<string, string> _namedResources;
private IEnumerable<string> _publishExcludePatterns;
private IEnumerable<PackIncludeEntry> _packInclude;
private readonly string _projectDirectory;
private readonly string _projectFilePath;
private JsonObject _rawProject;
private bool _initialized;
internal ProjectFilesCollection(JsonObject rawProject, string projectDirectory, string projectFilePath)
{
_projectDirectory = projectDirectory;
_projectFilePath = projectFilePath;
_rawProject = rawProject;
}
internal void EnsureInitialized()
{
if (_initialized)
{
return;
}
var excludeBuiltIns = PatternsCollectionHelper.GetPatternsCollection(_rawProject, _projectDirectory, _projectFilePath, "excludeBuiltIn", DefaultBuiltInExcludePatterns);
var excludePatterns = PatternsCollectionHelper.GetPatternsCollection(_rawProject, _projectDirectory, _projectFilePath, "exclude")
.Concat(excludeBuiltIns);
var contentBuiltIns = PatternsCollectionHelper.GetPatternsCollection(_rawProject, _projectDirectory, _projectFilePath, "contentBuiltIn", DefaultContentsBuiltInPatterns);
var compileBuiltIns = PatternsCollectionHelper.GetPatternsCollection(_rawProject, _projectDirectory, _projectFilePath, "compileBuiltIn", DefaultCompileBuiltInPatterns);
var resourceBuiltIns = PatternsCollectionHelper.GetPatternsCollection(_rawProject, _projectDirectory, _projectFilePath, "resourceBuiltIn", DefaultResourcesBuiltInPatterns);
_publishExcludePatterns = PatternsCollectionHelper.GetPatternsCollection(_rawProject, _projectDirectory, _projectFilePath, "publishExclude", DefaultPublishExcludePatterns);
_sharedPatternsGroup = PatternGroup.Build(_rawProject, _projectDirectory, _projectFilePath, "shared", fallbackIncluding: DefaultSharedPatterns, additionalExcluding: excludePatterns);
_resourcePatternsGroup = PatternGroup.Build(_rawProject, _projectDirectory, _projectFilePath, "resource", additionalIncluding: resourceBuiltIns, additionalExcluding: excludePatterns);
_preprocessPatternsGroup = PatternGroup.Build(_rawProject, _projectDirectory, _projectFilePath, "preprocess", fallbackIncluding: DefaultPreprocessPatterns, additionalExcluding: excludePatterns)
.ExcludeGroup(_sharedPatternsGroup)
.ExcludeGroup(_resourcePatternsGroup);
_compilePatternsGroup = PatternGroup.Build(_rawProject, _projectDirectory, _projectFilePath, "compile", additionalIncluding: compileBuiltIns, additionalExcluding: excludePatterns)
.ExcludeGroup(_sharedPatternsGroup)
.ExcludeGroup(_preprocessPatternsGroup)
.ExcludeGroup(_resourcePatternsGroup);
_contentPatternsGroup = PatternGroup.Build(_rawProject, _projectDirectory, _projectFilePath, "content", additionalIncluding: contentBuiltIns, additionalExcluding: excludePatterns.Concat(_publishExcludePatterns))
.ExcludeGroup(_compilePatternsGroup)
.ExcludeGroup(_preprocessPatternsGroup)
.ExcludeGroup(_sharedPatternsGroup)
.ExcludeGroup(_resourcePatternsGroup);
_namedResources = NamedResourceReader.ReadNamedResources(_rawProject, _projectFilePath);
// Files to be packed along with the project
var packIncludeJson = _rawProject.ValueAsJsonObject(PackIncludePropertyName);
if (packIncludeJson != null)
{
_packInclude = packIncludeJson
.Keys
.Select(k => new PackIncludeEntry(k, packIncludeJson.Value(k)))
.ToList();
}
else
{
_packInclude = new List<PackIncludeEntry>();
}
_initialized = true;
_rawProject = null;
}
public IEnumerable<PackIncludeEntry> PackInclude
{
get
{
EnsureInitialized();
return _packInclude;
}
}
public IEnumerable<string> SourceFiles
{
get { return CompilePatternsGroup.SearchFiles(_projectDirectory).Distinct(); }
}
public IEnumerable<string> PreprocessSourceFiles
{
get { return PreprocessPatternsGroup.SearchFiles(_projectDirectory).Distinct(); }
}
public IDictionary<string, string> ResourceFiles
{
get
{
var resources = ResourcePatternsGroup
.SearchFiles(_projectDirectory)
.Distinct()
.ToDictionary(res => res, res => (string)null);
NamedResourceReader.ApplyNamedResources(_namedResources, resources);
return resources;
}
}
public IEnumerable<string> SharedFiles
{
get { return SharedPatternsGroup.SearchFiles(_projectDirectory).Distinct(); }
}
public IEnumerable<string> GetFilesForBundling(bool includeSource, IEnumerable<string> additionalExcludePatterns)
{
var patternGroup = new PatternGroup(ContentPatternsGroup.IncludePatterns,
ContentPatternsGroup.ExcludePatterns.Concat(additionalExcludePatterns),
ContentPatternsGroup.IncludeLiterals);
if (!includeSource)
{
foreach (var excludedGroup in ContentPatternsGroup.ExcludePatternsGroup)
{
patternGroup.ExcludeGroup(excludedGroup);
}
}
return patternGroup.SearchFiles(_projectDirectory);
}
internal PatternGroup CompilePatternsGroup
{
get
{
EnsureInitialized();
return _compilePatternsGroup;
}
}
internal PatternGroup SharedPatternsGroup
{
get
{
EnsureInitialized();
return _sharedPatternsGroup;
}
}
internal PatternGroup ResourcePatternsGroup
{
get
{
EnsureInitialized();
return _resourcePatternsGroup;
}
}
internal PatternGroup PreprocessPatternsGroup
{
get
{
EnsureInitialized();
return _preprocessPatternsGroup;
}
}
internal PatternGroup ContentPatternsGroup
{
get
{
EnsureInitialized();
return _contentPatternsGroup;
}
}
}
}

View File

@ -0,0 +1,144 @@
// 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.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Dnx.Runtime.Json;
using NuGet;
namespace Microsoft.Dnx.Runtime
{
public class ProjectReader
{
public Project ReadProject(Stream stream, string projectName, string projectPath, ICollection<DiagnosticMessage> diagnostics)
{
var project = new Project();
var reader = new StreamReader(stream);
var rawProject = JsonDeserializer.Deserialize(reader) as JsonObject;
if (rawProject == null)
{
throw FileFormatException.Create(
"The JSON file can't be deserialized to a JSON object.",
projectPath);
}
// Meta-data properties
project.Name = projectName;
project.ProjectFilePath = Path.GetFullPath(projectPath);
var version = rawProject.Value("version") as JsonString;
project.Description = rawProject.ValueAsString("description");
project.Summary = rawProject.ValueAsString("summary");
project.Copyright = rawProject.ValueAsString("copyright");
project.Title = rawProject.ValueAsString("title");
project.WebRoot = rawProject.ValueAsString("webroot");
project.EntryPoint = rawProject.ValueAsString("entryPoint");
project.ProjectUrl = rawProject.ValueAsString("projectUrl");
project.LicenseUrl = rawProject.ValueAsString("licenseUrl");
project.IconUrl = rawProject.ValueAsString("iconUrl");
project.Authors = rawProject.ValueAsStringArray("authors") ?? new string[] { };
project.Owners = rawProject.ValueAsStringArray("owners") ?? new string[] { };
project.Tags = rawProject.ValueAsStringArray("tags") ?? new string[] { };
project.Language = rawProject.ValueAsString("language");
project.ReleaseNotes = rawProject.ValueAsString("releaseNotes");
project.RequireLicenseAcceptance = rawProject.ValueAsBoolean("requireLicenseAcceptance", defaultValue: false);
project.IsLoadable = rawProject.ValueAsBoolean("loadable", defaultValue: true);
// TODO: Move this to the dependencies node
project.EmbedInteropTypes = rawProject.ValueAsBoolean("embedInteropTypes", defaultValue: false);
// Project files
project.Files = new ProjectFilesCollection(rawProject, project.ProjectDirectory, project.ProjectFilePath);
var commands = rawProject.Value("commands") as JsonObject;
if (commands != null)
{
foreach (var key in commands.Keys)
{
var value = commands.ValueAsString(key);
if (value != null)
{
project.Commands[key] = value;
}
}
}
var scripts = rawProject.Value("scripts") as JsonObject;
if (scripts != null)
{
foreach (var key in scripts.Keys)
{
var stringValue = scripts.ValueAsString(key);
if (stringValue != null)
{
project.Scripts[key] = new string[] { stringValue };
continue;
}
var arrayValue = scripts.ValueAsStringArray(key);
if (arrayValue != null)
{
project.Scripts[key] = arrayValue;
continue;
}
throw FileFormatException.Create(
string.Format("The value of a script in {0} can only be a string or an array of strings", Project.ProjectFileName),
scripts.Value(key),
project.ProjectFilePath);
}
}
return project;
}
private static SemanticVersion SpecifySnapshot(string version, string snapshotValue)
{
if (version.EndsWith("-*"))
{
if (string.IsNullOrEmpty(snapshotValue))
{
version = version.Substring(0, version.Length - 2);
}
else
{
version = version.Substring(0, version.Length - 1) + snapshotValue;
}
}
return new SemanticVersion(version);
}
private static bool TryGetStringEnumerable(JsonObject parent, string property, out IEnumerable<string> result)
{
var collection = new List<string>();
var valueInString = parent.ValueAsString(property);
if (valueInString != null)
{
collection.Add(valueInString);
}
else
{
var valueInArray = parent.ValueAsStringArray(property);
if (valueInArray != null)
{
collection.AddRange(valueInArray);
}
else
{
result = null;
return false;
}
}
result = collection.SelectMany(value => value.Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries));
return true;
}
}
}

View File

@ -0,0 +1,50 @@
// 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;
namespace Microsoft.Dnx.Runtime
{
internal static class RuntimeEnvironmentHelper
{
private static Lazy<bool> _isMono = new Lazy<bool>(() =>
_runtimeEnv.Value.RuntimeType == "Mono");
private static Lazy<bool> _isWindows = new Lazy<bool>(() =>
_runtimeEnv.Value.OperatingSystem == "Windows");
private static Lazy<IRuntimeEnvironment> _runtimeEnv = new Lazy<IRuntimeEnvironment>(() =>
GetRuntimeEnvironment());
private static IRuntimeEnvironment GetRuntimeEnvironment()
{
var provider = Infrastructure.CallContextServiceLocator.Locator.ServiceProvider;
var environment = (IRuntimeEnvironment)provider?.GetService(typeof(IRuntimeEnvironment));
if (environment == null)
{
throw new InvalidOperationException("Failed to resolve IRuntimeEnvironment");
}
return environment;
}
public static IRuntimeEnvironment RuntimeEnvironment
{
get
{
return _runtimeEnv.Value;
}
}
public static bool IsWindows
{
get { return _isWindows.Value; }
}
public static bool IsMono
{
get { return _isMono.Value; }
}
}
}

View File

@ -0,0 +1,330 @@
// 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.Text;
namespace NuGet
{
/// <summary>
/// A hybrid implementation of SemVer that supports semantic versioning as described at http://semver.org while not strictly enforcing it to
/// allow older 4-digit versioning schemes to continue working.
/// </summary>
internal sealed class SemanticVersion : IComparable, IComparable<SemanticVersion>, IEquatable<SemanticVersion>
{
private string _normalizedVersionString;
public SemanticVersion(string version)
: this(Parse(version))
{
}
public SemanticVersion(int major, int minor, int build, int revision)
: this(new Version(major, minor, build, revision))
{
}
public SemanticVersion(int major, int minor, int build, string specialVersion)
: this(new Version(major, minor, build), specialVersion)
{
}
public SemanticVersion(Version version)
: this(version, string.Empty)
{
}
public SemanticVersion(Version version, string specialVersion)
{
if (version == null)
{
throw new ArgumentNullException(nameof(version));
}
Version = NormalizeVersionValue(version);
SpecialVersion = specialVersion ?? string.Empty;
}
internal SemanticVersion(SemanticVersion semVer)
{
Version = semVer.Version;
SpecialVersion = semVer.SpecialVersion;
}
/// <summary>
/// Gets the normalized version portion.
/// </summary>
public Version Version
{
get;
private set;
}
/// <summary>
/// Gets the optional special version.
/// </summary>
public string SpecialVersion
{
get;
private set;
}
private static string[] SplitAndPadVersionString(string version)
{
string[] a = version.Split('.');
if (a.Length == 4)
{
return a;
}
else
{
// if 'a' has less than 4 elements, we pad the '0' at the end
// to make it 4.
var b = new string[4] { "0", "0", "0", "0" };
Array.Copy(a, 0, b, 0, a.Length);
return b;
}
}
/// <summary>
/// Parses a version string using loose semantic versioning rules that allows 2-4 version components followed by an optional special version.
/// </summary>
public static SemanticVersion Parse(string version)
{
if (string.IsNullOrEmpty(version))
{
throw new ArgumentNullException(nameof(version));
}
SemanticVersion semVer;
if (!TryParse(version, out semVer))
{
throw new ArgumentException(nameof(version));
}
return semVer;
}
/// <summary>
/// Parses a version string using loose semantic versioning rules that allows 2-4 version components followed by an optional special version.
/// </summary>
public static bool TryParse(string version, out SemanticVersion value)
{
return TryParseInternal(version, strict: false, semVer: out value);
}
/// <summary>
/// Parses a version string using strict semantic versioning rules that allows exactly 3 components and an optional special version.
/// </summary>
public static bool TryParseStrict(string version, out SemanticVersion value)
{
return TryParseInternal(version, strict: true, semVer: out value);
}
private static bool TryParseInternal(string version, bool strict, out SemanticVersion semVer)
{
semVer = null;
if (string.IsNullOrEmpty(version))
{
return false;
}
version = version.Trim();
var versionPart = version;
string specialVersion = string.Empty;
if (version.IndexOf('-') != -1)
{
var parts = version.Split(new char[] { '-' }, 2, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2)
{
return false;
}
versionPart = parts[0];
specialVersion = parts[1];
}
Version versionValue;
if (!Version.TryParse(versionPart, out versionValue))
{
return false;
}
if (strict)
{
// Must have major, minor and build only.
if (versionValue.Major == -1 ||
versionValue.Minor == -1 ||
versionValue.Build == -1 ||
versionValue.Revision != -1)
{
return false;
}
}
semVer = new SemanticVersion(NormalizeVersionValue(versionValue), specialVersion);
return true;
}
/// <summary>
/// Attempts to parse the version token as a SemanticVersion.
/// </summary>
/// <returns>An instance of SemanticVersion if it parses correctly, null otherwise.</returns>
public static SemanticVersion ParseOptionalVersion(string version)
{
SemanticVersion semVer;
TryParse(version, out semVer);
return semVer;
}
private static Version NormalizeVersionValue(Version version)
{
return new Version(version.Major,
version.Minor,
Math.Max(version.Build, 0),
Math.Max(version.Revision, 0));
}
public int CompareTo(object obj)
{
if (Object.ReferenceEquals(obj, null))
{
return 1;
}
SemanticVersion other = obj as SemanticVersion;
if (other == null)
{
throw new ArgumentException(nameof(obj));
}
return CompareTo(other);
}
public int CompareTo(SemanticVersion other)
{
if (Object.ReferenceEquals(other, null))
{
return 1;
}
int result = Version.CompareTo(other.Version);
if (result != 0)
{
return result;
}
bool empty = string.IsNullOrEmpty(SpecialVersion);
bool otherEmpty = string.IsNullOrEmpty(other.SpecialVersion);
if (empty && otherEmpty)
{
return 0;
}
else if (empty)
{
return 1;
}
else if (otherEmpty)
{
return -1;
}
return StringComparer.OrdinalIgnoreCase.Compare(SpecialVersion, other.SpecialVersion);
}
public static bool operator ==(SemanticVersion version1, SemanticVersion version2)
{
if (Object.ReferenceEquals(version1, null))
{
return Object.ReferenceEquals(version2, null);
}
return version1.Equals(version2);
}
public static bool operator !=(SemanticVersion version1, SemanticVersion version2)
{
return !(version1 == version2);
}
public static bool operator <(SemanticVersion version1, SemanticVersion version2)
{
if (version1 == null)
{
throw new ArgumentNullException(nameof(version1));
}
return version1.CompareTo(version2) < 0;
}
public static bool operator <=(SemanticVersion version1, SemanticVersion version2)
{
return (version1 == version2) || (version1 < version2);
}
public static bool operator >(SemanticVersion version1, SemanticVersion version2)
{
if (version1 == null)
{
throw new ArgumentNullException(nameof(version1));
}
return version2 < version1;
}
public static bool operator >=(SemanticVersion version1, SemanticVersion version2)
{
return (version1 == version2) || (version1 > version2);
}
public override string ToString()
{
if (_normalizedVersionString == null)
{
var builder = new StringBuilder();
builder
.Append(Version.Major)
.Append('.')
.Append(Version.Minor)
.Append('.')
.Append(Math.Max(0, Version.Build));
if (Version.Revision > 0)
{
builder
.Append('.')
.Append(Version.Revision);
}
if (!string.IsNullOrEmpty(SpecialVersion))
{
builder
.Append('-')
.Append(SpecialVersion);
}
_normalizedVersionString = builder.ToString();
}
return _normalizedVersionString;
}
public bool Equals(SemanticVersion other)
{
return !Object.ReferenceEquals(null, other) &&
Version.Equals(other.Version) &&
SpecialVersion.Equals(other.SpecialVersion, StringComparison.OrdinalIgnoreCase);
}
public override bool Equals(object obj)
{
SemanticVersion semVer = obj as SemanticVersion;
return !Object.ReferenceEquals(null, semVer) && Equals(semVer);
}
public override int GetHashCode()
{
int hashCode = Version.GetHashCode();
if (SpecialVersion != null)
{
hashCode = hashCode * 4567 + SpecialVersion.GetHashCode();
}
return hashCode;
}
}
}

View File

@ -0,0 +1,30 @@
// 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.IO;
namespace Microsoft.Dnx.Watcher.Core
{
internal class FileSystemWatcherRoot : IWatcherRoot
{
private readonly FileSystemWatcher _watcher;
public FileSystemWatcherRoot(FileSystemWatcher watcher)
{
_watcher = watcher;
}
public string Path
{
get
{
return _watcher.Path;
}
}
public void Dispose()
{
_watcher.Dispose();
}
}
}

View File

@ -0,0 +1,217 @@
// 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.Collections.Generic;
using System.IO;
namespace Microsoft.Dnx.Watcher.Core
{
public class FileWatcher : IFileWatcher
{
private readonly HashSet<string> _files = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, HashSet<string>> _directories = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
private readonly List<IWatcherRoot> _watchers = new List<IWatcherRoot>();
internal FileWatcher()
{
}
public FileWatcher(string path)
{
AddWatcher(path);
}
public event Action<string> OnChanged;
public void WatchDirectory(string path, string extension)
{
var extensions = _directories.GetOrAdd(path, _ => new HashSet<string>(StringComparer.OrdinalIgnoreCase));
extensions.Add(extension);
}
public bool WatchFile(string path)
{
return _files.Add(path);
}
public void WatchProject(string projectPath)
{
if (string.IsNullOrEmpty(projectPath))
{
return;
}
// If any watchers already handle this path then noop
if (!IsAlreadyWatched(projectPath))
{
// To reduce the number of watchers we have we add a watcher to the root
// of this project so that we'll be notified if anything we care
// about changes
var rootPath = ResolveRootDirectory(projectPath);
AddWatcher(rootPath);
}
}
// For testing
internal bool IsAlreadyWatched(string projectPath)
{
if (string.IsNullOrEmpty(projectPath))
{
return false;
}
bool anyWatchers = false;
foreach (var watcher in _watchers)
{
// REVIEW: This needs to work x-platform, should this be case
// sensitive?
if (EnsureTrailingSlash(projectPath).StartsWith(EnsureTrailingSlash(watcher.Path), StringComparison.OrdinalIgnoreCase))
{
anyWatchers = true;
}
}
return anyWatchers;
}
public void Dispose()
{
foreach (var w in _watchers)
{
w.Dispose();
}
_watchers.Clear();
}
public bool ReportChange(string newPath, WatcherChangeTypes changeType)
{
return ReportChange(oldPath: null, newPath: newPath, changeType: changeType);
}
public bool ReportChange(string oldPath, string newPath, WatcherChangeTypes changeType)
{
if (HasChanged(oldPath, newPath, changeType))
{
if (OnChanged != null)
{
OnChanged(oldPath ?? newPath);
}
return true;
}
return false;
}
private static string EnsureTrailingSlash(string path)
{
if (string.IsNullOrEmpty(path))
{
return path;
}
if (path[path.Length - 1] != Path.DirectorySeparatorChar)
{
return path + Path.DirectorySeparatorChar;
}
return path;
}
// For testing only
internal void AddWatcher(IWatcherRoot watcherRoot)
{
_watchers.Add(watcherRoot);
}
private void AddWatcher(string path)
{
var watcher = new FileSystemWatcher(path);
watcher.IncludeSubdirectories = true;
watcher.EnableRaisingEvents = true;
watcher.Changed += OnWatcherChanged;
watcher.Renamed += OnRenamed;
watcher.Deleted += OnWatcherChanged;
watcher.Created += OnWatcherChanged;
_watchers.Add(new FileSystemWatcherRoot(watcher));
}
private void OnRenamed(object sender, RenamedEventArgs e)
{
ReportChange(e.OldFullPath, e.FullPath, e.ChangeType);
}
private void OnWatcherChanged(object sender, FileSystemEventArgs e)
{
ReportChange(e.FullPath, e.ChangeType);
}
private bool HasChanged(string oldPath, string newPath, WatcherChangeTypes changeType)
{
// File changes
if (_files.Contains(newPath) ||
(oldPath != null && _files.Contains(oldPath)))
{
return true;
}
HashSet<string> extensions;
if (_directories.TryGetValue(newPath, out extensions) ||
_directories.TryGetValue(Path.GetDirectoryName(newPath), out extensions))
{
string extension = Path.GetExtension(newPath);
if (String.IsNullOrEmpty(extension))
{
// Assume it's a directory
if (changeType == WatcherChangeTypes.Created ||
changeType == WatcherChangeTypes.Renamed)
{
foreach (var e in extensions)
{
WatchDirectory(newPath, e);
}
}
else if (changeType == WatcherChangeTypes.Deleted)
{
return true;
}
// Ignore anything else
return false;
}
return extensions.Contains(extension);
}
return false;
}
private static string ResolveRootDirectory(string projectPath)
{
var di = new DirectoryInfo(projectPath);
while (di.Parent != null)
{
var globalJsonPath = Path.Combine(di.FullName, "global.json");
if (File.Exists(globalJsonPath))
{
return di.FullName;
}
di = di.Parent;
}
// If we don't find any files then make the project folder the root
return projectPath;
}
}
}

View File

@ -0,0 +1,12 @@
// 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;
namespace Microsoft.Dnx.Watcher.Core
{
internal interface IWatcherRoot : IDisposable
{
string Path { get; }
}
}

View File

@ -0,0 +1,97 @@
// 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.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Dnx.Watcher.Core
{
public class ProcessWatcher : IProcessWatcher
{
private Process _runningProcess;
public int Start(string executable, string arguments, string workingDir)
{
// This is not thread safe but it will not run in a multithreaded environment so don't worry
if (_runningProcess != null)
{
throw new InvalidOperationException("The previous process is still running");
}
_runningProcess = new Process();
_runningProcess.StartInfo = new ProcessStartInfo()
{
FileName = executable,
Arguments = arguments,
UseShellExecute = false,
WorkingDirectory = workingDir
};
RemoveCompilationPortEnvironmentVariable(_runningProcess.StartInfo);
_runningProcess.Start();
return _runningProcess.Id;
}
public async Task<int> WaitForExitAsync(CancellationToken cancellationToken)
{
try
{
await Task.Run(() =>
{
while (!cancellationToken.IsCancellationRequested)
{
if (_runningProcess.WaitForExit(500))
{
break;
}
}
if (!_runningProcess.HasExited)
{
_runningProcess.Kill();
}
});
return _runningProcess.ExitCode;
}
finally
{
_runningProcess = null;
}
}
private static void RemoveCompilationPortEnvironmentVariable(ProcessStartInfo procStartInfo)
{
string[] _environmentVariablesToRemove = new string[]
{
"DNX_COMPILATION_SERVER_PORT",
};
#if DNX451
var environmentVariables = procStartInfo.EnvironmentVariables.Keys.Cast<string>();
#else
var environmentVariables = procStartInfo.Environment.Keys;
#endif
var envVarsToRemove = environmentVariables
.Where(envVar => _environmentVariablesToRemove.Contains(envVar, StringComparer.OrdinalIgnoreCase))
.ToArray();
// Workaround for the DNX start issue (it passes some environment variables that it shouldn't)
foreach (var envVar in envVarsToRemove)
{
#if DNX451
procStartInfo.EnvironmentVariables.Remove(envVar);
#else
procStartInfo.Environment.Remove(envVar);
#endif
}
}
}
}

View File

@ -0,0 +1,45 @@
// 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.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Dnx.Runtime;
namespace Microsoft.Dnx.Watcher.Core
{
internal class Project : IProject
{
public Project(Runtime.Project runtimeProject)
{
ProjectFile = runtimeProject.ProjectFilePath;
Files = runtimeProject.Files.SourceFiles.Concat(
runtimeProject.Files.ResourceFiles.Values.Concat(
runtimeProject.Files.PreprocessSourceFiles.Concat(
runtimeProject.Files.SharedFiles))).Concat(
new string[] { runtimeProject.ProjectFilePath })
.ToList();
var projectLockJsonPath = Path.Combine(runtimeProject.ProjectDirectory, LockFileReader.LockFileName);
var lockFileReader = new LockFileReader();
if (File.Exists(projectLockJsonPath))
{
var lockFile = lockFileReader.Read(projectLockJsonPath);
ProjectDependencies = lockFile.ProjectLibraries.Select(dep => dep.Path).ToList();
}
else
{
ProjectDependencies = new string[0];
}
}
public IEnumerable<string> ProjectDependencies { get; private set; }
public IEnumerable<string> Files { get; private set; }
public string ProjectFile { get; private set; }
}
}

View File

@ -0,0 +1,53 @@
// 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.Collections.Generic;
using System.Linq;
using Microsoft.Dnx.Runtime;
namespace Microsoft.Dnx.Watcher.Core
{
public class ProjectProvider : IProjectProvider
{
public bool TryReadProject(string projectFile, out IProject project, out string errors)
{
Runtime.Project runtimeProject;
if (!TryGetProject(projectFile, out runtimeProject, out errors))
{
project = null;
return false;
}
errors = null;
project = new Project(runtimeProject);
return true;
}
// Same as TryGetProject but it doesn't throw
private bool TryGetProject(string projectFile, out Runtime.Project project, out string errorMessage)
{
try
{
var errors = new List<DiagnosticMessage>();
if (!Runtime.Project.TryGetProject(projectFile, out project, errors))
{
errorMessage = string.Join(Environment.NewLine, errors.Select(e => e.ToString()));
}
else
{
errorMessage = null;
return true;
}
}
catch (Exception ex)
{
errorMessage = ex.Message;
}
project = null;
return false;
}
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>d3da3bbb-e206-404f-aee6-17fb9b6f1221</ProjectGuid>
<RootNamespace>Microsoft.Dnx.Watcher.Core</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,36 @@
{
"version": "1.0.0-*",
"compilationOptions": { "warningsAsErrors": true },
"dependencies": {
"Microsoft.AspNet.FileProviders.Abstractions": "1.0.0-*",
"Microsoft.AspNet.FileProviders.Physical": "1.0.0-*",
"Microsoft.Dnx.Runtime.Abstractions": "1.0.0-*",
"Microsoft.Dnx.Runtime.Json.Sources": {
"type": "build",
"version": "1.0.0-*"
},
"Microsoft.Framework.Logging.Abstractions": "1.0.0-*",
"Microsoft.Framework.FileSystemGlobbing": "1.0.0-*"
},
"frameworks": {
"dnx451": {
"frameworkAssemblies": {
"System.Collections": "",
"System.Runtime": ""
}
},
"dnxcore50": {
"dependencies": {
"System.Diagnostics.Process": "4.1.0-beta-*",
"System.Linq": "4.0.1-beta-*",
"System.Runtime.Extensions": "4.0.11-beta-*",
"System.Threading.Thread": "4.0.0-beta-*"
}
}
},
"scripts": {
"postbuild": [
]
}
}

View File

@ -0,0 +1,64 @@
// 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 Microsoft.Dnx.Runtime.Common.CommandLine;
using Microsoft.Framework.Logging;
namespace Microsoft.Dnx.Watcher
{
/// <summary>
/// Logger to print formatted command output.
/// </summary>
public class CommandOutputLogger : ILogger
{
private readonly CommandOutputProvider _provider;
private readonly AnsiConsole _outConsole;
private readonly string _loggerName;
public CommandOutputLogger(CommandOutputProvider commandOutputProvider, string loggerName, bool useConsoleColor)
{
_provider = commandOutputProvider;
_outConsole = AnsiConsole.GetOutput(useConsoleColor);
_loggerName = loggerName;
}
public IDisposable BeginScopeImpl(object state)
{
throw new NotImplementedException();
}
public bool IsEnabled(LogLevel logLevel)
{
if (logLevel < _provider.LogLevel)
{
return false;
}
return true;
}
public void Log(LogLevel logLevel, int eventId, object state, Exception exception, Func<object, Exception, string> formatter)
{
if (IsEnabled(logLevel))
{
_outConsole.WriteLine($"[{_loggerName}] {Caption(logLevel)}: {formatter(state, exception)}");
}
}
private string Caption(LogLevel logLevel)
{
switch (logLevel)
{
case LogLevel.Debug: return "\x1b[35mdebug\x1b[39m";
case LogLevel.Verbose: return "\x1b[35mverbose\x1b[39m";
case LogLevel.Information: return "\x1b[32minfo\x1b[39m";
case LogLevel.Warning: return "\x1b[33mwarn\x1b[39m";
case LogLevel.Error: return "\x1b[31mfail\x1b[39m";
case LogLevel.Critical: return "\x1b[31mcritical\x1b[39m";
}
throw new Exception("Unknown LogLevel");
}
}
}

View File

@ -0,0 +1,30 @@
// 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 Microsoft.Dnx.Runtime;
using Microsoft.Framework.Logging;
namespace Microsoft.Dnx.Watcher
{
public class CommandOutputProvider : ILoggerProvider
{
private readonly bool _isWindows;
public CommandOutputProvider(IRuntimeEnvironment runtimeEnv)
{
_isWindows = runtimeEnv.OperatingSystem == "Windows";
}
public ILogger CreateLogger(string name)
{
return new CommandOutputLogger(this, name, useConsoleColor: _isWindows);
}
public void Dispose()
{
}
public LogLevel LogLevel { get; set; } = LogLevel.Information;
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>8a8ceabc-ac47-43ff-a5df-69224f7e1f46</ProjectGuid>
<RootNamespace>Microsoft.Dnx.Watcher</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,133 @@
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.Dnx.Runtime;
using Microsoft.Dnx.Runtime.Common.CommandLine;
using Microsoft.Dnx.Watcher.Core;
using Microsoft.Framework.Logging;
namespace Microsoft.Dnx.Watcher
{
public class Program
{
private const string DnxWatchArgumentSeparator = "--dnx-args";
private readonly ILoggerFactory _loggerFactory;
public Program(IRuntimeEnvironment runtimeEnvironment)
{
_loggerFactory = new LoggerFactory();
var commandProvider = new CommandOutputProvider(runtimeEnvironment);
_loggerFactory.AddProvider(commandProvider);
}
public int Main(string[] args)
{
using (CancellationTokenSource ctrlCTokenSource = new CancellationTokenSource())
{
Console.CancelKeyPress += (sender, ev) =>
{
ctrlCTokenSource.Cancel();
ev.Cancel = false;
};
string[] watchArgs, dnxArgs;
SeparateWatchArguments(args, out watchArgs, out dnxArgs);
return MainInternal(watchArgs, dnxArgs, ctrlCTokenSource.Token);
}
}
internal static void SeparateWatchArguments(string[] args, out string[] watchArgs, out string[] dnxArgs)
{
int argsIndex = -1;
watchArgs = args.TakeWhile((arg, idx) =>
{
argsIndex = idx;
return !string.Equals(arg, DnxWatchArgumentSeparator, StringComparison.OrdinalIgnoreCase);
}).ToArray();
dnxArgs = args.Skip(argsIndex + 1).ToArray();
if (dnxArgs.Length == 0)
{
// If no explicit dnx arguments then all arguments get passed to dnx
dnxArgs = watchArgs;
watchArgs = new string[0];
}
}
private int MainInternal(string[] watchArgs, string[] dnxArgs, CancellationToken cancellationToken)
{
var app = new CommandLineApplication();
app.Name = "dnx-watch";
app.FullName = "Microsoft .NET DNX File Watcher";
app.HelpOption("-?|-h|--help");
// Show help information if no subcommand/option was specified
app.OnExecute(() =>
{
app.ShowHelp();
return 2;
});
var projectArg = app.Option(
"--project <PATH>",
"Path to the project.json file or the application folder. Defaults to the current folder if not provided. Will be passed to DNX.",
CommandOptionType.SingleValue);
var workingDirArg = app.Option(
"--workingDir <DIR>",
"The working directory for DNX. Defaults to the current directory.",
CommandOptionType.SingleValue);
// This option is here just to be displayed in help
// it will not be parsed because it is removed before the code is executed
app.Option(
$"{DnxWatchArgumentSeparator} <ARGS>",
"Marks the arguments that will be passed to DNX. Anything following this option is passed. If not specified, all the arguments are passed to DNX.",
CommandOptionType.SingleValue);
app.OnExecute(() =>
{
var projectToRun = projectArg.HasValue() ?
projectArg.Value() :
Directory.GetCurrentDirectory();
if (!projectToRun.EndsWith("project.json", StringComparison.Ordinal))
{
projectToRun = Path.Combine(projectToRun, "project.json");
}
var workingDir = workingDirArg.HasValue() ?
workingDirArg.Value() :
Directory.GetCurrentDirectory();
var watcher = DnxWatcher.CreateDefault(_loggerFactory);
try
{
watcher.WatchAsync(projectToRun, dnxArgs, workingDir, cancellationToken).Wait();
}
catch (AggregateException ex)
{
if (ex.InnerExceptions.Count != 1 || !(ex.InnerException is TaskCanceledException))
{
throw;
}
}
return 1;
});
return app.Execute(watchArgs);
}
}
}

View File

@ -0,0 +1,6 @@
// 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.Runtime.CompilerServices;
[assembly:InternalsVisibleTo("Microsoft.Dnx.Watcher.Tests")]

View File

@ -0,0 +1,23 @@
{
"version": "1.0.0-*",
"compilationOptions": { "warningsAsErrors": true },
"dependencies": {
"Microsoft.Dnx.Watcher.Core": "1.0.0-*",
"Microsoft.Framework.CommandLineUtils.Sources": { "version": "1.0.0-*", "type": "build" },
"Microsoft.Framework.Logging": "1.0.0-*",
"Microsoft.Framework.Logging.Console": "1.0.0-*"
},
"frameworks": {
"dnx451": { },
"dnxcore50": { }
},
"commands": {
"dnx-watch": "Microsoft.Dnx.Watcher"
},
"scripts": {
"postbuild": [
]
}
}

View File

@ -0,0 +1,48 @@
// 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 Xunit;
namespace Microsoft.Dnx.Watcher.Tests
{
// This project can output the Class library as a NuGet Package.
// To enable this option, right-click on the project and select the Properties menu item. In the Build tab select "Produce outputs on build".
public class CommandLineParsingTests
{
[Fact]
public void NoWatcherArgs()
{
var args = "--arg1 v1 --arg2 v2".Split(' ');
string[] watcherArgs, dnxArgs;
Program.SeparateWatchArguments(args, out watcherArgs, out dnxArgs);
Assert.Empty(watcherArgs);
Assert.Equal(args, dnxArgs);
}
[Fact]
public void ArgsForBothDnxAndWatcher()
{
var args = "--arg1 v1 --arg2 v2 --dnx-args --arg3 --arg4 v4".Split(' ');
string[] watcherArgs, dnxArgs;
Program.SeparateWatchArguments(args, out watcherArgs, out dnxArgs);
Assert.Equal(new string[] {"--arg1", "v1", "--arg2", "v2" }, watcherArgs);
Assert.Equal(new string[] { "--arg3", "--arg4", "v4" }, dnxArgs);
}
[Fact]
public void MultipleSeparators()
{
var args = "--arg1 v1 --arg2 v2 --dnx-args --arg3 --dnxArgs --arg4 v4".Split(' ');
string[] watcherArgs, dnxArgs;
Program.SeparateWatchArguments(args, out watcherArgs, out dnxArgs);
Assert.Equal(new string[] { "--arg1", "v1", "--arg2", "v2" }, watcherArgs);
Assert.Equal(new string[] { "--arg3", "--dnxArgs", "--arg4", "v4" }, dnxArgs);
}
}
}

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>640d190b-26db-4dde-88ee-55814c86c43e</ProjectGuid>
<RootNamespace>Microsoft.Dnx.Watcher.Tests</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,15 @@
{
"dependencies": {
"Microsoft.Dnx.Runtime.Abstractions": "1.0.0-*",
"Microsoft.Dnx.Watcher": "1.0.0-*",
"xunit.runner.aspnet": "2.0.0-aspnet-*"
},
"frameworks": {
"dnx451": { },
"dnxcore50": { }
},
"commands": {
"test": "xunit.runner.aspnet"
}
}