2016-03-07 14:24:36 -06:00
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Net.Http ;
using System.Text ;
using System.Text.RegularExpressions ;
2016-05-25 08:45:18 -05:00
using System.Threading.Tasks ;
2016-03-07 14:24:36 -06:00
using Microsoft.DotNet.Cli.Build.Framework ;
using Newtonsoft.Json ;
using Newtonsoft.Json.Linq ;
using NuGet.Versioning ;
namespace Microsoft.DotNet.Scripts
{
public static class UpdateFilesTargets
{
private static HttpClient s_client = new HttpClient ( ) ;
[Target(nameof(GetDependencies), nameof(ReplaceVersions))]
public static BuildTargetResult UpdateFiles ( BuildTargetContext c ) = > c . Success ( ) ;
/// <summary>
/// Gets all the dependency information and puts it in the build properties.
/// </summary>
[Target]
public static BuildTargetResult GetDependencies ( BuildTargetContext c )
{
2016-05-25 08:45:18 -05:00
List < DependencyInfo > dependencyInfos = c . GetDependencyInfos ( ) ;
2016-03-07 14:24:36 -06:00
2016-05-25 08:45:18 -05:00
dependencyInfos . Add ( CreateDependencyInfo ( "CoreFx" , Config . Instance . CoreFxVersionUrl ) . Result ) ;
2016-06-07 12:14:14 -05:00
dependencyInfos . Add ( CreateDependencyInfo ( "CoreClr" , Config . Instance . CoreClrVersionUrl ) . Result ) ;
2016-06-03 01:03:48 -05:00
dependencyInfos . Add ( CreateDependencyInfo ( "Roslyn" , Config . Instance . RoslynVersionUrl ) . Result ) ;
2016-05-26 08:41:18 -05:00
dependencyInfos . Add ( CreateDependencyInfo ( "CoreSetup" , Config . Instance . CoreSetupVersionUrl ) . Result ) ;
2016-03-07 14:24:36 -06:00
2016-05-25 08:45:18 -05:00
return c . Success ( ) ;
}
private static async Task < DependencyInfo > CreateDependencyInfo ( string name , string packageVersionsUrl )
{
List < PackageInfo > newPackageVersions = new List < PackageInfo > ( ) ;
using ( Stream versionsStream = await s_client . GetStreamAsync ( packageVersionsUrl ) )
using ( StreamReader reader = new StreamReader ( versionsStream ) )
2016-03-07 14:24:36 -06:00
{
2016-05-25 08:45:18 -05:00
string currentLine ;
while ( ( currentLine = await reader . ReadLineAsync ( ) ) ! = null )
{
int spaceIndex = currentLine . IndexOf ( ' ' ) ;
2016-03-07 14:24:36 -06:00
2016-05-25 08:45:18 -05:00
newPackageVersions . Add ( new PackageInfo ( )
{
Id = currentLine . Substring ( 0 , spaceIndex ) ,
Version = new NuGetVersion ( currentLine . Substring ( spaceIndex + 1 ) )
} ) ;
}
}
string newReleaseVersion = newPackageVersions
. Where ( p = > p . Version . IsPrerelease )
. Select ( p = > p . Version . Release )
. FirstOrDefault ( )
? ?
// if there are no prerelease versions, just grab the first version
newPackageVersions
. Select ( p = > p . Version . ToNormalizedString ( ) )
. FirstOrDefault ( ) ;
return new DependencyInfo ( )
{
Name = name ,
NewVersions = newPackageVersions ,
NewReleaseVersion = newReleaseVersion
} ;
2016-03-07 14:24:36 -06:00
}
2016-05-26 08:41:18 -05:00
[Target(nameof(ReplaceProjectJson), nameof(ReplaceDependencyVersions))]
2016-03-07 14:24:36 -06:00
public static BuildTargetResult ReplaceVersions ( BuildTargetContext c ) = > c . Success ( ) ;
/// <summary>
/// Replaces all the dependency versions in the project.json files.
/// </summary>
[Target]
public static BuildTargetResult ReplaceProjectJson ( BuildTargetContext c )
{
2016-04-04 18:38:49 -05:00
List < DependencyInfo > dependencyInfos = c . GetDependencyInfos ( ) ;
2016-03-07 14:24:36 -06:00
2016-05-25 09:11:54 -05:00
const string noUpdateFileName = ".noautoupdate" ;
2016-04-11 14:01:59 -05:00
IEnumerable < string > projectJsonFiles = Enumerable . Union (
Directory . GetFiles ( Dirs . RepoRoot , "project.json" , SearchOption . AllDirectories ) ,
2016-05-26 09:43:37 -05:00
Directory . GetFiles ( Path . Combine ( Dirs . RepoRoot , @"src\dotnet\commands\dotnet-new" ) , "project.json.template" , SearchOption . AllDirectories ) )
2016-05-27 12:37:17 -07:00
. Where ( p = > ! File . Exists ( Path . Combine ( Path . GetDirectoryName ( p ) , noUpdateFileName ) ) & &
! Path . GetDirectoryName ( p ) . EndsWith ( "CSharp_Web" , StringComparison . Ordinal ) ) ;
2016-03-07 14:24:36 -06:00
JObject projectRoot ;
foreach ( string projectJsonFile in projectJsonFiles )
{
try
{
projectRoot = ReadProject ( projectJsonFile ) ;
}
catch ( Exception e )
{
c . Warn ( $"Non-fatal exception occurred reading '{projectJsonFile}'. Skipping file. Exception: {e}. " ) ;
continue ;
}
2016-04-22 16:39:44 -07:00
if ( projectRoot = = null )
{
c . Warn ( $"A non valid JSON file was encountered '{projectJsonFile}'. Skipping file." ) ;
continue ;
}
2016-03-07 14:24:36 -06:00
bool changedAnyPackage = FindAllDependencyProperties ( projectRoot )
. Select ( dependencyProperty = > ReplaceDependencyVersion ( dependencyProperty , dependencyInfos ) )
. ToArray ( )
. Any ( shouldWrite = > shouldWrite ) ;
if ( changedAnyPackage )
{
c . Info ( $"Writing changes to {projectJsonFile}" ) ;
WriteProject ( projectRoot , projectJsonFile ) ;
}
}
return c . Success ( ) ;
}
/// <summary>
/// Replaces the single dependency with the updated version, if it matches any of the dependencies that need to be updated.
/// </summary>
private static bool ReplaceDependencyVersion ( JProperty dependencyProperty , List < DependencyInfo > dependencyInfos )
{
string id = dependencyProperty . Name ;
foreach ( DependencyInfo dependencyInfo in dependencyInfos )
{
2016-05-25 08:45:18 -05:00
foreach ( PackageInfo packageInfo in dependencyInfo . NewVersions )
2016-03-07 14:24:36 -06:00
{
2016-05-25 08:45:18 -05:00
if ( id = = packageInfo . Id )
2016-03-07 14:24:36 -06:00
{
2016-06-02 14:08:39 -05:00
string oldVersion ;
2016-03-07 14:24:36 -06:00
if ( dependencyProperty . Value is JObject )
{
2016-06-02 14:08:39 -05:00
oldVersion = ( string ) dependencyProperty . Value [ "version" ] ;
2016-03-07 14:24:36 -06:00
}
else
{
2016-06-02 14:08:39 -05:00
oldVersion = ( string ) dependencyProperty . Value ;
2016-03-07 14:24:36 -06:00
}
2016-06-02 14:08:39 -05:00
string newVersion = packageInfo . Version . ToNormalizedString ( ) ;
if ( oldVersion ! = newVersion )
{
if ( dependencyProperty . Value is JObject )
{
dependencyProperty . Value [ "version" ] = newVersion ;
}
else
{
dependencyProperty . Value = newVersion ;
}
// mark the DependencyInfo as updated so we can tell which dependencies were updated
dependencyInfo . IsUpdated = true ;
return true ;
}
2016-03-07 14:24:36 -06:00
}
}
}
return false ;
}
private static JObject ReadProject ( string projectJsonPath )
{
using ( TextReader projectFileReader = File . OpenText ( projectJsonPath ) )
{
var projectJsonReader = new JsonTextReader ( projectFileReader ) ;
var serializer = new JsonSerializer ( ) ;
return serializer . Deserialize < JObject > ( projectJsonReader ) ;
}
}
private static void WriteProject ( JObject projectRoot , string projectJsonPath )
{
string projectJson = JsonConvert . SerializeObject ( projectRoot , Formatting . Indented ) ;
File . WriteAllText ( projectJsonPath , projectJson + Environment . NewLine ) ;
}
private static IEnumerable < JProperty > FindAllDependencyProperties ( JObject projectJsonRoot )
{
return projectJsonRoot
. Descendants ( )
. OfType < JProperty > ( )
. Where ( property = > property . Name = = "dependencies" )
. Select ( property = > property . Value )
. SelectMany ( o = > o . Children < JProperty > ( ) ) ;
}
/// <summary>
2016-05-27 13:24:39 -07:00
/// Replaces version numbers that are hard-coded in DependencyVersions.cs and CliDependencyVersions.cs.
2016-03-07 14:24:36 -06:00
/// </summary>
[Target]
2016-05-26 08:41:18 -05:00
public static BuildTargetResult ReplaceDependencyVersions ( BuildTargetContext c )
2016-03-07 14:24:36 -06:00
{
2016-05-26 08:41:18 -05:00
ReplaceFileContents ( @"build_projects\shared-build-targets-utils\DependencyVersions.cs" , fileContents = >
2016-04-05 10:46:08 -05:00
{
2016-06-07 12:14:14 -05:00
fileContents = ReplaceDependencyVersion ( c , fileContents , "CoreCLRVersion" , "Microsoft.NETCore.Runtime.CoreCLR" ) ;
2016-06-07 12:36:13 -05:00
fileContents = ReplaceDependencyVersion ( c , fileContents , "JitVersion" , "Microsoft.NETCore.Jit" ) ;
2016-05-27 13:24:39 -07:00
return fileContents ;
} ) ;
ReplaceFileContents ( @"build_projects\dotnet-cli-build\CliDependencyVersions.cs" , fileContents = >
{
2016-06-07 12:14:14 -05:00
fileContents = ReplaceDependencyVersion ( c , fileContents , "SharedFrameworkVersion" , "Microsoft.NETCore.App" ) ;
fileContents = ReplaceDependencyVersion ( c , fileContents , "SharedHostVersion" , "Microsoft.NETCore.DotNetHost" ) ;
2016-05-26 08:41:18 -05:00
return fileContents ;
2016-04-05 10:46:08 -05:00
} ) ;
return c . Success ( ) ;
}
2016-06-07 12:14:14 -05:00
private static string ReplaceDependencyVersion ( BuildTargetContext c , string fileContents , string dependencyPropertyName , string packageId )
2016-04-05 10:46:08 -05:00
{
2016-06-07 12:14:14 -05:00
Regex regex = new Regex ( $@"{dependencyPropertyName} = ""(?<version>.*)"";" ) ;
string newVersion = c . GetNewVersion ( packageId ) ;
2016-04-05 10:46:08 -05:00
2016-06-07 12:14:14 -05:00
return regex . ReplaceGroupValue ( fileContents , "version" , newVersion ) ;
2016-05-26 08:41:18 -05:00
}
2016-06-07 12:14:14 -05:00
private static string GetNewVersion ( this BuildTargetContext c , string packageId )
2016-05-26 08:41:18 -05:00
{
2016-06-07 12:14:14 -05:00
string newVersion = c . GetDependencyInfos ( )
. SelectMany ( d = > d . NewVersions )
2016-05-26 08:41:18 -05:00
. FirstOrDefault ( p = > p . Id = = packageId )
? . Version
. ToNormalizedString ( ) ;
if ( string . IsNullOrEmpty ( newVersion ) )
{
2016-06-07 12:14:14 -05:00
c . Error ( $"Could not find package version information for '{packageId}'" ) ;
return $"DEPENDENCY '{packageId}' NOT FOUND" ;
2016-05-26 08:41:18 -05:00
}
2016-06-07 12:14:14 -05:00
return newVersion ;
2016-05-26 08:41:18 -05:00
}
2016-04-05 10:46:08 -05:00
private static void ReplaceFileContents ( string repoRelativePath , Func < string , string > replacement )
{
string fullPath = Path . Combine ( Dirs . RepoRoot , repoRelativePath ) ;
string contents = File . ReadAllText ( fullPath ) ;
2016-03-07 14:24:36 -06:00
2016-04-05 10:46:08 -05:00
contents = replacement ( contents ) ;
File . WriteAllText ( fullPath , contents , Encoding . UTF8 ) ;
}
private static string ReplaceGroupValue ( this Regex regex , string input , string groupName , string newValue )
{
return regex . Replace ( input , m = >
2016-03-07 14:24:36 -06:00
{
string replacedValue = m . Value ;
2016-04-05 10:46:08 -05:00
Group group = m . Groups [ groupName ] ;
int startIndex = group . Index - m . Index ;
2016-03-07 14:24:36 -06:00
2016-04-05 10:46:08 -05:00
replacedValue = replacedValue . Remove ( startIndex , group . Length ) ;
replacedValue = replacedValue . Insert ( startIndex , newValue ) ;
2016-03-07 14:24:36 -06:00
return replacedValue ;
} ) ;
}
}
}