2015-12-11 07:00:35 +00: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 Microsoft.DotNet.Cli.Utils ;
using Microsoft.DotNet.ProjectModel ;
using Microsoft.DotNet.ProjectModel.Compilation ;
using NuGet.Frameworks ;
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
2015-12-19 00:39:43 +00:00
using Microsoft.Extensions.PlatformAbstractions ;
2016-02-02 18:50:59 +00:00
using Microsoft.DotNet.Files ;
2016-01-09 00:20:39 +00:00
using Microsoft.DotNet.Tools.Common ;
2016-01-11 21:40:47 +00:00
using Microsoft.DotNet.ProjectModel.Utilities ;
2015-12-11 07:00:35 +00:00
namespace Microsoft.DotNet.Tools.Publish
{
2016-01-31 05:47:50 +00:00
public partial class PublishCommand
2015-12-11 07:00:35 +00:00
{
public string ProjectPath { get ; set ; }
public string Configuration { get ; set ; }
public string OutputPath { get ; set ; }
public string Framework { get ; set ; }
public string Runtime { get ; set ; }
2015-12-21 20:23:05 +00:00
public bool NativeSubdirectories { get ; set ; }
2015-12-11 07:00:35 +00:00
public NuGetFramework NugetFramework { get ; set ; }
public IEnumerable < ProjectContext > ProjectContexts { get ; set ; }
public int NumberOfProjects { get ; private set ; }
public int NumberOfPublishedProjects { get ; private set ; }
public bool TryPrepareForPublish ( )
{
if ( Framework ! = null )
{
NugetFramework = NuGetFramework . Parse ( Framework ) ;
if ( NugetFramework . IsUnsupported )
{
Reporter . Output . WriteLine ( $"Unsupported framework {Framework}." . Red ( ) ) ;
return false ;
}
}
2015-12-19 00:39:43 +00:00
ProjectContexts = SelectContexts ( ProjectPath , NugetFramework , Runtime ) ;
if ( ! ProjectContexts . Any ( ) )
2015-12-11 07:00:35 +00:00
{
string errMsg = $"'{ProjectPath}' cannot be published for '{Framework ?? " < no framework provided > "}' '{Runtime ?? " < no runtime provided > "}'" ;
Reporter . Output . WriteLine ( errMsg . Red ( ) ) ;
return false ;
}
return true ;
}
public void PublishAllProjects ( )
{
NumberOfPublishedProjects = 0 ;
NumberOfProjects = 0 ;
2016-01-11 21:40:47 +00:00
2015-12-11 07:00:35 +00:00
foreach ( var project in ProjectContexts )
{
2015-12-21 20:23:05 +00:00
if ( PublishProjectContext ( project , OutputPath , Configuration , NativeSubdirectories ) )
2015-12-11 07:00:35 +00:00
{
NumberOfPublishedProjects + + ;
}
NumberOfProjects + + ;
}
}
/// <summary>
/// Publish the project for given 'framework (ex - dnxcore50)' and 'runtimeID (ex - win7-x64)'
/// </summary>
/// <param name="context">project that is to be published</param>
2016-01-20 23:41:46 +00:00
/// <param name="baseOutputPath">Location of published files</param>
2015-12-11 07:00:35 +00:00
/// <param name="configuration">Debug or Release</param>
2016-01-20 23:41:46 +00:00
/// <param name="nativeSubdirectories"></param>
2015-12-11 07:00:35 +00:00
/// <returns>Return 0 if successful else return non-zero</returns>
2016-01-26 14:39:13 +00:00
private static bool PublishProjectContext ( ProjectContext context , string outputPath , string configuration , bool nativeSubdirectories )
2015-12-11 07:00:35 +00:00
{
Reporter . Output . WriteLine ( $"Publishing {context.RootProject.Identity.Name.Yellow()} for {context.TargetFramework.DotNetFrameworkName.Yellow()}/{context.RuntimeIdentifier.Yellow()}" ) ;
var options = context . ProjectFile . GetCompilerOptions ( context . TargetFramework , configuration ) ;
2016-01-26 14:39:13 +00:00
if ( string . IsNullOrEmpty ( outputPath ) )
{
outputPath = context . GetOutputPathCalculator ( ) . GetOutputDirectoryPath ( configuration ) ;
}
2015-12-11 07:00:35 +00:00
2016-01-11 21:40:47 +00:00
var contextVariables = new Dictionary < string , string >
{
{ "publish:ProjectPath" , context . ProjectDirectory } ,
{ "publish:Configuration" , configuration } ,
2016-01-25 04:28:52 +00:00
{ "publish:OutputPath" , outputPath } ,
2016-01-11 21:40:47 +00:00
{ "publish:Framework" , context . TargetFramework . Framework } ,
{ "publish:Runtime" , context . RuntimeIdentifier } ,
} ;
RunScripts ( context , ScriptNames . PrePublish , contextVariables ) ;
2016-01-26 14:39:13 +00:00
if ( ! Directory . Exists ( outputPath ) )
2015-12-11 07:00:35 +00:00
{
2016-01-26 14:39:13 +00:00
Directory . CreateDirectory ( outputPath ) ;
2015-12-11 07:00:35 +00:00
}
// Compile the project (and transitively, all it's dependencies)
2016-02-03 20:04:09 +00:00
var result = Build . BuildCommand . Run (
2016-01-22 22:04:04 +00:00
new string [ ] {
2016-01-26 14:39:13 +00:00
"--framework" ,
2016-01-22 22:04:04 +00:00
$"{context.TargetFramework.DotNetFrameworkName}" ,
2016-01-29 17:21:55 +00:00
"--runtime" ,
context . RuntimeIdentifier ,
2016-01-26 14:39:13 +00:00
"--configuration" ,
2016-01-29 17:21:55 +00:00
configuration ,
context . ProjectFile . ProjectDirectory
2016-02-03 20:04:09 +00:00
} ) ;
2015-12-11 07:00:35 +00:00
2016-02-03 20:04:09 +00:00
if ( result ! = 0 )
2015-12-11 07:00:35 +00:00
{
return false ;
}
// Use a library exporter to collect publish assets
var exporter = context . CreateExporter ( configuration ) ;
foreach ( var export in exporter . GetAllExports ( ) )
{
Reporter . Verbose . WriteLine ( $"Publishing {export.Library.Identity.ToString().Green().Bold()} ..." ) ;
2016-01-26 14:39:13 +00:00
PublishFiles ( export . RuntimeAssemblies , outputPath , nativeSubdirectories : false ) ;
2015-12-21 20:23:05 +00:00
PublishFiles ( export . NativeLibraries , outputPath , nativeSubdirectories ) ;
2016-01-28 04:35:43 +00:00
PublishFiles ( export . RuntimeAssets , outputPath ) ;
2016-01-22 00:40:33 +00:00
if ( options . PreserveCompilationContext . GetValueOrDefault ( ) )
{
PublishRefs ( export , outputPath ) ;
}
2015-12-11 07:00:35 +00:00
}
2016-02-02 18:50:59 +00:00
var contentFiles = new ContentFiles ( context ) ;
contentFiles . StructuredCopyTo ( outputPath ) ;
2016-01-09 00:20:39 +00:00
2015-12-11 07:00:35 +00:00
// Publish a host if this is an application
if ( options . EmitEntryPoint . GetValueOrDefault ( ) )
{
Reporter . Verbose . WriteLine ( $"Making {context.ProjectFile.Name.Cyan()} runnable ..." ) ;
PublishHost ( context , outputPath ) ;
}
2016-01-11 21:40:47 +00:00
RunScripts ( context , ScriptNames . PostPublish , contextVariables ) ;
2015-12-11 07:00:35 +00:00
Reporter . Output . WriteLine ( $"Published to {outputPath}" . Green ( ) . Bold ( ) ) ;
2016-01-11 21:40:47 +00:00
2015-12-11 07:00:35 +00:00
return true ;
}
2016-01-22 00:40:33 +00:00
private static void PublishRefs ( LibraryExport export , string outputPath )
{
var refsPath = Path . Combine ( outputPath , "refs" ) ;
if ( ! Directory . Exists ( refsPath ) )
{
Directory . CreateDirectory ( refsPath ) ;
}
// Do not copy compilation assembly if it's in runtime assemblies
var runtimeAssemblies = new HashSet < LibraryAsset > ( export . RuntimeAssemblies ) ;
foreach ( var compilationAssembly in export . CompilationAssemblies )
{
if ( ! runtimeAssemblies . Contains ( compilationAssembly ) )
{
var destFileName = Path . Combine ( refsPath , Path . GetFileName ( compilationAssembly . ResolvedPath ) ) ;
File . Copy ( compilationAssembly . ResolvedPath , destFileName , overwrite : true ) ;
}
}
}
2015-12-11 07:00:35 +00:00
private static int PublishHost ( ProjectContext context , string outputPath )
{
if ( context . TargetFramework . IsDesktop ( ) )
{
return 0 ;
}
2016-01-10 07:33:22 +00:00
foreach ( var binaryName in Constants . HostBinaryNames )
2015-12-11 07:00:35 +00:00
{
2016-01-10 07:33:22 +00:00
var hostBinaryPath = Path . Combine ( AppContext . BaseDirectory , binaryName ) ;
if ( ! File . Exists ( hostBinaryPath ) )
{
Reporter . Error . WriteLine ( $"Cannot find {binaryName} in the dotnet directory." . Red ( ) ) ;
return 1 ;
}
2015-12-11 07:00:35 +00:00
2016-01-10 07:33:22 +00:00
var outputBinaryName = binaryName . Equals ( Constants . HostExecutableName ) ? ( context . ProjectFile . Name + Constants . ExeSuffix ) : binaryName ;
var outputBinaryPath = Path . Combine ( outputPath , outputBinaryName ) ;
2015-12-11 07:00:35 +00:00
2016-01-10 07:33:22 +00:00
File . Copy ( hostBinaryPath , outputBinaryPath , overwrite : true ) ;
}
2015-12-11 07:00:35 +00:00
return 0 ;
}
2016-01-28 04:35:43 +00:00
private static void PublishFiles ( IEnumerable < string > files , string outputPath )
{
foreach ( var file in files )
{
var targetPath = Path . Combine ( outputPath , Path . GetFileName ( file ) ) ;
File . Copy ( file , targetPath , overwrite : true ) ;
}
}
2015-12-11 07:00:35 +00:00
2015-12-21 20:23:05 +00:00
private static void PublishFiles ( IEnumerable < LibraryAsset > files , string outputPath , bool nativeSubdirectories )
2015-12-11 07:00:35 +00:00
{
foreach ( var file in files )
{
2015-12-21 20:23:05 +00:00
var destinationDirectory = DetermineFileDestinationDirectory ( file , outputPath , nativeSubdirectories ) ;
if ( ! Directory . Exists ( destinationDirectory ) )
{
Directory . CreateDirectory ( destinationDirectory ) ;
}
2016-01-26 14:39:13 +00:00
2016-01-27 21:07:26 +00:00
File . Copy ( file . ResolvedPath , Path . Combine ( destinationDirectory , Path . GetFileName ( file . ResolvedPath ) ) , overwrite : true ) ;
2015-12-21 20:23:05 +00:00
}
}
private static string DetermineFileDestinationDirectory ( LibraryAsset file , string outputPath , bool nativeSubdirectories )
{
var destinationDirectory = outputPath ;
if ( nativeSubdirectories )
{
destinationDirectory = Path . Combine ( outputPath , GetNativeRelativeSubdirectory ( file . RelativePath ) ) ;
2015-12-11 07:00:35 +00:00
}
2015-12-21 20:23:05 +00:00
return destinationDirectory ;
}
private static string GetNativeRelativeSubdirectory ( string filepath )
{
string directoryPath = Path . GetDirectoryName ( filepath ) ;
string [ ] parts = directoryPath . Split ( new string [ ] { "native" } , 2 , StringSplitOptions . None ) ;
if ( parts . Length ! = 2 )
{
throw new Exception ( "Unrecognized Native Directory Format: " + filepath ) ;
}
string candidate = parts [ 1 ] ;
candidate = candidate . TrimStart ( new char [ ] { '/' , '\\' } ) ;
return candidate ;
2015-12-11 07:00:35 +00:00
}
2015-12-19 00:39:43 +00:00
private static IEnumerable < ProjectContext > SelectContexts ( string projectPath , NuGetFramework framework , string runtime )
{
var allContexts = ProjectContext . CreateContextForEachTarget ( projectPath ) ;
if ( string . IsNullOrEmpty ( runtime ) )
{
// Nothing was specified, so figure out what the candidate runtime identifiers are and try each of them
// Temporary until #619 is resolved
foreach ( var candidate in PlatformServices . Default . Runtime . GetAllCandidateRuntimeIdentifiers ( ) )
{
var contexts = GetMatchingProjectContexts ( allContexts , framework , candidate ) ;
if ( contexts . Any ( ) )
{
return contexts ;
}
}
return Enumerable . Empty < ProjectContext > ( ) ;
}
else
{
return GetMatchingProjectContexts ( allContexts , framework , runtime ) ;
}
}
/// <summary>
/// Return the matching framework/runtime ProjectContext.
/// If 'framework' or 'runtimeIdentifier' is null or empty then it matches with any.
/// </summary>
private static IEnumerable < ProjectContext > GetMatchingProjectContexts ( IEnumerable < ProjectContext > contexts , NuGetFramework framework , string runtimeIdentifier )
{
foreach ( var context in contexts )
{
if ( context . TargetFramework = = null | | string . IsNullOrEmpty ( context . RuntimeIdentifier ) )
{
continue ;
}
if ( string . IsNullOrEmpty ( runtimeIdentifier ) | | string . Equals ( runtimeIdentifier , context . RuntimeIdentifier , StringComparison . OrdinalIgnoreCase ) )
{
if ( framework = = null | | framework . Equals ( context . TargetFramework ) )
{
yield return context ;
}
}
}
}
2016-01-09 00:20:39 +00:00
private static void CopyContents ( ProjectContext context , string outputPath )
{
var contentFiles = context . ProjectFile . Files . GetContentFiles ( ) ;
Copy ( contentFiles , context . ProjectDirectory , outputPath ) ;
}
private static void Copy ( IEnumerable < string > contentFiles , string sourceDirectory , string targetDirectory )
{
if ( contentFiles = = null )
{
throw new ArgumentNullException ( nameof ( contentFiles ) ) ;
}
sourceDirectory = PathUtility . EnsureTrailingSlash ( sourceDirectory ) ;
targetDirectory = PathUtility . EnsureTrailingSlash ( targetDirectory ) ;
foreach ( var contentFilePath in contentFiles )
{
Reporter . Verbose . WriteLine ( $"Publishing {contentFilePath.Green().Bold()} ..." ) ;
var fileName = Path . GetFileName ( contentFilePath ) ;
var targetFilePath = contentFilePath . Replace ( sourceDirectory , targetDirectory ) ;
var targetFileParentFolder = Path . GetDirectoryName ( targetFilePath ) ;
// Create directory before copying a file
if ( ! Directory . Exists ( targetFileParentFolder ) )
{
Directory . CreateDirectory ( targetFileParentFolder ) ;
}
File . Copy (
contentFilePath ,
targetFilePath ,
overwrite : true ) ;
// clear read-only bit if set
var fileAttributes = File . GetAttributes ( targetFilePath ) ;
if ( ( fileAttributes & FileAttributes . ReadOnly ) = = FileAttributes . ReadOnly )
{
File . SetAttributes ( targetFilePath , fileAttributes & ~ FileAttributes . ReadOnly ) ;
}
}
}
2016-01-11 21:40:47 +00:00
private static void RunScripts ( ProjectContext context , string name , Dictionary < string , string > contextVariables )
{
foreach ( var script in context . ProjectFile . Scripts . GetOrEmpty ( name ) )
{
ScriptExecutor . CreateCommandForScript ( context . ProjectFile , script , contextVariables )
. ForwardStdErr ( )
. ForwardStdOut ( )
. Execute ( ) ;
}
}
2015-12-11 07:00:35 +00:00
}
}