22// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33
44using System ;
5+ using System . Collections . Concurrent ;
6+ using System . Collections . Generic ;
57using System . ComponentModel . Composition ;
68using System . Globalization ;
79using System . IO ;
1012using System . Threading . Tasks ;
1113using NuGet . Common ;
1214using NuGet . Configuration ;
15+ using NuGet . Frameworks ;
1316using NuGet . PackageManagement ;
1417using NuGet . Packaging ;
1518using NuGet . Packaging . Core ;
@@ -36,6 +39,9 @@ public class InstallCommand : DownloadCommandBase
3639 [ Option ( typeof ( NuGetCommand ) , "InstallCommandDependencyVersion" ) ]
3740 public string DependencyVersion { get ; set ; }
3841
42+ [ Option ( typeof ( NuGetCommand ) , "InstallCommandFrameworkDescription" ) ]
43+ public string Framework { get ; set ; }
44+
3945 [ Option ( typeof ( NuGetCommand ) , "InstallCommandExcludeVersionDescription" , AltName = "x" ) ]
4046 public bool ExcludeVersion { get ; set ; }
4147
@@ -51,24 +57,25 @@ public class InstallCommand : DownloadCommandBase
5157 [ ImportingConstructor ]
5258 protected internal InstallCommand ( )
5359 {
54- // On mono, parallel builds are broken for some reason. See https://gist.github.com/4201936 for the errors
55- // That are thrown.
56- DisableParallelProcessing = RuntimeEnvironmentHelper . IsMono ;
5760 }
5861
5962 public override Task ExecuteCommandAsync ( )
6063 {
64+ // On mono, parallel builds are broken for some reason. See https://gist.github.com/4201936 for the errors
65+ // That are thrown.
66+ DisableParallelProcessing |= RuntimeEnvironmentHelper . IsMono ;
67+
6168 if ( DisableParallelProcessing )
6269 {
6370 HttpSourceResourceProvider . Throttle = SemaphoreSlimThrottle . CreateBinarySemaphore ( ) ;
6471 }
6572
6673 CalculateEffectivePackageSaveMode ( ) ;
6774 CalculateEffectiveSettings ( ) ;
68- string installPath = ResolveInstallPath ( ) ;
75+ var installPath = ResolveInstallPath ( ) ;
6976
70- string configFilePath = Path . GetFullPath ( Arguments . Count == 0 ? Constants . PackageReferenceFile : Arguments [ 0 ] ) ;
71- string configFileName = Path . GetFileName ( configFilePath ) ;
77+ var configFilePath = Path . GetFullPath ( Arguments . Count == 0 ? Constants . PackageReferenceFile : Arguments [ 0 ] ) ;
78+ var configFileName = Path . GetFileName ( configFilePath ) ;
7279
7380 // If the first argument is a packages.xxx.config file, install everything it lists
7481 // Otherwise, treat the first argument as a package Id
@@ -80,27 +87,27 @@ public override Task ExecuteCommandAsync()
8087 if ( Console != null && RequireConsent &&
8188 new PackageRestoreConsent ( Settings ) . IsGranted )
8289 {
83- string message = String . Format (
90+ var message = string . Format (
8491 CultureInfo . CurrentCulture ,
8592 LocalizedResourceManager . GetString ( "RestoreCommandPackageRestoreOptOutMessage" ) ,
8693 NuGetResources . PackageRestoreConsentCheckBoxText . Replace ( "&" , "" ) ) ;
8794 Console . WriteLine ( message ) ;
8895 }
8996
90- return PerformV2Restore ( configFilePath , installPath ) ;
97+ return PerformV2RestoreAsync ( configFilePath , installPath ) ;
9198 }
9299 else
93100 {
94101 var packageId = Arguments [ 0 ] ;
95102 var version = Version != null ? new NuGetVersion ( Version ) : null ;
96- return InstallPackage ( packageId , version , installPath ) ;
103+ return InstallPackageAsync ( packageId , version , installPath ) ;
97104 }
98105 }
99106
100107 private void CalculateEffectiveSettings ( )
101108 {
102109 // If the SolutionDir is specified, use the .nuget directory under it to determine the solution-level settings
103- if ( ! String . IsNullOrEmpty ( SolutionDirectory ) )
110+ if ( ! string . IsNullOrEmpty ( SolutionDirectory ) )
104111 {
105112 var path = Path . Combine ( SolutionDirectory . TrimEnd ( Path . DirectorySeparatorChar ) , NuGetConstants . NuGetSolutionSettingsFolder ) ;
106113
@@ -119,20 +126,20 @@ private void CalculateEffectiveSettings()
119126
120127 internal string ResolveInstallPath ( )
121128 {
122- if ( ! String . IsNullOrEmpty ( OutputDirectory ) )
129+ if ( ! string . IsNullOrEmpty ( OutputDirectory ) )
123130 {
124131 // Use the OutputDirectory if specified.
125132 return OutputDirectory ;
126133 }
127134
128- string installPath = SettingsUtility . GetRepositoryPath ( Settings ) ;
129- if ( ! String . IsNullOrEmpty ( installPath ) )
135+ var installPath = SettingsUtility . GetRepositoryPath ( Settings ) ;
136+ if ( ! string . IsNullOrEmpty ( installPath ) )
130137 {
131138 // If a value is specified in config, use that.
132139 return installPath ;
133140 }
134141
135- if ( ! String . IsNullOrEmpty ( SolutionDirectory ) )
142+ if ( ! string . IsNullOrEmpty ( SolutionDirectory ) )
136143 {
137144 // For package restore scenarios, deduce the path of the packages directory from the solution directory.
138145 return Path . Combine ( SolutionDirectory , CommandLineConstants . PackagesDirectoryName ) ;
@@ -142,7 +149,7 @@ internal string ResolveInstallPath()
142149 return CurrentDirectory ;
143150 }
144151
145- private async Task PerformV2Restore ( string packagesConfigFilePath , string installPath )
152+ private async Task PerformV2RestoreAsync ( string packagesConfigFilePath , string installPath )
146153 {
147154 var sourceRepositoryProvider = GetSourceRepositoryProvider ( ) ;
148155 var nuGetPackageManager = new NuGetPackageManager ( sourceRepositoryProvider , Settings , installPath , ExcludeVersion ) ;
@@ -158,15 +165,17 @@ private async Task PerformV2Restore(string packagesConfigFilePath, string instal
158165 isMissing : true ) ) ;
159166
160167 var packageSources = GetPackageSources ( Settings ) ;
161-
168+
162169 Console . PrintPackageSources ( packageSources ) ;
163170
171+ var failedEvents = new ConcurrentQueue < PackageRestoreFailedEventArgs > ( ) ;
172+
164173 var packageRestoreContext = new PackageRestoreContext (
165174 nuGetPackageManager ,
166175 packageRestoreData ,
167176 CancellationToken . None ,
168177 packageRestoredEvent : null ,
169- packageRestoreFailedEvent : null ,
178+ packageRestoreFailedEvent : ( sender , args ) => { failedEvents . Enqueue ( args ) ; } ,
170179 sourceRepositories : packageSources . Select ( sourceRepositoryProvider . CreateRepository ) ,
171180 maxNumberOfParallelTasks : DisableParallelProcessing ? 1 : PackageManagementConstants . DefaultMaxDegreeOfParallelism ) ;
172181
@@ -189,7 +198,7 @@ private async Task PerformV2Restore(string packagesConfigFilePath, string instal
189198
190199 var downloadContext = new PackageDownloadContext ( cacheContext , installPath , DirectDownload ) ;
191200
192- await PackageRestoreManager . RestoreMissingPackagesAsync (
201+ var result = await PackageRestoreManager . RestoreMissingPackagesAsync (
193202 packageRestoreContext ,
194203 new ConsoleProjectContext ( Console ) ,
195204 downloadContext ) ;
@@ -198,6 +207,17 @@ await PackageRestoreManager.RestoreMissingPackagesAsync(
198207 {
199208 GetDownloadResultUtility . CleanUpDirectDownloads ( downloadContext ) ;
200209 }
210+
211+ if ( ! result . Restored || failedEvents . Count > 0 )
212+ {
213+ // Log errors if they exist
214+ foreach ( var message in failedEvents . Select ( e => new RestoreLogMessage ( LogLevel . Error , NuGetLogCode . Undefined , e . Exception . Message ) ) )
215+ {
216+ await Console . LogAsync ( message ) ;
217+ }
218+
219+ throw new ExitCodeException ( 1 ) ;
220+ }
201221 }
202222 }
203223
@@ -221,9 +241,9 @@ private DependencyBehavior TryGetDependencyBehavior(string behaviorStr)
221241 private DependencyBehavior GetDependencyBehavior ( )
222242 {
223243 // If dependencyVersion is not set by either the config or commandline, default dependency behavior is 'Lowest'.
224- DependencyBehavior dependencyBehavior = DependencyBehavior . Lowest ;
244+ var dependencyBehavior = DependencyBehavior . Lowest ;
225245
226- string settingsDependencyVersion = SettingsUtility . GetConfigValue ( Settings , "dependencyVersion" ) ;
246+ var settingsDependencyVersion = SettingsUtility . GetConfigValue ( Settings , "dependencyVersion" ) ;
227247
228248 // Check to see if commandline flag is set. Else check for dependencyVersion in .config.
229249 if ( ! string . IsNullOrEmpty ( DependencyVersion ) )
@@ -238,28 +258,33 @@ private DependencyBehavior GetDependencyBehavior()
238258 return dependencyBehavior ;
239259 }
240260
241- private async Task InstallPackage (
261+ private async Task InstallPackageAsync (
242262 string packageId ,
243263 NuGetVersion version ,
244264 string installPath )
245265 {
246266 if ( version == null )
247267 {
248- NoCache = true ;
268+ // Avoid searching for the highest version in the global packages folder,
269+ // it needs to come from the feeds instead. Once found it may come from
270+ // the global packages folder unless NoCache is true.
271+ ExcludeCacheAsSource = true ;
249272 }
250273
251- var folderProject = new FolderNuGetProject (
252- installPath ,
253- new PackagePathResolver ( installPath , ! ExcludeVersion ) ) ;
274+ var framework = GetTargetFramework ( ) ;
275+
276+ // Create the project and set the framework if available.
277+ var project = new InstallCommandProject (
278+ root : installPath ,
279+ packagePathResolver : new PackagePathResolver ( installPath , ! ExcludeVersion ) ,
280+ targetFramework : framework ) ;
254281
255282 var sourceRepositoryProvider = GetSourceRepositoryProvider ( ) ;
256283 var packageManager = new NuGetPackageManager ( sourceRepositoryProvider , Settings , installPath ) ;
257284
258285 var packageSources = GetPackageSources ( Settings ) ;
259-
260- Console . PrintPackageSources ( packageSources ) ;
261-
262286 var primaryRepositories = packageSources . Select ( sourceRepositoryProvider . CreateRepository ) ;
287+ Console . PrintPackageSources ( packageSources ) ;
263288
264289 var allowPrerelease = Prerelease || ( version != null && version . IsPrerelease ) ;
265290
@@ -273,10 +298,15 @@ private async Task InstallPackage(
273298
274299 if ( version == null )
275300 {
301+ // Write out a helpful message before the http messages are shown
302+ Console . Log ( LogLevel . Minimal , string . Format (
303+ CultureInfo . CurrentCulture ,
304+ LocalizedResourceManager . GetString ( "InstallPackageMessage" ) , packageId , installPath ) ) ;
305+
276306 // Find the latest version using NuGetPackageManager
277307 var resolvePackage = await NuGetPackageManager . GetLatestVersionAsync (
278308 packageId ,
279- folderProject ,
309+ project ,
280310 resolutionContext ,
281311 primaryRepositories ,
282312 Console ,
@@ -295,9 +325,23 @@ private async Task InstallPackage(
295325 version = resolvePackage . LatestVersion ;
296326 }
297327
328+ // Get a list of packages already in the folder.
329+ var installedPackages = await project . GetFolderPackagesAsync ( CancellationToken . None ) ;
330+
331+ // Find existing versions of the package
332+ var alreadyInstalledVersions = new HashSet < NuGetVersion > ( installedPackages
333+ . Where ( e => StringComparer . OrdinalIgnoreCase . Equals ( packageId , e . PackageIdentity . Id ) )
334+ . Select ( e => e . PackageIdentity . Version ) ) ;
335+
298336 var packageIdentity = new PackageIdentity ( packageId , version ) ;
299337
300- if ( folderProject . PackageExists ( packageIdentity ) )
338+ // Check if the package already exists or a higher version exists already.
339+ var skipInstall = project . PackageExists ( packageIdentity ) ;
340+
341+ // For SxS allow other versions to install. For non-SxS skip if a higher version exists.
342+ skipInstall |= ( ExcludeVersion && alreadyInstalledVersions . Any ( e => e >= version ) ) ;
343+
344+ if ( skipInstall )
301345 {
302346 var message = string . Format (
303347 CultureInfo . CurrentCulture ,
@@ -318,15 +362,15 @@ private async Task InstallPackage(
318362 projectContext . PackageExtractionContext . PackageSaveMode = EffectivePackageSaveMode ;
319363 }
320364
321- using ( var cacheContext = new SourceCacheContext ( ) )
365+ using ( var cacheContext = new SourceCacheContext ( ) )
322366 {
323367 cacheContext . NoCache = NoCache ;
324368 cacheContext . DirectDownload = DirectDownload ;
325369
326370 var downloadContext = new PackageDownloadContext ( cacheContext , installPath , DirectDownload ) ;
327371
328372 await packageManager . InstallPackageAsync (
329- folderProject ,
373+ project ,
330374 packageIdentity ,
331375 resolutionContext ,
332376 projectContext ,
@@ -342,5 +386,30 @@ await packageManager.InstallPackageAsync(
342386 }
343387 }
344388 }
389+
390+ /// <summary>
391+ /// Parse the Framework parameter or use Any as the default framework.
392+ /// </summary>
393+ private NuGetFramework GetTargetFramework ( )
394+ {
395+ var targetFramework = NuGetFramework . AnyFramework ;
396+
397+ if ( ! string . IsNullOrEmpty ( Framework ) )
398+ {
399+ targetFramework = NuGetFramework . Parse ( Framework ) ;
400+ }
401+
402+ if ( targetFramework . IsUnsupported )
403+ {
404+ // Fail with a helpful message if the user provided an invalid framework.
405+ var message = string . Format ( CultureInfo . CurrentCulture ,
406+ LocalizedResourceManager . GetString ( "UnsupportedFramework" ) ,
407+ Framework ) ;
408+
409+ throw new ArgumentException ( message ) ;
410+ }
411+
412+ return targetFramework ;
413+ }
345414 }
346415}
0 commit comments