-
Notifications
You must be signed in to change notification settings - Fork 74
Create replacement API for Microsoft.DotNet.ProjectModel #180
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
{ | ||
"projects": [ "src"] | ||
"projects": [ "src", "test"] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
// 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 Microsoft.DotNet.ProjectModel; | ||
using Newtonsoft.Json; | ||
using Newtonsoft.Json.Linq; | ||
using NuGet.Frameworks; | ||
|
||
namespace Microsoft.Extensions.ProjectModel | ||
{ | ||
internal class DotNetProjectContext : IProjectContext | ||
{ | ||
private readonly ProjectContext _projectContext; | ||
private readonly OutputPaths _paths; | ||
private readonly Lazy<JObject> _rawProject; | ||
private readonly CommonCompilerOptions _compilerOptions; | ||
|
||
public DotNetProjectContext(ProjectContext projectContext, string configuration, string outputPath) | ||
{ | ||
if (projectContext == null) | ||
{ | ||
throw new ArgumentNullException(nameof(projectContext)); | ||
} | ||
|
||
if (string.IsNullOrEmpty(configuration)) | ||
{ | ||
throw new ArgumentNullException(nameof(configuration)); | ||
} | ||
|
||
_rawProject = new Lazy<JObject>(() => | ||
{ | ||
using (var stream = new FileStream(projectContext.ProjectFile.ProjectFilePath, FileMode.Open, FileAccess.Read)) | ||
using (var streamReader = new StreamReader(stream)) | ||
using (var jsonReader = new JsonTextReader(streamReader)) | ||
{ | ||
return JObject.Load(jsonReader); | ||
} | ||
}); | ||
|
||
Configuration = configuration; | ||
_projectContext = projectContext; | ||
|
||
_paths = projectContext.GetOutputPaths(configuration, buidBasePath: null, outputPath: outputPath); | ||
_compilerOptions = _projectContext.ProjectFile.GetCompilerOptions(TargetFramework, Configuration); | ||
|
||
// Workaround https://github.com/dotnet/cli/issues/3164 | ||
IsClassLibrary = !(_compilerOptions.EmitEntryPoint | ||
?? projectContext.ProjectFile.GetCompilerOptions(null, configuration).EmitEntryPoint.GetValueOrDefault()); | ||
} | ||
|
||
public bool IsClassLibrary { get; } | ||
|
||
public NuGetFramework TargetFramework => _projectContext.TargetFramework; | ||
public string Config => _paths.RuntimeFiles.Config; | ||
public string DepsJson => _paths.RuntimeFiles.DepsJson; | ||
public string RuntimeConfigJson => _paths.RuntimeFiles.RuntimeConfigJson; | ||
public string PackagesDirectory => _projectContext.PackagesDirectory; | ||
|
||
public string AssemblyFullPath => | ||
!IsClassLibrary && (_projectContext.IsPortable || TargetFramework.IsDesktop()) | ||
? _paths.RuntimeFiles.Executable | ||
: _paths.RuntimeFiles.Assembly; | ||
|
||
public string Configuration { get; } | ||
public string ProjectFullPath => _projectContext.ProjectFile.ProjectFilePath; | ||
public string ProjectName => _projectContext.ProjectFile.Name; | ||
// TODO read from xproj if available | ||
public string RootNamespace => _projectContext.ProjectFile.Name; | ||
public string TargetDirectory => _paths.RuntimeOutputPath; | ||
public string Platform => _compilerOptions.Platform; | ||
|
||
public IEnumerable<string> CompilationItems | ||
=> _compilerOptions.CompileInclude.ResolveFiles(); | ||
|
||
public IEnumerable<string> EmbededItems | ||
=> _compilerOptions.EmbedInclude.ResolveFiles(); | ||
|
||
/// <summary> | ||
/// Returns string values of top-level keys in the project.json file | ||
/// </summary> | ||
/// <param name="propertyName"></param> | ||
/// <param name="propertyNameComparer"></param> | ||
/// <returns></returns> | ||
public string FindProperty(string propertyName) => FindProperty<string>(propertyName); | ||
|
||
public TProperty FindProperty<TProperty>(string propertyName) | ||
{ | ||
foreach (var item in _rawProject.Value) | ||
{ | ||
if (item.Key.Equals(propertyName, StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
return item.Value.Value<TProperty>(); | ||
} | ||
} | ||
|
||
return default(TProperty); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// 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.DotNet.ProjectModel.Files; | ||
|
||
namespace Microsoft.Extensions.ProjectModel | ||
{ | ||
internal static class IncludeContextExtensions | ||
{ | ||
public static IEnumerable<string> ResolveFiles(this IncludeContext context) | ||
{ | ||
if (context == null) | ||
{ | ||
throw new ArgumentNullException(nameof(context)); | ||
} | ||
|
||
return IncludeFilesResolver | ||
.GetIncludeFiles(context, "/", diagnostics: null) | ||
.Select(f => f.SourcePath); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// 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 NuGet.Frameworks; | ||
|
||
namespace Microsoft.Extensions.ProjectModel | ||
{ | ||
public interface IProjectContext | ||
{ | ||
string ProjectName { get; } | ||
string Configuration { get; } | ||
string Platform { get; } | ||
string ProjectFullPath { get; } | ||
string RootNamespace { get; } | ||
bool IsClassLibrary { get; } | ||
NuGetFramework TargetFramework { get; } | ||
string Config { get; } | ||
string DepsJson { get; } | ||
string RuntimeConfigJson { get; } | ||
string PackagesDirectory { get; } | ||
string TargetDirectory { get; } | ||
string AssemblyFullPath { get; } | ||
IEnumerable<string> CompilationItems { get; } | ||
IEnumerable<string> EmbededItems { get; } | ||
string FindProperty(string propertyName); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// 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.Extensions.ProjectModel.Internal | ||
{ | ||
internal class DotNetCoreSdk | ||
{ | ||
public string BasePath { get; set; } | ||
public string Version { get; set; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
// 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.DotNet.Cli.Utils; | ||
using Newtonsoft.Json; | ||
using NuGet.Versioning; | ||
|
||
namespace Microsoft.Extensions.ProjectModel.Internal | ||
{ | ||
internal class DotNetCoreSdkResolver | ||
{ | ||
private readonly string _installationDir; | ||
|
||
/// <summary> | ||
/// Represents a resolver that uses the currently executing <see cref="Muxer"/> to find the .NET Core SDK installation | ||
/// </summary> | ||
public static readonly DotNetCoreSdkResolver DefaultResolver = new DotNetCoreSdkResolver(Path.GetDirectoryName(new Muxer().MuxerPath)); | ||
|
||
/// <summary> | ||
/// Instantiates a resolver that locates the SDK | ||
/// </summary> | ||
/// <param name="installationDir">The directory containing dotnet muxer, aka DOTNET_HOME</param> | ||
public DotNetCoreSdkResolver(string installationDir) | ||
{ | ||
_installationDir = installationDir; | ||
} | ||
|
||
/// <summary> | ||
/// Find the latest SDK installation (uses SemVer 1.0 to determine what is "latest") | ||
/// </summary> | ||
/// <returns>Path to SDK root directory</returns> | ||
public DotNetCoreSdk ResolveLatest() | ||
{ | ||
var latest = FindInstalled() | ||
.Select(d => new { path = d, version = SemanticVersion.Parse(Path.GetFileName(d)) }) | ||
.OrderByDescending(sdk => sdk.version) | ||
.FirstOrDefault(); | ||
|
||
if (latest == null) | ||
{ | ||
throw CreateSdkNotInstalledException(); | ||
} | ||
|
||
return new DotNetCoreSdk | ||
{ | ||
BasePath = latest.path, | ||
Version = latest.version.ToFullString() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any value in ToStringing here? Why not just let the DotNetCoreSdk class have Version has a semantic version? Seems that we don't use the version directly so it's wasteful/less informative to do this bit. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would also let you not use an anonymous object in the logic you use to determine "latest" here and then in the "sdk" logic in the next method There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If they end up not being valid semantic versioning then doesn't our resolution logic fall apart? Reason being we order the versions based on how semantic versions are ordered. |
||
}; | ||
} | ||
|
||
public DotNetCoreSdk ResolveProjectSdk(string projectDir) | ||
{ | ||
var sdkVersion = ResolveGlobalJsonSdkVersion(projectDir); | ||
if (string.IsNullOrEmpty(sdkVersion)) | ||
{ | ||
return ResolveLatest(); | ||
} | ||
|
||
var sdk = FindInstalled() | ||
.Where(p => Path.GetFileName(p).Equals(sdkVersion, StringComparison.OrdinalIgnoreCase)) | ||
.Select(d => new { path = d, version = SemanticVersion.Parse(Path.GetFileName(d)) }) | ||
.FirstOrDefault(); | ||
|
||
if (sdk == null) | ||
{ | ||
throw CreateSdkNotInstalledException(); | ||
} | ||
|
||
return new DotNetCoreSdk | ||
{ | ||
BasePath = sdk.path, | ||
Version = sdk.version.ToFullString() | ||
}; | ||
} | ||
|
||
private Exception CreateSdkNotInstalledException() | ||
{ | ||
return new DirectoryNotFoundException($"Could not find an installation of the .NET Core SDK in '{_installationDir}'"); | ||
} | ||
|
||
private IEnumerable<string> FindInstalled() | ||
=> Directory.EnumerateDirectories(Path.Combine(_installationDir, "sdk")); | ||
|
||
private string ResolveGlobalJsonSdkVersion(string start) | ||
{ | ||
var dir = new DirectoryInfo(start); | ||
FileInfo fileInfo = null; | ||
while (dir != null) | ||
{ | ||
var candidate = Path.Combine(dir.FullName, "global.json"); | ||
if (File.Exists(candidate)) | ||
{ | ||
fileInfo = new FileInfo(candidate); | ||
break; | ||
} | ||
dir = dir.Parent; | ||
} | ||
if (fileInfo == null) | ||
{ | ||
return null; | ||
} | ||
try | ||
{ | ||
var contents = File.ReadAllText(fileInfo.FullName); | ||
var globalJson = JsonConvert.DeserializeObject<GlobalJsonStub>(contents); | ||
return globalJson?.sdk?.version; | ||
} | ||
catch (JsonException) | ||
{ | ||
// TODO log | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it would be helpful at least the exception message is logged. |
||
return null; | ||
} | ||
} | ||
|
||
private class GlobalJsonStub | ||
{ | ||
public GlobalJsonSdkStub sdk { get; set; } | ||
|
||
public class GlobalJsonSdkStub | ||
{ | ||
public string version { get; set; } | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<Project ToolsVersion="14.0.25420" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
<PropertyGroup> | ||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.25420</VisualStudioVersion> | ||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> | ||
</PropertyGroup> | ||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> | ||
<PropertyGroup Label="Globals"> | ||
<ProjectGuid>99d6ce89-7302-4c3a-83eb-d534c24644d2</ProjectGuid> | ||
<RootNamespace>Microsoft.Extensions.ProjectModel</RootNamespace> | ||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> | ||
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> | ||
</PropertyGroup> | ||
<PropertyGroup> | ||
<SchemaVersion>2.0</SchemaVersion> | ||
</PropertyGroup> | ||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> | ||
</Project> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure this warrants its own extension method/class. Might as well inline this as a private method.