-
Notifications
You must be signed in to change notification settings - Fork 74
Create replacement API for Microsoft.DotNet.ProjectModel #180
Conversation
|
||
namespace Microsoft.Extensions.ProjectModel | ||
{ | ||
internal class MsBuildProjectContextBuilder |
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.
Also consider adding the ability to specify TargetFramework.
Certain targets may only be available when a TargetFramework is specified.
ResolvePackageDependenciesDesignTime
is one such target.
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.
What format does MSBuild expect TargetFramework to be? e.g. .NETCoreApp,1.0
or netcoreapp1.0
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.
AFAIK netcoreapp1.0
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.
Added .WithTargetFramework
/// <param name="propertyName"></param> | ||
/// <param name="propertyNameComparer"></param> | ||
/// <returns></returns> | ||
public string FindProperty(string propertyName, StringComparison propertyNameComparer) |
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.
Nit: move methods below properties and fields.
private const string DefaultConfiguration = "Debug"; | ||
public static IProjectContext AsAbstract(this ProjectContext context) | ||
{ | ||
return new DotNetProjectContext(context, DefaultConfiguration, outputPath: null); |
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.
Consider getting configuration as parameter with a default value of "Debug"
string PackagesDirectory { get; } | ||
string TargetDirectory { get; } | ||
string AssemblyFullPath { get; } | ||
string FindProperty(string propertyName, StringComparison propertyNameComparer); |
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.
Move the method below properties?
{ | ||
return null; | ||
} | ||
var t = new { sdk = new { version = "" } }; |
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.
Is this being used?
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.
Nope, will clean this up.
return this; | ||
} | ||
|
||
public virtual IProjectContext Build() |
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.
Consider Changing return type to MsBuildProjectContext.
|
||
if (logger.Errors.Count > 0) | ||
{ | ||
throw new InvalidOperationException(logger.Errors[0].Message); |
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.
Why only the first one?
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.
Good point. I'll aggregate all errors.
@@ -16,6 +16,7 @@ k-standard-goals | |||
if (Directory.Exists("src") && !IsLinux) | |||
{ | |||
Directory.CreateDirectory(BUILD_DIR_LOCAL); | |||
DotnetPack("src/Microsoft.Extensions.ProjectModel.Sources/project.json", BUILD_DIR_LOCAL, E("Configuration"), E("KOREBUILD_DOTNET_PACK_OPTIONS") + " --no-build"); |
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.
why do we need this?
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.
Because this makefile overrides the default build target.
_rawProject = JObject.Load(jsonReader); | ||
} | ||
_project = wrappedProject; | ||
_paths = wrappedProject.GetOutputPaths(configuration, /* buildBasePath: */ null, outputPath); |
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.
doesn't seem to be our convention to leave comment between parameter. i understand where it comes from but suggest against this approach. a named parameter is more flexible with tools when you rename parameter through IDE.
{ | ||
internal class DotNetProjectContext : IProjectContext | ||
{ | ||
private readonly ProjectContext _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.
rename to _projectContext
to improve readability
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.
solved
using (var streamReader = new StreamReader(stream)) | ||
using (var jsonReader = new JsonTextReader(streamReader)) | ||
{ | ||
_rawProject = JObject.Load(jsonReader); |
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.
doesn't the project property on project context already have the project.json parsed?
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.
if it is really necessary, make this process lazy.
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.
I couldn't find API that exposes PJ as a grab bag of key/value pairs...but if it exists, happy to use it instead.
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.
question is, do we need to. i know the FindProperty
is defined on the interface, but all the information of a project.json
is exposed through the project model. the project.json
shouldn't be treated as a simple dictionary.
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.
all the information of a project.json is exposed through the project model
Not user secrets id, or other custom properties...
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.
fair.
public IEnumerable<string> CompilationItems | ||
=> _compilerOptions.CompileInclude.ResolveFiles(); | ||
public IEnumerable<string> EmbededItems | ||
=> _compilerOptions.EmbedInclude.ResolveFiles(); |
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.
make both properties same line definition
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.
and move them ahead of the first public method
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.
nit: is the method ResolveFiles
costy? like globbing files. if so make the properties to cache the results.
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.
Yes, these are costly, but in theory can change over time. Callers should expect any evaluation of this yields the latest set of items and that each evaluation could yield different results.
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.
Fair
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.
Please still change the code style.
internal class MsBuildContext | ||
{ | ||
public string MsBuildExecutableFullPath { get; set; } | ||
public string ExtensionsPath { get; set; } |
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.
can both properties be readonly?
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.
solved
WithProperty("_ResolveReferenceDependencies", "true"); | ||
} | ||
|
||
protected virtual ProjectCollection CreateProjectCollection() |
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.
same line
{ | ||
// don't to expensive things | ||
return WithProperty("DesignTimeBuild", "true") | ||
.WithProperty("_ResolveReferenceDependencies", "true") |
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.
my ocd tells me that the W
should be aligned.
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.
and you fix that! thank you!
} | ||
|
||
public MsBuildProjectContextBuilder WithBuildTargets(string[] targets) | ||
{ |
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.
check null?
var xml = ProjectRootElement.Create(xmlReader, projectCollection, preserveFormatting: true); | ||
xml.FullPath = fileInfo.PhysicalPath; | ||
|
||
return new Project(xml, globalProps, /*toolsVersion:*/ null, projectCollection); |
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.
named parameter instead of comment among parameters
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.
⌚
_isExecutable = wrappedProject.ProjectFile.GetCompilerOptions(wrappedProject.TargetFramework, configuration).EmitEntryPoint | ||
?? wrappedProject.ProjectFile.GetCompilerOptions(null, configuration).EmitEntryPoint.GetValueOrDefault(); | ||
|
||
_compilerOptions = _project.ProjectFile.GetCompilerOptions(TargetFramework, Configuration); |
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.
Set this before the _isExecutable
bit and you can re-use. (Will need to set Configuration before as well)
Configuration = configuration; | ||
} | ||
|
||
public bool IsClassLibrary => !_isExecutable; |
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.
{ get; }
and set this in the ctor instead of having an _isExecutable
variable. Can then just use this property.
public string FindProperty(string propertyName, StringComparison propertyNameComparer) | ||
=> FindProperty<string>(propertyName, propertyNameComparer); | ||
|
||
public T FindProperty<T>(string propertyName, StringComparison propertyNameComparer) |
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.
TProperty
public IEnumerable<string> EmbededItems | ||
=> _compilerOptions.EmbedInclude.ResolveFiles(); | ||
|
||
public ProjectContext Unwrap() => _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.
Do we really need this? It'd be nice to build our tools without the expectation that they can consume the original project context. Aka our IProjectContext
should have sufficient information, if it doesn't we should add API.
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.
Good point. I'll removing so we are forced improve the IProjectContext abstraction instead of falling back. We can revisit later if there is a read need for this.
{ | ||
var project = new ProjectContextBuilder() | ||
.AsDesignTime() | ||
.WithProject(ProjectReader.GetProject(filePath)) |
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.
Break statements like these into multiple declarations. It's an additional line but it makes debugging 10x easier if it ever comes to it.
return new DotNetCoreSdk | ||
{ | ||
BasePath = latest.path, | ||
Version = latest.version.ToFullString() |
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.
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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
NuGet.Versioning.SematicVersion
is an implementation detail of how we figure out which version is "latest". In theory, SDK versions may not be valid SemanticVersion
s.
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.
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.
|
||
namespace Microsoft.Extensions.ProjectModel.Internal | ||
{ | ||
internal class InMemoryLogger : ILogger |
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.
Any reason it shouldn't be a private class in the class it's used in?
|
||
namespace Microsoft.Extensions.ProjectModel | ||
{ | ||
internal static class ProjectContextExtensions |
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.
These extensions seem wasteful.
Reasons:
- First is trying to avoid using ignorecase. Our dev guidelines state to always specify comparison.
- Second is overly specific to user secrets. Let the projects who care about user secrets add this API; no need to pollute the IntelliSense list with extra info when it typically will not be needed.
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.
Fair enough. I'm in favor of smaller APIs :)
|
||
public MsBuildProjectContextBuilder() | ||
{ | ||
Initialize(); |
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.
lol, why do we need to specify a method that can be overridden (instead of inlining)? Makes me think the builder isn't sufficient as is.
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.
Seems like a lot here is protected virtual. So same question applies there.
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.
The intent is to make it possible to override builder defaults. Overriding methods is easier than unwinding work done in the base constructor.
throw new ArgumentNullException(nameof(filePath)); | ||
} | ||
|
||
return WithProjectFile(new PhysicalFileInfo(new FileInfo(filePath))); |
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.
break apart
🆙 📅 Made lots of updates per PR feedback. |
6170551
to
2fdd27d
Compare
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.
most of the comments in the first review are addressed. the PR looks good to me.
public IEnumerable<string> CompilationItems | ||
=> _compilerOptions.CompileInclude.ResolveFiles(); | ||
public IEnumerable<string> EmbededItems | ||
=> _compilerOptions.EmbedInclude.ResolveFiles(); |
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.
Fair
public IEnumerable<string> CompilationItems | ||
=> _compilerOptions.CompileInclude.ResolveFiles(); | ||
public IEnumerable<string> EmbededItems | ||
=> _compilerOptions.EmbedInclude.ResolveFiles(); |
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.
Please still change the code style.
{ | ||
internal class DotNetProjectContext : IProjectContext | ||
{ | ||
private readonly ProjectContext _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.
solved
using (var streamReader = new StreamReader(stream)) | ||
using (var jsonReader = new JsonTextReader(streamReader)) | ||
{ | ||
_rawProject = JObject.Load(jsonReader); |
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.
fair.
{ | ||
return null; | ||
} | ||
var t = new { sdk = new { version = "" } }; |
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.
solved
|
||
public string Parameters { get; set; } | ||
public LoggerVerbosity Verbosity { get; set; } | ||
public void Initialize(IEventSource eventSource) |
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.
solved
internal class MsBuildContext | ||
{ | ||
public string MsBuildExecutableFullPath { get; set; } | ||
public string ExtensionsPath { get; set; } |
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.
solved
{ | ||
// don't to expensive things | ||
return WithProperty("DesignTimeBuild", "true") | ||
.WithProperty("_ResolveReferenceDependencies", "true") |
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.
and you fix that! thank you!
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.
Get this in and we can rev on it as we need. 😄
2fdd27d
to
40ff75d
Compare
Creates a new package "Microsoft.Extensions.ProjectModel.Sources" which contains API for project evaluation to replace Microsoft.DotNet.ProjectModel.
Follows patterns from Mircrosoft.DotNet.ProjectModel
cc @NTaylorMullen @bricelam @prafullbhosale @muratg @Eilon