diff --git a/TestAssets/TestProjects/BuildTestPortableProject/Lib.cs b/TestAssets/TestProjects/BuildTestPortableProject/Lib.cs new file mode 100644 index 000000000..2ac94ca9d --- /dev/null +++ b/TestAssets/TestProjects/BuildTestPortableProject/Lib.cs @@ -0,0 +1,3 @@ +public static class Thingy +{ +} diff --git a/TestAssets/TestProjects/BuildTestPortableProject/project.json b/TestAssets/TestProjects/BuildTestPortableProject/project.json new file mode 100644 index 000000000..43c20c90b --- /dev/null +++ b/TestAssets/TestProjects/BuildTestPortableProject/project.json @@ -0,0 +1,16 @@ +{ + "dependencies": { + }, + "frameworks": { + "net45": {}, + "netstandardapp1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ], + "dependencies": { + "NETStandard.Library": "1.0.0-rc2-23901" + } + } + } +} diff --git a/TestAssets/TestProjects/BuildTestStandaloneProject/Lib.cs b/TestAssets/TestProjects/BuildTestStandaloneProject/Lib.cs new file mode 100644 index 000000000..2ac94ca9d --- /dev/null +++ b/TestAssets/TestProjects/BuildTestStandaloneProject/Lib.cs @@ -0,0 +1,3 @@ +public static class Thingy +{ +} diff --git a/TestAssets/TestProjects/BuildTestStandaloneProject/project.json b/TestAssets/TestProjects/BuildTestStandaloneProject/project.json new file mode 100644 index 000000000..d8b5d9d86 --- /dev/null +++ b/TestAssets/TestProjects/BuildTestStandaloneProject/project.json @@ -0,0 +1,20 @@ +{ + "dependencies": { }, + "frameworks": { + "netstandardapp1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ], + "dependencies": { + "NETStandard.Library": "1.0.0-rc2-23901" + } + } + }, + "runtimes": { + "win7-x64": {}, + "osx.10.10-x64": {}, + "ubuntu.14.04-x64": {}, + "centos.7-x64": {} + } +} diff --git a/scripts/dev-dotnet.ps1 b/scripts/dev-dotnet.ps1 index 8b5f1290f..4728a8b7c 100644 --- a/scripts/dev-dotnet.ps1 +++ b/scripts/dev-dotnet.ps1 @@ -6,11 +6,16 @@ $oldPath = $env:PATH try { # Put the stage2 output on the front of the path - $stage2 = "$PSScriptRoot\..\artifacts\win7-x64\stage2\bin" + if(!(Get-Command dotnet -ErrorAction SilentlyContinue)) { + throw "You need to have a version of 'dotnet' on your path so we can determine the RID" + } + + $rid = dotnet --version | where { $_ -match "^ Runtime Id:\s*(.*)$" } | foreach { $matches[1] } + $stage2 = "$PSScriptRoot\..\artifacts\$rid\stage2\bin" if (Test-Path $stage2) { $env:PATH="$stage2;$env:PATH" } else { - Write-Host "You don't have a dev build in the 'artifacts\win7-x64\stage2' folder!" + Write-Host "You don't have a dev build in the 'artifacts\$rid\stage2' folder!" } dotnet @args diff --git a/scripts/dotnet-cli-build/CompileTargets.cs b/scripts/dotnet-cli-build/CompileTargets.cs index b84fa6452..bbf03cf4f 100644 --- a/scripts/dotnet-cli-build/CompileTargets.cs +++ b/scripts/dotnet-cli-build/CompileTargets.cs @@ -45,6 +45,13 @@ namespace Microsoft.DotNet.Cli.Build "Microsoft.Extensions.Testing.Abstractions" }; + // Updates the stage 2 with recent changes. + [Target(nameof(PrepareTargets.Init), nameof(CompileStage2))] + public static BuildTargetResult UpdateBuild(BuildTargetContext c) + { + return c.Success(); + } + [Target(nameof(PrepareTargets.Init), nameof(CompileCoreHost), nameof(CompileStage1), nameof(CompileStage2))] public static BuildTargetResult Compile(BuildTargetContext c) { diff --git a/src/Microsoft.DotNet.Cli.Utils/project.json b/src/Microsoft.DotNet.Cli.Utils/project.json index 877df020f..625b48bf4 100644 --- a/src/Microsoft.DotNet.Cli.Utils/project.json +++ b/src/Microsoft.DotNet.Cli.Utils/project.json @@ -19,8 +19,9 @@ "netstandard1.3": { "imports": "dnxcore50", "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" + "NETStandard.Library": "1.0.0-rc2-23901", + "System.Diagnostics.Process": "4.1.0-rc2-23901" } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.DotNet.Compiler.Common/Executable.cs b/src/Microsoft.DotNet.Compiler.Common/Executable.cs index 31f92825f..3aedeedc8 100644 --- a/src/Microsoft.DotNet.Compiler.Common/Executable.cs +++ b/src/Microsoft.DotNet.Compiler.Common/Executable.cs @@ -39,11 +39,6 @@ namespace Microsoft.Dotnet.Cli.Compiler.Common public void MakeCompilationOutputRunnable() { - if (string.IsNullOrEmpty(_context.RuntimeIdentifier)) - { - throw new InvalidOperationException($"Can not make output runnable for framework {_context.TargetFramework}, because it doesn't have runtime target"); - } - CopyContentFiles(); ExportRuntimeAssets(); } @@ -72,8 +67,11 @@ namespace Microsoft.Dotnet.Cli.Compiler.Common { WriteDepsFileAndCopyProjectDependencies(_exporter); - // TODO: Pick a host based on the RID - CoreHost.CopyTo(_runtimeOutputPath, _context.ProjectFile.Name + Constants.ExeSuffix); + if (!string.IsNullOrEmpty(_context.RuntimeIdentifier)) + { + // TODO: Pick a host based on the RID + CoreHost.CopyTo(_runtimeOutputPath, _context.ProjectFile.Name + Constants.ExeSuffix); + } } private void CopyContentFiles() diff --git a/src/Microsoft.DotNet.ProjectModel/OutputPathsCalculator.cs b/src/Microsoft.DotNet.ProjectModel/OutputPathsCalculator.cs index aa9c9fb87..fdaa7c5e3 100644 --- a/src/Microsoft.DotNet.ProjectModel/OutputPathsCalculator.cs +++ b/src/Microsoft.DotNet.ProjectModel/OutputPathsCalculator.cs @@ -48,12 +48,18 @@ namespace Microsoft.DotNet.ProjectModel { if (!string.IsNullOrEmpty(runtimeIdentifier)) { - runtimeOutputPath= PathUtility.EnsureTrailingSlash(Path.Combine(compilationOutputPath, runtimeIdentifier)); + runtimeOutputPath = PathUtility.EnsureTrailingSlash(Path.Combine(compilationOutputPath, runtimeIdentifier)); + } + else + { + // "Runtime" assets (i.e. the deps file) will be dropped to the compilation output path, because + // we are building a RID-less target. + runtimeOutputPath = compilationOutputPath; } } else { - runtimeOutputPath= PathUtility.EnsureTrailingSlash(Path.GetFullPath(outputPath)); + runtimeOutputPath = PathUtility.EnsureTrailingSlash(Path.GetFullPath(outputPath)); } var intermediateOutputPath = PathUtility.EnsureTrailingSlash(Path.Combine( diff --git a/src/Microsoft.DotNet.ProjectModel/Project.cs b/src/Microsoft.DotNet.ProjectModel/Project.cs index c4a8c4a41..5e187f55b 100644 --- a/src/Microsoft.DotNet.ProjectModel/Project.cs +++ b/src/Microsoft.DotNet.ProjectModel/Project.cs @@ -35,7 +35,7 @@ namespace Microsoft.DotNet.ProjectModel return Path.GetDirectoryName(ProjectFilePath); } } - + public AnalyzerOptions AnalyzerOptions { get; set; } public string Name { get; set; } @@ -124,12 +124,12 @@ namespace Microsoft.DotNet.ProjectModel public bool HasRuntimeOutput(string configuration) { - var compilationOptions = GetCompilerOptions(targetFramework: null, configurationName: configuration); // TODO: Make this opt in via another mechanism return compilationOptions.EmitEntryPoint.GetValueOrDefault() || IsTestProject; } + private CommonCompilerOptions GetCompilerOptions() { return _defaultCompilerOptions; diff --git a/src/Microsoft.DotNet.ProjectModel/ProjectContext.cs b/src/Microsoft.DotNet.ProjectModel/ProjectContext.cs index 9e237ff11..99f869f36 100644 --- a/src/Microsoft.DotNet.ProjectModel/ProjectContext.cs +++ b/src/Microsoft.DotNet.ProjectModel/ProjectContext.cs @@ -83,7 +83,7 @@ namespace Microsoft.DotNet.ProjectModel .WithRuntimeIdentifiers(runtimeIdentifiers) .Build(); } - + public static ProjectContextBuilder CreateBuilder(string projectPath, NuGetFramework framework) { if (projectPath.EndsWith(Project.FileName)) @@ -120,15 +120,12 @@ namespace Microsoft.DotNet.ProjectModel /// /// Creates a project context for each target located in the project at /// - public static IEnumerable CreateContextForEachTarget(string projectPath) + public static IEnumerable CreateContextForEachTarget(string projectPath, ProjectReaderSettings settings = null) { - if (!projectPath.EndsWith(Project.FileName)) - { - projectPath = Path.Combine(projectPath, Project.FileName); - } var project = ProjectReader.GetProject(projectPath); return new ProjectContextBuilder() + .WithReaderSettings(settings) .WithProject(project) .BuildAllTargets(); } @@ -143,15 +140,20 @@ namespace Microsoft.DotNet.ProjectModel public ProjectContext CreateRuntimeContext(IEnumerable runtimeIdentifiers) { - var context = Create(ProjectFile.ProjectFilePath, TargetFramework, runtimeIdentifiers); - if (context.RuntimeIdentifier == null) + // Check if there are any runtime targets (i.e. are we portable) + var standalone = LockFile.Targets + .Where(t => t.TargetFramework.Equals(TargetFramework)) + .Any(t => !string.IsNullOrEmpty(t.RuntimeIdentifier)); + + var context = Create(ProjectFile.ProjectFilePath, TargetFramework, standalone ? runtimeIdentifiers : Enumerable.Empty()); + if (standalone && context.RuntimeIdentifier == null) { + // We are standalone, but don't support this runtime var rids = string.Join(", ", runtimeIdentifiers); - throw new InvalidOperationException($"Can not find runtime target for framework '{TargetFramework}' and RID's '{rids}'. " + + throw new InvalidOperationException($"Can not find runtime target for framework '{TargetFramework}' compatible with one of the target runtimes: '{rids}'. " + "Possible causes:" + Environment.NewLine + - "1. Project is not restored or restore failed - run `dotnet restore`" + Environment.NewLine + - "2. Project is not targeting `runable` framework (`netstandardapp*` or `net*`)" - ); + "1. The project has not been restored or restore failed - run `dotnet restore`" + Environment.NewLine + + $"2. The project does not list one of '{rids}' in the 'runtimes' section."); } return context; } diff --git a/src/Microsoft.DotNet.ProjectModel/ProjectReader.cs b/src/Microsoft.DotNet.ProjectModel/ProjectReader.cs index 2a58b3bd5..39f28ba12 100644 --- a/src/Microsoft.DotNet.ProjectModel/ProjectReader.cs +++ b/src/Microsoft.DotNet.ProjectModel/ProjectReader.cs @@ -61,18 +61,20 @@ namespace Microsoft.DotNet.ProjectModel return true; } - public static Project GetProject(string projectFile, ProjectReaderSettings settings = null) - { - return GetProject(projectFile, new List(), settings); - } + public static Project GetProject(string projectPath, ProjectReaderSettings settings = null) => GetProject(projectPath, new List(), settings); - public static Project GetProject(string projectFile, ICollection diagnostics, ProjectReaderSettings settings = null) + public static Project GetProject(string projectPath, ICollection diagnostics, ProjectReaderSettings settings = null) { - var name = Path.GetFileName(Path.GetDirectoryName(projectFile)); - - using (var stream = new FileStream(projectFile, FileMode.Open, FileAccess.Read, FileShare.Read)) + if (!projectPath.EndsWith(Project.FileName)) { - return new ProjectReader().ReadProject(stream, name, projectFile, diagnostics, settings); + projectPath = Path.Combine(projectPath, Project.FileName); + } + + var name = Path.GetFileName(Path.GetDirectoryName(projectPath)); + + using (var stream = new FileStream(projectPath, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + return new ProjectReader().ReadProject(stream, name, projectPath, diagnostics, settings); } } @@ -546,7 +548,8 @@ namespace Microsoft.DotNet.ProjectModel analyzerOptions.LanguageId = languageId; break; - default:; + default: + ; throw FileFormatException.Create( $"Unrecognized analyzerOption key: {key}", project.ProjectFilePath); diff --git a/src/dotnet/commands/dotnet-build/CompileContext.cs b/src/dotnet/commands/dotnet-build/CompileContext.cs index 7e0e43957..de2b43475 100644 --- a/src/dotnet/commands/dotnet-build/CompileContext.cs +++ b/src/dotnet/commands/dotnet-build/CompileContext.cs @@ -48,7 +48,7 @@ namespace Microsoft.DotNet.Tools.Build { CreateOutputDirectories(); - return CompileDendencies(incremental) && CompileRootProject(incremental); + return CompileDependencies(incremental) && CompileRootProject(incremental); } private bool CompileRootProject(bool incremental) @@ -66,7 +66,7 @@ namespace Microsoft.DotNet.Tools.Build return success; } - private bool CompileDendencies(bool incremental) + private bool CompileDependencies(bool incremental) { if (_args.ShouldSkipDependencies) { @@ -395,15 +395,7 @@ namespace Microsoft.DotNet.Tools.Build if (succeeded) { - if (_rootProject.ProjectFile.HasRuntimeOutput(_args.ConfigValue)) - { - MakeRunnable(); - } - else if (!string.IsNullOrEmpty(_args.OutputValue)) - { - var outputPaths = _rootProject.GetOutputPaths(_args.ConfigValue, _args.BuildBasePathValue, _args.OutputValue); - CopyCompilationOutput(outputPaths); - } + MakeRunnable(); } return succeeded; @@ -428,9 +420,22 @@ namespace Microsoft.DotNet.Tools.Build private void MakeRunnable() { var runtimeContext = _rootProject.CreateRuntimeContext(_args.GetRuntimes()); + if(_args.PortableMode) + { + // HACK: Force the use of the portable target + runtimeContext = _rootProject; + } + var outputPaths = runtimeContext.GetOutputPaths(_args.ConfigValue, _args.BuildBasePathValue, _args.OutputValue); var libraryExporter = runtimeContext.CreateExporter(_args.ConfigValue, _args.BuildBasePathValue); - CopyCompilationOutput(outputPaths); + + // If we're building for a specific RID, we need to copy the RID-less compilation output into + // the RID-specific output dir + if (!string.IsNullOrEmpty(runtimeContext.RuntimeIdentifier)) + { + CopyCompilationOutput(outputPaths); + } + var executable = new Executable(runtimeContext, outputPaths, libraryExporter); executable.MakeCompilationOutputRunnable(); diff --git a/src/dotnet/commands/dotnet-compile/CompilerCommandApp.cs b/src/dotnet/commands/dotnet-compile/CompilerCommandApp.cs index 2c7fa2a0f..9a66f1777 100644 --- a/src/dotnet/commands/dotnet-compile/CompilerCommandApp.cs +++ b/src/dotnet/commands/dotnet-compile/CompilerCommandApp.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using Microsoft.Dnx.Runtime.Common.CommandLine; @@ -28,6 +29,7 @@ namespace Microsoft.DotNet.Tools.Compiler private CommandOption _runtimeOption; private CommandOption _versionSuffixOption; private CommandOption _configurationOption; + private CommandOption _portableOption; private CommandArgument _projectArgument; private CommandOption _nativeOption; private CommandOption _archOption; @@ -41,8 +43,8 @@ namespace Microsoft.DotNet.Tools.Compiler // resolved values for the options and arguments public string ProjectPathValue { get; set; } public string BuildBasePathValue { get; set; } - public string OutputValue { get; set; } public string RuntimeValue { get; set; } + public string OutputValue { get; set; } public string VersionSuffixValue { get; set; } public string ConfigValue { get; set; } public bool IsNativeValue { get; set; } @@ -53,6 +55,7 @@ namespace Microsoft.DotNet.Tools.Compiler public bool IsCppModeValue { get; set; } public string AppDepSdkPathValue { get; set; } public string CppCompilerFlagsValue { get; set; } + public bool PortableMode { get; set; } // workaround: CommandLineApplication is internal therefore I cannot make _app protected so baseclasses can add their own params private readonly Dictionary baseClassOptions; @@ -77,12 +80,15 @@ namespace Microsoft.DotNet.Tools.Compiler _outputOption = _app.Option("-o|--output ", "Directory in which to place outputs", CommandOptionType.SingleValue); _buildBasePath = _app.Option("-b|--build-base-path ", "Directory in which to place temporary outputs", CommandOptionType.SingleValue); - _frameworkOption = _app.Option("-f|--framework ", "Compile a specific framework", CommandOptionType.MultipleValue); + _frameworkOption = _app.Option("-f|--framework ", "Compile a specific framework", CommandOptionType.SingleValue); + _runtimeOption = _app.Option("-r|--runtime ", "Produce runtime-specific assets for the specified runtime", CommandOptionType.SingleValue); _configurationOption = _app.Option("-c|--configuration ", "Configuration under which to build", CommandOptionType.SingleValue); - _runtimeOption = _app.Option("-r|--runtime ", "Target runtime to publish for", CommandOptionType.SingleValue); _versionSuffixOption = _app.Option("--version-suffix ", "Defines what `*` should be replaced with in version field in project.json", CommandOptionType.SingleValue); _projectArgument = _app.Argument("", "The project to compile, defaults to the current directory. Can be a path to a project.json or a project directory"); + // HACK: Allow us to treat a project as though it was portable by ignoring the runtime-specific targets. This is temporary until RID inference is removed from NuGet + _portableOption = _app.Option("--portable", "TEMPORARY: Enforces portable build/publish mode", CommandOptionType.NoValue); + // Native Args _nativeOption = _app.Option("-n|--native", "Compiles source to native machine code.", CommandOptionType.NoValue); _archOption = _app.Option("-a|--arch ", "The architecture for which to compile. x64 only currently supported.", CommandOptionType.SingleValue); @@ -98,6 +104,12 @@ namespace Microsoft.DotNet.Tools.Compiler { _app.OnExecute(() => { + if (_outputOption.HasValue() && !_frameworkOption.HasValue()) + { + Reporter.Error.WriteLine("When the '--output' option is provided, the '--framework' option must also be provided."); + return 1; + } + // Locate the project and get the name and full path ProjectPathValue = _projectArgument.Value; if (string.IsNullOrEmpty(ProjectPathValue)) @@ -110,6 +122,7 @@ namespace Microsoft.DotNet.Tools.Compiler ConfigValue = _configurationOption.Value() ?? Constants.DefaultConfiguration; RuntimeValue = _runtimeOption.Value(); VersionSuffixValue = _versionSuffixOption.Value(); + PortableMode = _portableOption.HasValue(); IsNativeValue = _nativeOption.HasValue(); ArchValue = _archOption.Value(); @@ -120,8 +133,6 @@ namespace Microsoft.DotNet.Tools.Compiler IsCppModeValue = _cppModeOption.HasValue(); CppCompilerFlagsValue = _cppCompilerFlagsOption.Value(); - IEnumerable contexts; - // Set defaults based on the environment var settings = ProjectReaderSettings.ReadFromEnvironment(); @@ -130,29 +141,35 @@ namespace Microsoft.DotNet.Tools.Compiler settings.VersionSuffix = VersionSuffixValue; } - if (_frameworkOption.HasValue()) + // Load the project file and construct all the targets + var targets = ProjectContext.CreateContextForEachFramework(ProjectPathValue, settings).ToList(); + + if (targets.Count == 0) { - contexts = _frameworkOption.Values - .Select(f => - { - return ProjectContext.CreateBuilder(ProjectPathValue, NuGetFramework.Parse(f)) - .WithReaderSettings(settings) - .Build(); - }); - } - else - { - if (!string.IsNullOrEmpty(OutputValue)) - { - throw new InvalidOperationException($"'{_frameworkOption.LongName}' is required when '{_outputOption.LongName}' is specified"); - } - else - { - contexts = ProjectContext.CreateContextForEachFramework(ProjectPathValue, settings); - } + // Project is missing 'frameworks' section + Reporter.Error.WriteLine("Project does not have any frameworks listed in the 'frameworks' section."); + return 1; } - var success = execute(contexts.ToList(), this); + // Filter the targets down based on the inputs + if (_frameworkOption.HasValue()) + { + var fx = NuGetFramework.Parse(_frameworkOption.Value()); + targets = targets.Where(t => fx.Equals(t.TargetFramework)).ToList(); + + if (targets.Count == 0) + { + // We filtered everything out + Reporter.Error.WriteLine($"Project does not support framework: {fx.DotNetFrameworkName}."); + return 1; + } + + Debug.Assert(targets.Count == 1); + } + + Debug.Assert(targets.All(t => string.IsNullOrEmpty(t.RuntimeIdentifier))); + + var success = execute(targets, this); return success ? 0 : 1; }); diff --git a/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs b/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs index aeb692979..d2b149b31 100644 --- a/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs +++ b/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs @@ -9,7 +9,6 @@ using Microsoft.DotNet.Cli.Compiler.Common; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.ProjectModel; using Microsoft.DotNet.ProjectModel.Compilation; -using Microsoft.DotNet.ProjectModel.Utilities; using Microsoft.Extensions.DependencyModel; namespace Microsoft.DotNet.Tools.Compiler @@ -161,6 +160,10 @@ namespace Microsoft.DotNet.Tools.Compiler contextVariables.Add( "compile:RuntimeOutputDir", runtimeOutputPath.RuntimeOutputPath.TrimEnd('\\', '/')); + + contextVariables.Add( + "compile:RuntimeIdentifier", + runtimeContext.RuntimeIdentifier); } _scriptRunner.RunScripts(context, ScriptNames.PreCompile, contextVariables); diff --git a/test/ArgumentForwardingTests/project.json b/test/ArgumentForwardingTests/project.json index 7175750b5..8b99a663f 100644 --- a/test/ArgumentForwardingTests/project.json +++ b/test/ArgumentForwardingTests/project.json @@ -27,5 +27,5 @@ "testRunner": "xunit", - "scripts": { "precompile": "dotnet build ../ArgumentsReflector/project.json --framework netstandardapp1.5 --output %compile:RuntimeOutputDir%" } + "scripts": { "precompile": "dotnet build ../ArgumentsReflector/project.json --framework netstandardapp1.5 --runtime %compile:RuntimeIdentifier% --output %compile:RuntimeOutputDir%" } } diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/CommandResultAssertions.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/CommandResultAssertions.cs index 00516044b..3fab175b5 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/CommandResultAssertions.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/CommandResultAssertions.cs @@ -60,7 +60,14 @@ namespace Microsoft.DotNet.Tools.Test.Utilities return new AndConstraint(this); } - public AndConstraint StdOutMatchPattern(string pattern, RegexOptions options = RegexOptions.None) + public AndConstraint HaveStdOutContaining(string pattern) + { + Execute.Assertion.ForCondition(_commandResult.StdOut.Contains(pattern)) + .FailWith(AppendDiagnosticsTo($"The command output did not contain expected result: {pattern}{Environment.NewLine}")); + return new AndConstraint(this); + } + + public AndConstraint HaveStdOutMatching(string pattern, RegexOptions options = RegexOptions.None) { Execute.Assertion.ForCondition(Regex.Match(_commandResult.StdOut, pattern, options).Success) .FailWith(AppendDiagnosticsTo($"Matching the command output failed. Pattern: {pattern}{Environment.NewLine}")); @@ -74,6 +81,20 @@ namespace Microsoft.DotNet.Tools.Test.Utilities return new AndConstraint(this); } + public AndConstraint HaveStdErrContaining(string pattern) + { + Execute.Assertion.ForCondition(_commandResult.StdErr.Contains(pattern)) + .FailWith(AppendDiagnosticsTo($"The command error output did not contain expected result: {pattern}{Environment.NewLine}")); + return new AndConstraint(this); + } + + public AndConstraint HaveStdErrMatching(string pattern, RegexOptions options = RegexOptions.None) + { + Execute.Assertion.ForCondition(Regex.Match(_commandResult.StdErr, pattern, options).Success) + .FailWith(AppendDiagnosticsTo($"Matching the command error output failed. Pattern: {pattern}{Environment.NewLine}")); + return new AndConstraint(this); + } + public AndConstraint NotHaveStdOut() { Execute.Assertion.ForCondition(string.IsNullOrEmpty(_commandResult.StdOut)) diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/BuildCommand.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/BuildCommand.cs index 93102cf12..da059a1fe 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/BuildCommand.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/BuildCommand.cs @@ -11,23 +11,25 @@ namespace Microsoft.DotNet.Tools.Test.Utilities public sealed class BuildCommand : TestCommand { private Project _project; - private string _projectPath; - private string _outputDirectory; - private string _buidBasePathDirectory; - private string _configuration; - private string _framework; - private string _versionSuffix; - private bool _noHost; - private bool _native; - private string _architecture; - private string _ilcArgs; - private string _ilcPath; - private string _appDepSDKPath; - private bool _nativeCppMode; - private string _cppCompilerFlags; - private bool _buildProfile; - private bool _noIncremental; - private bool _noDependencies; + private readonly string _projectPath; + private readonly string _outputDirectory; + private readonly string _buidBasePathDirectory; + private readonly string _configuration; + private readonly string _framework; + private readonly string _versionSuffix; + private readonly bool _noHost; + private readonly bool _native; + private readonly string _architecture; + private readonly string _ilcArgs; + private readonly string _ilcPath; + private readonly string _appDepSDKPath; + private readonly bool _nativeCppMode; + private readonly string _cppCompilerFlags; + private readonly bool _buildProfile; + private readonly bool _noIncremental; + private readonly bool _noDependencies; + private readonly string _runtime; + private readonly bool _forcePortable; private string OutputOption { @@ -39,6 +41,16 @@ namespace Microsoft.DotNet.Tools.Test.Utilities } } + private string ForcePortableOption + { + get + { + return _forcePortable ? + "--portable" : + string.Empty; + } + } + private string BuildBasePathOption { get @@ -67,7 +79,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities $"--framework {_framework}"; } } - + private string VersionSuffixOption { get @@ -98,6 +110,16 @@ namespace Microsoft.DotNet.Tools.Test.Utilities } } + private string RuntimeOption + { + get + { + return _runtime == string.Empty ? + "" : + $"--runtime {_runtime}"; + } + } + private string ArchitectureOption { get @@ -194,6 +216,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities string buidBasePath="", string configuration="", string framework="", + string runtime="", string versionSuffix="", bool noHost=false, bool native=false, @@ -205,7 +228,8 @@ namespace Microsoft.DotNet.Tools.Test.Utilities string cppCompilerFlags="", bool buildProfile=true, bool noIncremental=false, - bool noDependencies=false + bool noDependencies=false, + bool forcePortable=false ) : base("dotnet") { @@ -217,6 +241,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities _configuration = configuration; _versionSuffix = versionSuffix; _framework = framework; + _runtime = runtime; _noHost = noHost; _native = native; _architecture = architecture; @@ -228,6 +253,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities _buildProfile = buildProfile; _noIncremental = noIncremental; _noDependencies = noDependencies; + _forcePortable = forcePortable; } public override CommandResult Execute(string args = "") @@ -251,7 +277,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities private string BuildArgs() { - return $"{BuildProfile} {NoDependencies} {NoIncremental} \"{_projectPath}\" {OutputOption} {BuildBasePathOption} {ConfigurationOption} {FrameworkOption} {VersionSuffixOption} {NoHostOption} {NativeOption} {ArchitectureOption} {IlcArgsOption} {IlcPathOption} {AppDepSDKPathOption} {NativeCppModeOption} {CppCompilerFlagsOption}"; + return $"{BuildProfile} {ForcePortableOption} {NoDependencies} {NoIncremental} \"{_projectPath}\" {OutputOption} {BuildBasePathOption} {ConfigurationOption} {FrameworkOption} {RuntimeOption} {VersionSuffixOption} {NoHostOption} {NativeOption} {ArchitectureOption} {IlcArgsOption} {IlcPathOption} {AppDepSDKPathOption} {NativeCppModeOption} {CppCompilerFlagsOption}"; } } } diff --git a/test/dotnet-build.Tests/BuildInvalidArgumentsTests.cs b/test/dotnet-build.Tests/BuildInvalidArgumentsTests.cs new file mode 100644 index 000000000..15a38b24b --- /dev/null +++ b/test/dotnet-build.Tests/BuildInvalidArgumentsTests.cs @@ -0,0 +1,55 @@ +using System.IO; +using Microsoft.DotNet.Tools.Test.Utilities; +using Xunit; + +namespace Microsoft.DotNet.Tools.Builder.Tests +{ + public class BuildInvalidArgumentsTests : TestBase + { + [Fact] + public void ErrorOccursWhenBuildingPortableProjectToSpecificOutputPathWithoutSpecifyingFramework() + { + var testInstance = TestAssetsManager.CreateTestInstance("BuildTestPortableProject") + .WithLockFiles(); + + var result = new BuildCommand( + projectPath: testInstance.TestRoot, + output: Path.Combine(testInstance.TestRoot, "out")) + .ExecuteWithCapturedOutput(); + + result.Should().Fail(); + result.Should().HaveStdErrContaining("When the '--output' option is provided, the '--framework' option must also be provided."); + } + + [Fact] + public void ErrorOccursWhenBuildingPortableProjectAndSpecifyingFrameworkThatProjectDoesNotSupport() + { + var testInstance = TestAssetsManager.CreateTestInstance("BuildTestPortableProject") + .WithLockFiles(); + + var result = new BuildCommand( + projectPath: testInstance.TestRoot, + output: Path.Combine(testInstance.TestRoot, "out"), + framework: "sl40") + .ExecuteWithCapturedOutput(); + + result.Should().Fail(); + result.Should().HaveStdErrContaining("Project does not support framework: Silverlight,Version=v4.0."); + } + + [Fact] + public void ErrorOccursWhenBuildingStandaloneProjectToSpecificOutputPathWithoutSpecifyingFramework() + { + var testInstance = TestAssetsManager.CreateTestInstance("BuildTestStandaloneProject") + .WithLockFiles(); + + var result = new BuildCommand( + projectPath: testInstance.TestRoot, + output: Path.Combine(testInstance.TestRoot, "out")) + .ExecuteWithCapturedOutput(); + + result.Should().Fail(); + result.Should().HaveStdErrContaining("When the '--output' option is provided, the '--framework' option must also be provided."); + } + } +} diff --git a/test/dotnet-build.Tests/BuildPortableTests.cs b/test/dotnet-build.Tests/BuildPortableTests.cs new file mode 100644 index 000000000..6ecfad1f1 --- /dev/null +++ b/test/dotnet-build.Tests/BuildPortableTests.cs @@ -0,0 +1,44 @@ +using System.IO; +using Microsoft.DotNet.Tools.Test.Utilities; +using Xunit; + +namespace Microsoft.DotNet.Tools.Builder.Tests +{ + public class BuildPortableTests : TestBase + { + [Fact] + public void BuildingAPortableProjectProducesDepsFile() + { + var testInstance = TestAssetsManager.CreateTestInstance("BuildTestPortableProject") + .WithLockFiles(); + + var result = new BuildCommand( + projectPath: testInstance.TestRoot, + forcePortable: true) + .ExecuteWithCapturedOutput(); + + result.Should().Pass(); + + var outputBase = new DirectoryInfo(Path.Combine(testInstance.TestRoot, "bin", "Debug")); + + var netstandardappOutput = outputBase.Sub("netstandardapp1.5"); + var fxSubdirs = new[] { + netstandardappOutput, + outputBase.Sub("net45") + }; + + foreach(var fxSubdir in fxSubdirs) + { + fxSubdir.Should() + .Exist().And + .HaveFiles(new[] + { + "BuildTestPortableProject.dll", + "BuildTestPortableProject.pdb" + }); + } + + netstandardappOutput.Should().HaveFile("BuildTestPortableProject.deps"); + } + } +} diff --git a/test/dotnet-build.Tests/BuildProjectToProjectTests.cs b/test/dotnet-build.Tests/BuildProjectToProjectTests.cs index c3b55a740..8735a84a6 100644 --- a/test/dotnet-build.Tests/BuildProjectToProjectTests.cs +++ b/test/dotnet-build.Tests/BuildProjectToProjectTests.cs @@ -75,7 +75,7 @@ namespace Microsoft.DotNet.Tools.Builder.Tests // second build with no dependencies and no incremental; only the root rebuilds var result2 = BuildProject(noDependencies: true, noIncremental: true); - result2.Should().StdOutMatchPattern("Compiling.*L0.*"); + result2.Should().HaveStdOutMatching("Compiling.*L0.*"); AssertResultDoesNotContainStrings(result2, dependencies); diff --git a/test/dotnet-publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs b/test/dotnet-publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs index c1dcfeda3..22562e818 100644 --- a/test/dotnet-publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs +++ b/test/dotnet-publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs @@ -188,7 +188,7 @@ namespace Microsoft.DotNet.Tools.Publish.Tests var publishCommand = new PublishCommand(testProject); var result = publishCommand.ExecuteWithCapturedOutput(); - result.Should().StdOutMatchPattern("\nprepublish_output( \\?[^%]+\\?){5}.+\npostpublish_output( \\?[^%]+\\?){5}", RegexOptions.Singleline); + result.Should().HaveStdOutMatching("\nprepublish_output( \\?[^%]+\\?){5}.+\npostpublish_output( \\?[^%]+\\?){5}", RegexOptions.Singleline); result.Should().Pass(); } }