2015-10-06 10:46:43 -07:00
using System ;
2015-10-13 14:31:29 -07:00
using System.Collections.Generic ;
2015-10-06 10:46:43 -07:00
using System.IO ;
2015-10-13 14:31:29 -07:00
using System.Linq ;
2015-10-21 15:21:14 -07:00
using System.Runtime.InteropServices ;
2015-10-06 10:46:43 -07:00
using Microsoft.Dnx.Runtime.Common.CommandLine ;
using Microsoft.DotNet.Cli.Utils ;
2015-10-13 14:31:29 -07:00
using Microsoft.Extensions.ProjectModel ;
using NuGet.Frameworks ;
2015-10-06 10:46:43 -07:00
2015-10-07 14:39:36 -07:00
namespace Microsoft.DotNet.Tools.Publish
2015-10-06 10:46:43 -07:00
{
public class Program
{
2015-10-21 15:21:14 -07:00
public static readonly IEnumerable < string > CoreCLRFileNames = GetCoreCLRFileNames ( ) ;
2015-10-06 10:46:43 -07:00
public static int Main ( string [ ] args )
{
2015-10-13 14:31:29 -07:00
DebugHelper . HandleDebugSwitch ( ref args ) ;
2015-10-06 10:46:43 -07:00
var app = new CommandLineApplication ( ) ;
app . Name = "dotnet publish" ;
app . FullName = ".NET Publisher" ;
app . Description = "Publisher for the .NET Platform" ;
app . HelpOption ( "-h|--help" ) ;
var framework = app . Option ( "-f|--framework <FRAMEWORK>" , "Target framework to compile for" , CommandOptionType . SingleValue ) ;
var runtime = app . Option ( "-r|--runtime <RUNTIME_IDENTIFIER>" , "Target runtime to publish for" , CommandOptionType . SingleValue ) ;
var output = app . Option ( "-o|--output <OUTPUT_PATH>" , "Path in which to publish the app" , CommandOptionType . SingleValue ) ;
2015-10-13 14:31:29 -07:00
var configuration = app . Option ( "-c|--configuration <CONFIGURATION>" , "Configuration under which to build" , CommandOptionType . SingleValue ) ;
var project = app . Argument ( "<PROJECT>" , "The project to publish, defaults to the current directory. Can be a path to a project.json or a project directory" ) ;
2015-10-06 10:46:43 -07:00
app . OnExecute ( ( ) = >
{
2015-10-17 07:50:02 -07:00
if ( ! CheckArg ( framework ) )
2015-10-15 12:56:07 -07:00
{
return 1 ;
}
2015-10-17 07:50:02 -07:00
if ( ! CheckArg ( runtime ) )
2015-10-15 12:56:07 -07:00
{
return 1 ;
}
2015-10-06 10:46:43 -07:00
// Locate the project and get the name and full path
var path = project . Value ;
if ( string . IsNullOrEmpty ( path ) )
{
path = Directory . GetCurrentDirectory ( ) ;
}
2015-10-13 14:31:29 -07:00
// Load project context and publish it
2015-10-15 12:56:07 -07:00
var fx = NuGetFramework . Parse ( framework . Value ( ) ) ;
var rids = new [ ] { runtime . Value ( ) } ;
var context = ProjectContext . Create ( path , fx , rids ) ;
2015-10-24 03:59:39 -07:00
if ( string . IsNullOrEmpty ( context . RuntimeIdentifier ) )
{
Reporter . Output . WriteLine ( $"Unknown runtime identifier {runtime.Value()}." . Red ( ) ) ;
return 1 ;
}
2015-10-13 14:31:29 -07:00
return Publish ( context , output . Value ( ) , configuration . Value ( ) ? ? Constants . DefaultConfiguration ) ;
2015-10-06 10:46:43 -07:00
} ) ;
try
{
return app . Execute ( args ) ;
}
2015-10-18 02:06:15 -07:00
catch ( Exception ex )
2015-10-06 10:46:43 -07:00
{
2015-10-20 01:43:37 -07:00
#if DEBUG
Console . Error . WriteLine ( ex ) ;
#else
2015-10-06 10:46:43 -07:00
Console . Error . WriteLine ( ex . Message ) ;
2015-10-20 01:43:37 -07:00
#endif
2015-10-06 10:46:43 -07:00
return 1 ;
}
}
2015-10-15 12:56:07 -07:00
private static bool CheckArg ( CommandOption argument )
2015-10-06 10:46:43 -07:00
{
if ( ! argument . HasValue ( ) )
{
2015-10-15 12:56:07 -07:00
Reporter . Error . WriteLine ( $"Missing required argument: {argument.LongName.Red().Bold()}" ) ;
return false ;
2015-10-06 10:46:43 -07:00
}
2015-10-15 12:56:07 -07:00
return true ;
2015-10-06 10:46:43 -07:00
}
2015-10-13 14:31:29 -07:00
private static int Publish ( ProjectContext context , string outputPath , string configuration )
2015-10-06 10:46:43 -07:00
{
2015-10-13 14:31:29 -07:00
Reporter . Output . WriteLine ( $"Publishing {context.RootProject.Identity.Name.Yellow()} for {context.TargetFramework.DotNetFrameworkName.Yellow()}/{context.RuntimeIdentifier}" ) ;
2015-10-22 04:34:01 -07:00
var options = context . ProjectFile . GetCompilerOptions ( context . TargetFramework , configuration ) ;
if ( ! options . EmitEntryPoint . GetValueOrDefault ( ) )
{
Reporter . Output . WriteLine ( $"{context.RootProject.Identity} does not have an entry point defined." . Red ( ) ) ;
return 1 ;
}
2015-10-18 00:35:56 -07:00
// Generate the output path
2015-10-06 10:46:43 -07:00
if ( string . IsNullOrEmpty ( outputPath ) )
{
2015-10-13 14:31:29 -07:00
outputPath = Path . Combine (
2015-10-15 12:56:07 -07:00
context . ProjectFile . ProjectDirectory ,
Constants . BinDirectoryName ,
2015-10-13 14:31:29 -07:00
configuration ,
context . TargetFramework . GetTwoDigitShortFolderName ( ) ,
2015-10-24 03:59:39 -07:00
context . RuntimeIdentifier ) ;
2015-10-06 10:46:43 -07:00
}
2015-10-18 00:35:56 -07:00
2015-10-06 10:46:43 -07:00
if ( ! Directory . Exists ( outputPath ) )
{
Directory . CreateDirectory ( outputPath ) ;
}
2015-10-13 14:31:29 -07:00
// Compile the project (and transitively, all it's dependencies)
2015-10-21 15:21:14 -07:00
var result = Command . Create ( "dotnet-compile" , $"--framework \" { context . TargetFramework . DotNetFrameworkName } \ " --configuration \"{configuration}\" \"{context.ProjectFile.ProjectDirectory}\"" )
2015-10-06 10:46:43 -07:00
. ForwardStdErr ( )
. ForwardStdOut ( )
2015-10-21 03:11:27 -07:00
. Execute ( ) ;
2015-10-18 00:35:56 -07:00
2015-10-06 10:46:43 -07:00
if ( result . ExitCode ! = 0 )
{
return result . ExitCode ;
}
2015-10-13 14:31:29 -07:00
// Use a library exporter to collect publish assets
var exporter = context . CreateExporter ( configuration ) ;
2015-10-18 00:35:56 -07:00
2015-10-20 04:35:40 -07:00
// Copy things marked as copy to output (which we don't have yet)
// so does copy too many things
CopyContents ( context , outputPath ) ;
2015-10-13 14:31:29 -07:00
foreach ( var export in exporter . GetAllExports ( ) )
2015-10-06 10:46:43 -07:00
{
2015-10-13 14:31:29 -07:00
Reporter . Output . WriteLine ( $"Publishing {export.Library.Identity.ToString().Green().Bold()} ..." ) ;
2015-10-06 10:46:43 -07:00
2015-10-13 14:31:29 -07:00
PublishFiles ( export . RuntimeAssemblies , outputPath ) ;
PublishFiles ( export . NativeLibraries , outputPath ) ;
2015-10-06 10:46:43 -07:00
}
2015-10-16 15:30:28 -07:00
int exitCode ;
2015-10-20 04:35:40 -07:00
if ( context . RuntimeIdentifier . StartsWith ( "win" ) )
2015-10-16 15:30:28 -07:00
{
exitCode = PublishForWindows ( context , outputPath ) ;
}
else
{
exitCode = PublishForUnix ( context , outputPath ) ;
}
Reporter . Output . WriteLine ( $"Published to {outputPath}" . Green ( ) . Bold ( ) ) ;
return exitCode ;
}
2015-10-13 14:31:29 -07:00
2015-10-16 15:30:28 -07:00
private static int PublishForUnix ( ProjectContext context , string outputPath )
{
2015-10-21 15:21:14 -07:00
CopyCoreCLR ( outputPath ) ;
var coreConsole = Path . Combine ( outputPath , Constants . CoreConsoleName ) ;
var coreRun = Path . Combine ( outputPath , Constants . CoreRunName ) ;
2015-10-16 11:11:07 -07:00
2015-10-13 14:31:29 -07:00
// Use the 'command' field to generate the name
2015-10-16 15:30:28 -07:00
var outputExe = Path . Combine ( outputPath , context . ProjectFile . Name ) ;
2015-10-18 00:35:56 -07:00
// Write a script that can be used to launch with CoreRun
var script = $ @ "#!/usr/bin/env bash
2015-10-16 15:30:28 -07:00
SOURCE = "" $ { { BASH_SOURCE [ 0 ] } } ""
while [ - h "" $ SOURCE "" ] ; do # resolve $ SOURCE until the file is no longer a symlink
DIR = "" $ ( cd - P "" $ ( dirname "" $ SOURCE "" ) "" & & pwd ) ""
SOURCE = "" $ ( readlink "" $ SOURCE "" ) ""
[[ $SOURCE != /* ] ] & & SOURCE = "" $ DIR / $ SOURCE "" # if $ SOURCE was a relative symlink , we need to resolve it relative to the path where the symlink file was located
done
DIR = "" $ ( cd - P "" $ ( dirname "" $ SOURCE "" ) "" & & pwd ) ""
2015-10-21 00:35:28 -07:00
exec "" $ DIR / corerun "" "" $ DIR / { context . ProjectFile . Name } . exe "" $ *
";
2015-10-18 00:35:56 -07:00
File . WriteAllText ( outputExe , script ) ;
Command . Create ( "chmod" , $"a+x {outputExe}" )
. ForwardStdOut ( )
. ForwardStdErr ( )
2015-10-21 03:11:27 -07:00
. Execute ( ) ;
2015-10-18 00:35:56 -07:00
2015-10-16 15:30:28 -07:00
return 0 ;
}
private static int PublishForWindows ( ProjectContext context , string outputPath )
{
2015-10-20 04:35:40 -07:00
if ( context . TargetFramework . IsDesktop ( ) )
{
return 0 ;
}
2015-10-16 15:30:28 -07:00
2015-10-21 15:21:14 -07:00
CopyCoreCLR ( outputPath ) ;
var coreConsole = Path . Combine ( outputPath , Constants . CoreConsoleName ) ;
var coreRun = Path . Combine ( outputPath , Constants . CoreRunName ) ;
2015-10-16 15:30:28 -07:00
var outputExe = Path . Combine ( outputPath , context . ProjectFile . Name + Constants . ExeSuffix ) ;
2015-10-13 14:31:29 -07:00
2015-10-20 04:35:40 -07:00
// Rename the {app}.exe to {app}.dll
File . Copy ( outputExe , Path . ChangeExtension ( outputExe , ".dll" ) , overwrite : true ) ;
// Change coreconsole.exe to the {app}.exe name
File . Copy ( coreConsole , outputExe , overwrite : true ) ;
2015-10-06 10:46:43 -07:00
return 0 ;
}
2015-10-13 14:31:29 -07:00
2015-10-21 15:21:14 -07:00
private static void CopyCoreCLR ( string outputPath )
{
// TEMPORARILY bring checked-in CoreCLR stuff along for the ride.
var clrPath = AppContext . BaseDirectory ;
foreach ( var file in CoreCLRFileNames )
{
File . Copy ( Path . Combine ( clrPath , file ) , Path . Combine ( outputPath , file ) , overwrite : true ) ;
}
}
2015-10-16 17:41:32 -07:00
private static void CopyContents ( ProjectContext context , string outputPath )
{
var sourceFiles = context . ProjectFile . Files . GetFilesForBundling ( ) ;
Copy ( sourceFiles , context . ProjectDirectory , outputPath ) ;
}
private static void Copy ( IEnumerable < string > sourceFiles , string sourceDirectory , string targetDirectory )
{
if ( sourceFiles = = null )
{
throw new ArgumentNullException ( nameof ( sourceFiles ) ) ;
}
sourceDirectory = EnsureTrailingSlash ( sourceDirectory ) ;
targetDirectory = EnsureTrailingSlash ( targetDirectory ) ;
foreach ( var sourceFilePath in sourceFiles )
{
var fileName = Path . GetFileName ( sourceFilePath ) ;
var targetFilePath = sourceFilePath . Replace ( sourceDirectory , targetDirectory ) ;
var targetFileParentFolder = Path . GetDirectoryName ( targetFilePath ) ;
// Create directory before copying a file
if ( ! Directory . Exists ( targetFileParentFolder ) )
{
Directory . CreateDirectory ( targetFileParentFolder ) ;
}
File . Copy (
sourceFilePath ,
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 ) ;
}
}
}
2015-10-20 04:35:40 -07:00
2015-10-16 17:41:32 -07:00
private static string EnsureTrailingSlash ( string path )
{
return EnsureTrailingCharacter ( path , Path . DirectorySeparatorChar ) ;
}
private static string EnsureTrailingCharacter ( string path , char trailingCharacter )
{
if ( path = = null )
{
throw new ArgumentNullException ( nameof ( path ) ) ;
}
// if the path is empty, we want to return the original string instead of a single trailing character.
if ( path . Length = = 0 | | path [ path . Length - 1 ] = = trailingCharacter )
{
return path ;
}
return path + trailingCharacter ;
}
2015-10-13 14:31:29 -07:00
private static void PublishFiles ( IEnumerable < string > files , string outputPath )
{
foreach ( var file in files )
{
File . Copy ( file , Path . Combine ( outputPath , Path . GetFileName ( file ) ) , overwrite : true ) ;
}
}
2015-10-21 15:21:14 -07:00
private static IEnumerable < string > GetCoreCLRFileNames ( )
{
if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
{
yield return "coreclr.dll" ;
yield return "CoreConsole.exe" ;
yield return "CoreRun.exe" ;
yield return "mscorlib.ni.dll" ;
}
else if ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) )
{
yield return "libcoreclr.dylib" ;
yield return "coreconsole" ;
yield return "corerun" ;
yield return "mscorlib.dll" ;
yield return "System.Globalization.Native.dylib" ;
}
else
{
yield return "libcoreclr.so" ;
yield return "coreconsole" ;
yield return "corerun" ;
yield return "mscorlib.dll" ;
yield return "System.Globalization.Native.so" ;
}
}
2015-10-06 10:46:43 -07:00
}
}