2015-10-13 14:31:29 -07:00
// 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 ;
2015-10-15 15:09:37 -07:00
using Microsoft.Extensions.JsonParser.Sources ;
2015-10-17 08:55:06 -07:00
using Microsoft.Extensions.ProjectModel.Files ;
2015-10-13 14:31:29 -07:00
using Microsoft.Extensions.ProjectModel.Graph ;
2015-10-17 22:42:50 -07:00
using Microsoft.Extensions.ProjectModel.Utilities ;
2015-10-13 14:31:29 -07:00
using NuGet.Frameworks ;
using NuGet.Versioning ;
namespace Microsoft.Extensions.ProjectModel
{
2015-10-15 15:09:37 -07:00
public class ProjectReader
2015-10-13 14:31:29 -07:00
{
2015-10-17 22:42:50 -07:00
public static bool TryGetProject ( string path , out Project project , ICollection < DiagnosticMessage > diagnostics = null )
{
project = null ;
string projectPath = null ;
if ( string . Equals ( Path . GetFileName ( path ) , Project . FileName , StringComparison . OrdinalIgnoreCase ) )
{
projectPath = path ;
path = Path . GetDirectoryName ( path ) ;
}
else if ( ! HasProjectFile ( path ) )
{
return false ;
}
else
{
projectPath = Path . Combine ( path , Project . FileName ) ;
}
// Assume the directory name is the project name if none was specified
var projectName = PathUtility . GetDirectoryName ( Path . GetFullPath ( 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 ;
}
2015-10-13 14:31:29 -07:00
public static Project GetProject ( string projectFile )
{
return GetProject ( projectFile , new List < DiagnosticMessage > ( ) ) ;
}
public static Project GetProject ( string projectFile , ICollection < DiagnosticMessage > diagnostics )
{
var name = Path . GetFileName ( Path . GetDirectoryName ( projectFile ) ) ;
using ( var stream = new FileStream ( projectFile , FileMode . Open , FileAccess . Read , FileShare . Read ) )
{
return new ProjectReader ( ) . ReadProject ( stream , name , projectFile , diagnostics ) ;
}
}
public Project ReadProject ( Stream stream , string projectName , string projectPath , ICollection < DiagnosticMessage > diagnostics )
{
var project = new Project ( ) ;
2015-10-15 15:09:37 -07:00
var reader = new StreamReader ( stream ) ;
var rawProject = JsonDeserializer . Deserialize ( reader ) as JsonObject ;
2015-10-13 14:31:29 -07:00
if ( rawProject = = null )
{
throw FileFormatException . Create (
"The JSON file can't be deserialized to a JSON object." ,
projectPath ) ;
}
// Meta-data properties
2015-10-18 00:35:56 -07:00
project . Name = rawProject . ValueAsString ( "name" ) ? ? projectName ;
2015-10-13 14:31:29 -07:00
project . ProjectFilePath = Path . GetFullPath ( projectPath ) ;
2015-10-15 15:09:37 -07:00
var version = rawProject . Value ( "version" ) as JsonString ;
2015-10-13 14:31:29 -07:00
if ( version = = null )
{
project . Version = new NuGetVersion ( "1.0.0" ) ;
}
else
{
try
{
2015-10-30 10:34:02 -07:00
var buildVersion = Environment . GetEnvironmentVariable ( "DOTNET_BUILD_VERSION" ) ;
2015-10-15 15:09:37 -07:00
project . Version = SpecifySnapshot ( version , buildVersion ) ;
2015-10-13 14:31:29 -07:00
}
catch ( Exception ex )
{
throw FileFormatException . Create ( ex , version , project . ProjectFilePath ) ;
}
}
2015-10-17 07:20:56 -07:00
var fileVersion = Environment . GetEnvironmentVariable ( "DOTNET_ASSEMBLY_FILE_VERSION" ) ;
2015-10-13 14:31:29 -07:00
if ( string . IsNullOrWhiteSpace ( fileVersion ) )
{
project . AssemblyFileVersion = project . Version . Version ;
}
else
{
try
{
var simpleVersion = project . Version . Version ;
project . AssemblyFileVersion = new Version ( simpleVersion . Major ,
simpleVersion . Minor ,
simpleVersion . Build ,
int . Parse ( fileVersion ) ) ;
}
catch ( FormatException ex )
{
throw new FormatException ( "The assembly file version is invalid: " + fileVersion , ex ) ;
}
}
2015-10-15 15:09:37 -07:00
project . Description = rawProject . ValueAsString ( "description" ) ;
project . Summary = rawProject . ValueAsString ( "summary" ) ;
project . Copyright = rawProject . ValueAsString ( "copyright" ) ;
project . Title = rawProject . ValueAsString ( "title" ) ;
project . EntryPoint = rawProject . ValueAsString ( "entryPoint" ) ;
project . ProjectUrl = rawProject . ValueAsString ( "projectUrl" ) ;
project . LicenseUrl = rawProject . ValueAsString ( "licenseUrl" ) ;
project . IconUrl = rawProject . ValueAsString ( "iconUrl" ) ;
2015-10-23 15:21:49 -07:00
project . CompilerName = rawProject . ValueAsString ( "compilerName" ) ;
2015-10-13 14:31:29 -07:00
2015-10-17 07:20:56 -07:00
project . Authors = rawProject . ValueAsStringArray ( "authors" ) ? ? Array . Empty < string > ( ) ;
project . Owners = rawProject . ValueAsStringArray ( "owners" ) ? ? Array . Empty < string > ( ) ;
project . Tags = rawProject . ValueAsStringArray ( "tags" ) ? ? Array . Empty < string > ( ) ;
2015-10-13 14:31:29 -07:00
2015-10-15 15:09:37 -07:00
project . Language = rawProject . ValueAsString ( "language" ) ;
project . ReleaseNotes = rawProject . ValueAsString ( "releaseNotes" ) ;
2015-10-13 14:31:29 -07:00
2015-10-15 15:09:37 -07:00
project . RequireLicenseAcceptance = rawProject . ValueAsBoolean ( "requireLicenseAcceptance" , defaultValue : false ) ;
2015-10-17 22:42:50 -07:00
2015-10-17 08:05:35 -07:00
// REVIEW: Move this to the dependencies node?
2015-10-15 15:09:37 -07:00
project . EmbedInteropTypes = rawProject . ValueAsBoolean ( "embedInteropTypes" , defaultValue : false ) ;
2015-10-13 14:31:29 -07:00
project . Dependencies = new List < LibraryRange > ( ) ;
// Project files
project . Files = new ProjectFilesCollection ( rawProject , project . ProjectDirectory , project . ProjectFilePath ) ;
2015-10-15 15:09:37 -07:00
var commands = rawProject . Value ( "commands" ) as JsonObject ;
2015-10-13 14:31:29 -07:00
if ( commands ! = null )
{
2015-10-15 15:09:37 -07:00
foreach ( var key in commands . Keys )
2015-10-13 14:31:29 -07:00
{
2015-10-15 15:09:37 -07:00
var value = commands . ValueAsString ( key ) ;
2015-10-13 14:31:29 -07:00
if ( value ! = null )
{
2015-10-15 15:09:37 -07:00
project . Commands [ key ] = value ;
2015-10-13 14:31:29 -07:00
}
}
}
2015-10-15 15:09:37 -07:00
var scripts = rawProject . Value ( "scripts" ) as JsonObject ;
2015-10-13 14:31:29 -07:00
if ( scripts ! = null )
{
2015-10-15 15:09:37 -07:00
foreach ( var key in scripts . Keys )
2015-10-13 14:31:29 -07:00
{
2015-10-15 15:09:37 -07:00
var stringValue = scripts . ValueAsString ( key ) ;
2015-10-13 14:31:29 -07:00
if ( stringValue ! = null )
{
2015-10-15 15:09:37 -07:00
project . Scripts [ key ] = new string [ ] { stringValue } ;
2015-10-13 14:31:29 -07:00
continue ;
}
2015-10-15 15:09:37 -07:00
var arrayValue = scripts . ValueAsStringArray ( key ) ;
2015-10-13 14:31:29 -07:00
if ( arrayValue ! = null )
{
2015-10-15 15:09:37 -07:00
project . Scripts [ key ] = arrayValue ;
2015-10-13 14:31:29 -07:00
continue ;
}
throw FileFormatException . Create (
string . Format ( "The value of a script in {0} can only be a string or an array of strings" , Project . FileName ) ,
2015-10-15 15:09:37 -07:00
scripts . Value ( key ) ,
2015-10-13 14:31:29 -07:00
project . ProjectFilePath ) ;
}
}
BuildTargetFrameworksAndConfigurations ( project , rawProject , diagnostics ) ;
PopulateDependencies (
project . ProjectFilePath ,
project . Dependencies ,
rawProject ,
"dependencies" ,
isGacOrFrameworkReference : false ) ;
return project ;
}
private static NuGetVersion 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 ;
}
}
2015-10-15 15:09:37 -07:00
return new NuGetVersion ( version ) ;
2015-10-13 14:31:29 -07:00
}
private static void PopulateDependencies (
string projectPath ,
IList < LibraryRange > results ,
2015-10-15 15:09:37 -07:00
JsonObject settings ,
2015-10-13 14:31:29 -07:00
string propertyName ,
bool isGacOrFrameworkReference )
{
2015-10-15 15:09:37 -07:00
var dependencies = settings . ValueAsJsonObject ( propertyName ) ;
2015-10-13 14:31:29 -07:00
if ( dependencies ! = null )
{
2015-10-15 15:09:37 -07:00
foreach ( var dependencyKey in dependencies . Keys )
2015-10-13 14:31:29 -07:00
{
2015-10-15 15:09:37 -07:00
if ( string . IsNullOrEmpty ( dependencyKey ) )
2015-10-13 14:31:29 -07:00
{
throw FileFormatException . Create (
"Unable to resolve dependency ''." ,
2015-10-15 15:09:37 -07:00
dependencies . Value ( dependencyKey ) ,
2015-10-13 14:31:29 -07:00
projectPath ) ;
}
2015-10-15 15:09:37 -07:00
var dependencyValue = dependencies . Value ( dependencyKey ) ;
var dependencyTypeValue = LibraryDependencyType . Default ;
JsonString dependencyVersionAsString = null ;
2015-10-13 14:31:29 -07:00
LibraryType target = isGacOrFrameworkReference ? LibraryType . ReferenceAssembly : LibraryType . Unspecified ;
2015-10-15 15:09:37 -07:00
if ( dependencyValue is JsonObject )
2015-10-13 14:31:29 -07:00
{
// "dependencies" : { "Name" : { "version": "1.0", "type": "build", "target": "project" } }
2015-10-15 15:09:37 -07:00
var dependencyValueAsObject = ( JsonObject ) dependencyValue ;
dependencyVersionAsString = dependencyValueAsObject . ValueAsString ( "version" ) ;
2015-10-13 14:31:29 -07:00
2015-10-15 15:09:37 -07:00
var type = dependencyValueAsObject . ValueAsString ( "type" ) ;
2015-10-13 14:31:29 -07:00
if ( type ! = null )
{
2015-10-15 15:09:37 -07:00
dependencyTypeValue = LibraryDependencyType . Parse ( type . Value ) ;
2015-10-13 14:31:29 -07:00
}
// Read the target if specified
if ( ! isGacOrFrameworkReference )
{
LibraryType parsedTarget ;
2015-10-15 15:09:37 -07:00
var targetStr = dependencyValueAsObject . ValueAsString ( "target" ) ;
2015-10-13 14:31:29 -07:00
if ( ! string . IsNullOrEmpty ( targetStr ) & & LibraryType . TryParse ( targetStr , out parsedTarget ) )
{
target = parsedTarget ;
}
}
}
2015-10-15 15:09:37 -07:00
else if ( dependencyValue is JsonString )
2015-10-13 14:31:29 -07:00
{
// "dependencies" : { "Name" : "1.0" }
2015-10-15 15:09:37 -07:00
dependencyVersionAsString = ( JsonString ) dependencyValue ;
2015-10-13 14:31:29 -07:00
}
else
{
throw FileFormatException . Create (
string . Format ( "Invalid dependency version: {0}. The format is not recognizable." , dependencyKey ) ,
dependencyValue ,
projectPath ) ;
}
VersionRange dependencyVersionRange = null ;
2015-10-15 15:09:37 -07:00
if ( ! string . IsNullOrEmpty ( dependencyVersionAsString ? . Value ) )
2015-10-13 14:31:29 -07:00
{
try
{
2015-10-15 15:09:37 -07:00
dependencyVersionRange = VersionRange . Parse ( dependencyVersionAsString . Value ) ;
2015-10-13 14:31:29 -07:00
}
catch ( Exception ex )
{
throw FileFormatException . Create (
ex ,
dependencyValue ,
projectPath ) ;
}
}
results . Add ( new LibraryRange (
2015-10-15 15:09:37 -07:00
dependencyKey ,
2015-10-13 14:31:29 -07:00
dependencyVersionRange ,
target ,
dependencyTypeValue ,
projectPath ,
2015-10-15 15:09:37 -07:00
dependencies . Value ( dependencyKey ) . Line ,
dependencies . Value ( dependencyKey ) . Column ) ) ;
2015-10-13 14:31:29 -07:00
}
}
}
2015-10-15 15:09:37 -07:00
private static bool TryGetStringEnumerable ( JsonObject parent , string property , out IEnumerable < string > result )
2015-10-13 14:31:29 -07:00
{
var collection = new List < string > ( ) ;
2015-10-15 15:09:37 -07:00
var valueInString = parent . ValueAsString ( property ) ;
if ( valueInString ! = null )
2015-10-13 14:31:29 -07:00
{
2015-10-15 15:09:37 -07:00
collection . Add ( valueInString ) ;
2015-10-13 14:31:29 -07:00
}
else
{
2015-10-15 15:09:37 -07:00
var valueInArray = parent . ValueAsStringArray ( property ) ;
if ( valueInArray ! = null )
{
collection . AddRange ( valueInArray ) ;
}
else
{
result = null ;
return false ;
}
2015-10-13 14:31:29 -07:00
}
2015-10-15 15:09:37 -07:00
result = collection . SelectMany ( value = > value . Split ( new [ ] { ' ' , ',' } , StringSplitOptions . RemoveEmptyEntries ) ) ;
2015-10-13 14:31:29 -07:00
return true ;
}
2015-10-15 15:09:37 -07:00
private void BuildTargetFrameworksAndConfigurations ( Project project , JsonObject projectJsonObject , ICollection < DiagnosticMessage > diagnostics )
2015-10-13 14:31:29 -07:00
{
// Get the shared compilationOptions
2015-10-20 13:27:56 -07:00
project . _defaultCompilerOptions = GetCompilationOptions ( projectJsonObject ) ? ? new CommonCompilerOptions ( ) ;
2015-10-13 14:31:29 -07:00
project . _defaultTargetFrameworkConfiguration = new TargetFrameworkInformation
{
2015-10-15 15:09:37 -07:00
Dependencies = new List < LibraryRange > ( )
2015-10-13 14:31:29 -07:00
} ;
// Add default configurations
2015-10-20 13:27:56 -07:00
project . _compilerOptionsByConfiguration [ "Debug" ] = new CommonCompilerOptions
2015-10-13 14:31:29 -07:00
{
Defines = new [ ] { "DEBUG" , "TRACE" } ,
Optimize = false
} ;
2015-10-20 13:27:56 -07:00
project . _compilerOptionsByConfiguration [ "Release" ] = new CommonCompilerOptions
2015-10-13 14:31:29 -07:00
{
Defines = new [ ] { "RELEASE" , "TRACE" } ,
Optimize = true
} ;
// The configuration node has things like debug/release compiler settings
/ *
{
"configurations" : {
"Debug" : {
} ,
"Release" : {
}
}
}
* /
2015-10-15 15:09:37 -07:00
var configurationsSection = projectJsonObject . ValueAsJsonObject ( "configurations" ) ;
2015-10-13 14:31:29 -07:00
if ( configurationsSection ! = null )
{
2015-10-15 15:09:37 -07:00
foreach ( var configKey in configurationsSection . Keys )
2015-10-13 14:31:29 -07:00
{
2015-10-15 15:09:37 -07:00
var compilerOptions = GetCompilationOptions ( configurationsSection . ValueAsJsonObject ( configKey ) ) ;
2015-10-13 14:31:29 -07:00
// Only use this as a configuration if it's not a target framework
2015-10-15 15:09:37 -07:00
project . _compilerOptionsByConfiguration [ configKey ] = compilerOptions ;
2015-10-13 14:31:29 -07:00
}
}
// The frameworks node is where target frameworks go
/ *
{
"frameworks" : {
"net45" : {
} ,
"dnxcore50" : {
}
}
}
* /
2015-10-15 15:09:37 -07:00
var frameworks = projectJsonObject . ValueAsJsonObject ( "frameworks" ) ;
2015-10-13 14:31:29 -07:00
if ( frameworks ! = null )
{
2015-10-15 15:09:37 -07:00
foreach ( var frameworkKey in frameworks . Keys )
2015-10-13 14:31:29 -07:00
{
try
{
2015-10-15 15:09:37 -07:00
var frameworkToken = frameworks . ValueAsJsonObject ( frameworkKey ) ;
var success = BuildTargetFrameworkNode ( project , frameworkKey , frameworkToken ) ;
2015-10-13 14:31:29 -07:00
if ( ! success )
{
diagnostics ? . Add (
new DiagnosticMessage (
ErrorCodes . NU1008 ,
$"\" { frameworkKey } \ " is an unsupported framework." ,
project . ProjectFilePath ,
DiagnosticMessageSeverity . Error ,
2015-10-15 15:09:37 -07:00
frameworkToken . Line ,
frameworkToken . Column ) ) ;
2015-10-13 14:31:29 -07:00
}
}
catch ( Exception ex )
{
2015-10-15 15:09:37 -07:00
throw FileFormatException . Create ( ex , frameworks . Value ( frameworkKey ) , project . ProjectFilePath ) ;
2015-10-13 14:31:29 -07:00
}
}
}
}
/// <summary>
/// Parse a Json object which represents project configuration for a specified framework
/// </summary>
/// <param name="frameworkKey">The name of the framework</param>
/// <param name="frameworkValue">The Json object represent the settings</param>
/// <returns>Returns true if it successes.</returns>
2015-10-15 15:09:37 -07:00
private bool BuildTargetFrameworkNode ( Project project , string frameworkKey , JsonObject frameworkValue )
2015-10-13 14:31:29 -07:00
{
// If no compilation options are provided then figure them out from the node
var compilerOptions = GetCompilationOptions ( frameworkValue ) ? ?
2015-10-20 13:27:56 -07:00
new CommonCompilerOptions ( ) ;
2015-10-13 14:31:29 -07:00
var frameworkName = NuGetFramework . Parse ( frameworkKey ) ;
// If it's not unsupported then keep it
if ( frameworkName . IsUnsupported )
{
// REVIEW: Should we skip unsupported target frameworks
return false ;
}
// Add the target framework specific define
var defines = new HashSet < string > ( compilerOptions . Defines ? ? Enumerable . Empty < string > ( ) ) ;
var frameworkDefine = MakeDefaultTargetFrameworkDefine ( frameworkName ) ;
if ( ! string . IsNullOrEmpty ( frameworkDefine ) )
{
defines . Add ( frameworkDefine ) ;
}
compilerOptions . Defines = defines ;
var targetFrameworkInformation = new TargetFrameworkInformation
{
FrameworkName = frameworkName ,
2015-10-17 08:36:22 -07:00
Dependencies = new List < LibraryRange > ( ) ,
2015-10-28 02:21:00 -07:00
CompilerOptions = compilerOptions ,
Line = frameworkValue . Line ,
Column = frameworkValue . Column
2015-10-13 14:31:29 -07:00
} ;
var frameworkDependencies = new List < LibraryRange > ( ) ;
PopulateDependencies (
project . ProjectFilePath ,
frameworkDependencies ,
frameworkValue ,
"dependencies" ,
isGacOrFrameworkReference : false ) ;
var frameworkAssemblies = new List < LibraryRange > ( ) ;
PopulateDependencies (
project . ProjectFilePath ,
frameworkAssemblies ,
frameworkValue ,
"frameworkAssemblies" ,
isGacOrFrameworkReference : true ) ;
frameworkDependencies . AddRange ( frameworkAssemblies ) ;
targetFrameworkInformation . Dependencies = frameworkDependencies ;
2015-10-15 15:09:37 -07:00
targetFrameworkInformation . WrappedProject = frameworkValue . ValueAsString ( "wrappedProject" ) ;
2015-10-13 14:31:29 -07:00
2015-10-15 15:09:37 -07:00
var binNode = frameworkValue . ValueAsJsonObject ( "bin" ) ;
2015-10-13 14:31:29 -07:00
if ( binNode ! = null )
{
2015-10-15 15:09:37 -07:00
targetFrameworkInformation . AssemblyPath = binNode . ValueAsString ( "assembly" ) ;
targetFrameworkInformation . PdbPath = binNode . ValueAsString ( "pdb" ) ;
2015-10-13 14:31:29 -07:00
}
project . _targetFrameworks [ frameworkName ] = targetFrameworkInformation ;
return true ;
}
2015-10-20 13:27:56 -07:00
private static CommonCompilerOptions GetCompilationOptions ( JsonObject rawObject )
2015-10-13 14:31:29 -07:00
{
2015-10-15 15:09:37 -07:00
var rawOptions = rawObject . ValueAsJsonObject ( "compilationOptions" ) ;
2015-10-13 14:31:29 -07:00
if ( rawOptions = = null )
{
return null ;
}
2015-10-20 13:27:56 -07:00
return new CommonCompilerOptions
2015-10-13 14:31:29 -07:00
{
2015-10-15 15:09:37 -07:00
Defines = rawOptions . ValueAsStringArray ( "define" ) ,
LanguageVersion = rawOptions . ValueAsString ( "languageVersion" ) ,
AllowUnsafe = rawOptions . ValueAsNullableBoolean ( "allowUnsafe" ) ,
Platform = rawOptions . ValueAsString ( "platform" ) ,
WarningsAsErrors = rawOptions . ValueAsNullableBoolean ( "warningsAsErrors" ) ,
Optimize = rawOptions . ValueAsNullableBoolean ( "optimize" ) ,
KeyFile = rawOptions . ValueAsString ( "keyFile" ) ,
DelaySign = rawOptions . ValueAsNullableBoolean ( "delaySign" ) ,
2015-11-09 00:19:39 -08:00
UseOssSigning = rawOptions . ValueAsNullableBoolean ( "useOssSigning" ) ,
2015-10-15 15:09:37 -07:00
EmitEntryPoint = rawOptions . ValueAsNullableBoolean ( "emitEntryPoint" )
2015-10-13 14:31:29 -07:00
} ;
}
2015-10-15 15:09:37 -07:00
private static string MakeDefaultTargetFrameworkDefine ( NuGetFramework targetFramework )
2015-10-13 14:31:29 -07:00
{
var shortName = targetFramework . GetTwoDigitShortFolderName ( ) ;
if ( targetFramework . IsPCL )
{
return null ;
}
var candidateName = shortName . ToUpperInvariant ( ) ;
// Replace '-', '.', and '+' in the candidate name with '_' because TFMs with profiles use those (like "net40-client")
// and we want them representable as defines (i.e. "NET40_CLIENT")
candidateName = candidateName . Replace ( '-' , '_' ) . Replace ( '+' , '_' ) . Replace ( '.' , '_' ) ;
// We require the following from our Target Framework Define names
// Starts with A-Z or _
// Contains only A-Z, 0-9 and _
if ( ! string . IsNullOrEmpty ( candidateName ) & &
( char . IsLetter ( candidateName [ 0 ] ) | | candidateName [ 0 ] = = '_' ) & &
candidateName . All ( c = > Char . IsLetterOrDigit ( c ) | | c = = '_' ) )
{
return candidateName ;
}
return null ;
}
2015-10-17 22:42:50 -07:00
private static bool HasProjectFile ( string path )
{
string projectPath = Path . Combine ( path , Project . FileName ) ;
return File . Exists ( projectPath ) ;
}
2015-10-13 14:31:29 -07:00
}
}