Skip to content
This repository was archived by the owner on Nov 20, 2018. It is now read-only.

Create replacement API for Microsoft.DotNet.ProjectModel #180

Merged
merged 1 commit into from
Oct 4, 2016

Conversation

natemcmaster
Copy link
Contributor

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


namespace Microsoft.Extensions.ProjectModel
{
internal class MsBuildProjectContextBuilder
Copy link
Contributor

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.

Copy link
Contributor Author

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK netcoreapp1.0

Copy link
Contributor Author

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)
Copy link
Contributor

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);
Copy link
Contributor

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);
Copy link
Contributor

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 = "" } };
Copy link
Contributor

@prafullbhosale prafullbhosale Oct 3, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this being used?

Copy link
Contributor Author

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()
Copy link
Contributor

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);
Copy link
Contributor

@prafullbhosale prafullbhosale Oct 3, 2016

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?

Copy link
Contributor Author

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");
Copy link

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?

Copy link
Contributor Author

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);
Copy link

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;
Copy link

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

Copy link

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);
Copy link

@troydai troydai Oct 3, 2016

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?

Copy link

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.

Copy link
Contributor Author

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.

Copy link

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.

Copy link
Contributor Author

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...

Copy link

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();
Copy link

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

Copy link

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

Copy link

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.

Copy link
Contributor Author

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.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair

Copy link

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; }
Copy link

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?

Copy link

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()
Copy link

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")
Copy link

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.

Copy link

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)
{
Copy link

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);
Copy link

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

Copy link
Contributor

@NTaylorMullen NTaylorMullen left a 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);
Copy link
Contributor

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;
Copy link
Contributor

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)
Copy link
Contributor

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;
Copy link
Contributor

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.

Copy link
Contributor Author

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))
Copy link
Contributor

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()
Copy link
Contributor

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.

Copy link
Contributor

@NTaylorMullen NTaylorMullen Oct 3, 2016

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

Copy link
Contributor Author

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 SemanticVersions.

Copy link
Contributor

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
Copy link
Contributor

@NTaylorMullen NTaylorMullen Oct 3, 2016

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
Copy link
Contributor

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.

Copy link
Contributor Author

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();
Copy link
Contributor

@NTaylorMullen NTaylorMullen Oct 3, 2016

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

break apart

@natemcmaster
Copy link
Contributor Author

🆙 📅

Made lots of updates per PR feedback.

Copy link

@troydai troydai left a 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();
Copy link

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();
Copy link

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;
Copy link

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);
Copy link

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 = "" } };
Copy link

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)
Copy link

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; }
Copy link

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")
Copy link

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!

@natemcmaster natemcmaster changed the base branch from feature/msbuild to dev October 4, 2016 21:45
Copy link
Contributor

@NTaylorMullen NTaylorMullen left a 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. 😄

@natemcmaster natemcmaster force-pushed the namc/project-model-abstractions branch from 2fdd27d to 40ff75d Compare October 4, 2016 22:49
@natemcmaster natemcmaster merged commit 40ff75d into dev Oct 4, 2016
natemcmaster added a commit to aspnet/Coherence that referenced this pull request Oct 4, 2016
@natemcmaster natemcmaster deleted the namc/project-model-abstractions branch October 4, 2016 22:53
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants