Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Debug support #858

Merged
merged 1 commit into from
May 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Cake.Tests/Cake.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
<Compile Include="Extensions\StreamReaderExtensions.cs" />
<Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="Fixtures\ArgumentParserFixture.cs" />
<Compile Include="Fixtures\DebugCommandFixture.cs" />
<Compile Include="Fixtures\CakeApplicationFixture.cs" />
<Compile Include="Fixtures\MonoScriptProcessorFixture.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
Expand All @@ -79,6 +80,7 @@
<Compile Include="Unit\CakeReportPrinterTests.cs" />
<Compile Include="Unit\Diagnostics\CakeBuildLogTests.cs" />
<Compile Include="Unit\Diagnostics\FormatParserTests.cs" />
<Compile Include="Unit\Commands\DebugCommandTests.cs" />
<Compile Include="Unit\Scripting\BuildScriptHostTests.cs">
<SubType>Code</SubType>
</Compile>
Expand Down
47 changes: 47 additions & 0 deletions src/Cake.Tests/Fixtures/DebugCommandFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Cake.Core;
using Cake.Core.Diagnostics;
using Cake.Core.Scripting;
using Cake.Commands;
using Cake.Diagnostics;
using Cake.Scripting;
using NSubstitute;

namespace Cake.Tests.Fixtures
{
internal sealed class DebugCommandFixture
{
public IScriptRunner ScriptRunner { get; set; }
public ICakeLog Log { get; set; }
public IDebugger Debugger { get; set; }
public CakeOptions Options { get; set; }

public DebugCommandFixture()
{
ScriptRunner = Substitute.For<IScriptRunner>();
Log = Substitute.For<ICakeLog>();
Debugger = Substitute.For<IDebugger>();
Options = new CakeOptions
{
Script = "build.cake"
};
}

public DebugCommand CreateCommand()
{
var context = Substitute.For<ICakeContext>();
var engine = Substitute.For<ICakeEngine>();
var printer = Substitute.For<ICakeReportPrinter>();

context.Log.Returns(Log);

var host = new BuildScriptHost(engine, context, printer, Log);

return new DebugCommand(ScriptRunner, Debugger, host);
}

public bool Execute()
{
return CreateCommand().Execute(Options);
}
}
}
32 changes: 32 additions & 0 deletions src/Cake.Tests/Unit/Arguments/ArgumentParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,22 @@ public void Can_Parse_Version(string input)
// Then
Assert.Equal(true, result.ShowVersion);
}

[Theory]
[InlineData("-debug")]
[InlineData("-d")]
public void Can_Parse_Debug(string input)
{
// Given
var fixture = new ArgumentParserFixture();
var parser = new ArgumentParser(fixture.Log, fixture.VerbosityParser);

// When
var result = parser.Parse(new[] { "build.csx", input });

// Then
Assert.Equal(true, result.PerformDebug);
}
}

public sealed class WithTwoDashLongArguments
Expand Down Expand Up @@ -467,6 +483,22 @@ public void Can_Parse_Version(string input)
// Then
Assert.Equal(true, result.ShowVersion);
}

[Theory]
[InlineData("--debug")]
[InlineData("-d")]
public void Can_Parse_Debug(string input)
{
// Given
var fixture = new ArgumentParserFixture();
var parser = new ArgumentParser(fixture.Log, fixture.VerbosityParser);

// When
var result = parser.Parse(new[] { "build.csx", input });

// Then
Assert.Equal(true, result.PerformDebug);
}
}
}
}
Expand Down
61 changes: 61 additions & 0 deletions src/Cake.Tests/Unit/Commands/DebugCommandTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using Cake.Core.Diagnostics;
using Cake.Scripting;
using Cake.Tests.Fixtures;
using NSubstitute;
using Xunit;

namespace Cake.Tests.Unit.Scripting
{
public sealed class DebugCommandTests
{
public sealed class TheExecuteMethod
{
[Fact]
public void Should_Throw_If_Options_Is_Null()
{
// Given
var fixture = new DebugCommandFixture();
fixture.Options = null;

// When
var result = Record.Exception(() => fixture.Execute());

// Then
Assert.IsArgumentNullException(result, "options");
}

[Fact]
public void Should_Proxy_Call_To_ScriptRunner()
{
// Given
var fixture = new DebugCommandFixture();
var runner = fixture.ScriptRunner;
var options = fixture.Options;

// When
fixture.Execute();

// Then
runner.Received(1).Run(Arg.Any<BuildScriptHost>(), options.Script, options.Arguments);
}

[Fact]
public void Should_Report_Correct_Process_Id()
{
// Given
var fixture = new DebugCommandFixture();
var debugger = fixture.Debugger;
var log = fixture.Log;
var pid = 1234567;

debugger.GetProcessId().Returns(pid);

// When
fixture.Execute();

// Then
log.Received(1).Information(Verbosity.Quiet, Arg.Any<string>(), pid);
}
}
}
}
6 changes: 6 additions & 0 deletions src/Cake/Arguments/ArgumentParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ private bool ParseOption(string name, string value, CakeOptions options)
options.ShowVersion = true;
}

if (name.Equals("debug", StringComparison.OrdinalIgnoreCase) ||
name.Equals("d", StringComparison.OrdinalIgnoreCase))
{
options.PerformDebug = true;
}

if (options.Arguments.ContainsKey(name))
{
_log.Error("Multiple arguments with the same name ({0}).", name);
Expand Down
2 changes: 2 additions & 0 deletions src/Cake/Autofac/CakeModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ protected override void Load(ContainerBuilder builder)
builder.RegisterType<ScriptRunner>().As<IScriptRunner>().SingleInstance();
builder.RegisterType<CakeBuildLog>().As<ICakeLog>().As<IVerbosityAwareLog>().SingleInstance();
builder.RegisterType<VerbosityParser>().SingleInstance();
builder.RegisterType<CakeDebugger>().As<IDebugger>().SingleInstance();

// Register script hosts.
builder.RegisterType<BuildScriptHost>().SingleInstance();
Expand All @@ -64,6 +65,7 @@ protected override void Load(ContainerBuilder builder)

// Register commands.
builder.RegisterType<BuildCommand>().AsSelf().InstancePerDependency();
builder.RegisterType<DebugCommand>().AsSelf().InstancePerDependency();
builder.RegisterType<DescriptionCommand>().AsSelf().InstancePerDependency();
builder.RegisterType<DryRunCommand>().AsSelf().InstancePerDependency();
builder.RegisterType<HelpCommand>().AsSelf().InstancePerDependency();
Expand Down
15 changes: 13 additions & 2 deletions src/Cake/Autofac/ScriptingModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,19 @@ protected override void Load(ContainerBuilder builder)
{
// Roslyn
builder.RegisterType<RoslynScriptEngine>().As<IScriptEngine>().SingleInstance();
builder.RegisterType<RoslynScriptSessionFactory>().SingleInstance();
builder.RegisterType<RoslynNightlyScriptSessionFactory>().SingleInstance();

if (_options.Arguments.ContainsKey("debug"))
{
// Debug
builder.RegisterType<DebugRoslynScriptSessionFactory>().As<RoslynScriptSessionFactory>().SingleInstance();
builder.RegisterType<DebugRoslynNightlyScriptSessionFactory>().As<RoslynNightlyScriptSessionFactory>().SingleInstance();
}
else
{
// Default
builder.RegisterType<DefaultRoslynScriptSessionFactory>().As<RoslynScriptSessionFactory>().SingleInstance();
builder.RegisterType<DefaultRoslynNightlyScriptSessionFactory>().As<RoslynNightlyScriptSessionFactory>().SingleInstance();
}
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/Cake/Cake.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
<Compile Include="CakeReportPrinter.cs" />
<Compile Include="Commands\BuildCommand.cs" />
<Compile Include="Commands\CommandFactory.cs" />
<Compile Include="Commands\DebugCommand.cs" />
<Compile Include="Commands\DescriptionCommand.cs" />
<Compile Include="Commands\DryRunCommand.cs" />
<Compile Include="Commands\ErrorCommandDecorator.cs" />
Expand All @@ -146,10 +147,12 @@
<Compile Include="Commands\VersionCommand.cs" />
<Compile Include="Diagnostics\CakeBuildLog.cs" />
<Compile Include="Diagnostics\ConsolePalette.cs" />
<Compile Include="Diagnostics\CakeDebugger.cs" />
<Compile Include="Diagnostics\Formatting\FormatParser.cs" />
<Compile Include="Diagnostics\Formatting\FormatToken.cs" />
<Compile Include="Diagnostics\Formatting\LiteralToken.cs" />
<Compile Include="Diagnostics\Formatting\PropertyToken.cs" />
<Compile Include="Diagnostics\IDebugger.cs" />
<Compile Include="Diagnostics\IVerbosityAware.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
Expand All @@ -168,12 +171,20 @@
<Compile Include="Scripting\Mono\CodeGen\Parsing\ScriptBlock.cs" />
<Compile Include="Scripting\Mono\CodeGen\Parsing\ScriptParser.cs" />
<Compile Include="Scripting\Mono\CodeGen\MonoScriptProcessor.cs" />
<Compile Include="Scripting\Roslyn\Nightly\DefaultRoslynNightlyScriptSession.cs" />
<Compile Include="Scripting\Roslyn\Nightly\DebugRoslynNightlyScriptSession.cs" />
<Compile Include="Scripting\Roslyn\Nightly\RoslynNightlyScriptSession.cs" />
<Compile Include="Scripting\Roslyn\Nightly\DebugRoslynNightlyScriptSessionFactory.cs" />
<Compile Include="Scripting\Roslyn\Nightly\DefaultRoslynNightlyScriptSessionFactory.cs" />
<Compile Include="Scripting\Roslyn\Nightly\RoslynNightlyScriptSessionFactory.cs" />
<Compile Include="Scripting\Roslyn\RoslynCodeGenerator.cs" />
<Compile Include="Scripting\Roslyn\Stable\RoslynScriptSession.cs" />
<Compile Include="Scripting\Roslyn\Stable\DebugRoslynScriptSession.cs" />
<Compile Include="Scripting\Roslyn\Stable\DefaultRoslynScriptSession.cs" />
<Compile Include="Scripting\Roslyn\RoslynScriptEngine.cs" />
<Compile Include="Scripting\Roslyn\Stable\RoslynScriptSessionFactory.cs" />
<Compile Include="Scripting\Roslyn\Stable\DebugRoslynScriptSessionFactory.cs" />
<Compile Include="Scripting\Roslyn\Stable\DefaultRoslynScriptSessionFactory.cs" />
<Compile Include="Scripting\Mono\MonoScriptSession.cs" />
<Compile Include="Scripting\Mono\MonoScriptEngine.cs" />
<Compile Include="Scripting\Mono\CodeGen\MonoCodeGenerator.cs" />
Expand Down
4 changes: 4 additions & 0 deletions src/Cake/CakeApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ private ICommand CreateCommand(CakeOptions options)
{
return _commandFactory.CreateDescriptionCommand();
}
if (options.PerformDebug)
{
return _commandFactory.CreateDebugCommand();
}

return _commandFactory.CreateBuildCommand();
}
Expand Down
8 changes: 8 additions & 0 deletions src/Cake/CakeOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ public IDictionary<string, string> Arguments
/// </value>
public bool PerformDryRun { get; set; }

/// <summary>
/// Gets or sets a value indicating whether to debug script.
/// </summary>
/// <value>
/// <c>true</c> if a debug session should be started; otherwise, <c>false</c>.
/// </value>
public bool PerformDebug { get; set; }

/// <summary>
/// Gets or sets a value indicating whether to show help.
/// </summary>
Expand Down
8 changes: 8 additions & 0 deletions src/Cake/Commands/CommandFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@
internal sealed class CommandFactory : ICommandFactory
{
private readonly BuildCommand.Factory _buildCommandFactory;
private readonly DebugCommand.Factory _debugCommandFactory;
private readonly DescriptionCommand.Factory _descriptionCommandFactory;
private readonly DryRunCommand.Factory _dryRunCommandFactory;
private readonly HelpCommand.Factory _helpCommandFactory;
private readonly VersionCommand.Factory _versionCommandFactory;

public CommandFactory(
BuildCommand.Factory buildCommandFactory,
DebugCommand.Factory debugCommandFactory,
DescriptionCommand.Factory descriptionCommandFactory,
DryRunCommand.Factory dryRunCommandFactory,
HelpCommand.Factory helpCommandFactory,
VersionCommand.Factory versionCommandFactory)
{
_buildCommandFactory = buildCommandFactory;
_debugCommandFactory = debugCommandFactory;
_descriptionCommandFactory = descriptionCommandFactory;
_dryRunCommandFactory = dryRunCommandFactory;
_helpCommandFactory = helpCommandFactory;
Expand All @@ -27,6 +30,11 @@ public ICommand CreateBuildCommand()
return _buildCommandFactory();
}

public ICommand CreateDebugCommand()
{
return _debugCommandFactory();
}

public ICommand CreateDescriptionCommand()
{
return _descriptionCommandFactory();
Expand Down
52 changes: 52 additions & 0 deletions src/Cake/Commands/DebugCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System;
using System.Threading;
using Cake.Core.Diagnostics;
using Cake.Core.Scripting;
using Cake.Diagnostics;
using Cake.Scripting;

namespace Cake.Commands
{
/// <summary>
/// A command that builds and debugs a build script.
/// </summary>
internal sealed class DebugCommand : ICommand
{
private readonly IScriptRunner _scriptRunner;
private readonly IDebugger _debugger;
private readonly ICakeLog _log;
private readonly BuildScriptHost _host;

// Delegate factory used by Autofac.
public delegate DebugCommand Factory();

public DebugCommand(IScriptRunner scriptRunner, IDebugger debugger, BuildScriptHost host)
{
_scriptRunner = scriptRunner;
_debugger = debugger;
_host = host;
_log = _host.Context.Log;
}

public bool Execute(CakeOptions options)
{
if (options == null)
{
throw new ArgumentNullException("options");
}

var message = "Attach debugger to process {0} to continue";
var pid = _debugger.GetProcessId();

_log.Debug("Performing debug...");
_log.Information(Verbosity.Quiet, message, pid);

_debugger.WaitForAttach(Timeout.InfiniteTimeSpan);

_log.Debug("Debugger attached");

_scriptRunner.Run(_host, options.Script, options.Arguments);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently it will crash if break point isn't an task as the debugger isn't attached until tasks are executed.
image

I.e. if I start the script with a System.Diagnostics.Debugger.Break(); at root level.

Did a quick test by adding this before the _scriptRunner.Run(_host, options.Script, options.Arguments);

            if (!System.Diagnostics.Debugger.IsAttached)
            {
                System.Diagnostics.Debugger.Launch();
            }

This will launch the visual studio debug selector:
image

Then break at the launch
image

F5 will take you to the "root level" Break and all works from there
image

@cake-build/team & @mholo65 what do you think perhaps makes sense to move DebugScriptHost::RunTarget to DebugCommand::Execute?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devlead Yep, that's probably only solution if one wants to debug prior to RunTarget, which one most probably wants 😄

By doing this change, a separate DebugScriptHost shouldn't be needed anymore, it's fine with the default BuildScriptHost.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mholo65 yes makes total sense, go with that. Other than this looks epic 👍
Ping us once you adjusted, rebased and squashed into 1 commit and I'll review for merging.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should wait with launching the debugger until later. Waiting for attachment is probably OK for this PR and then we can expand on this. What do you think?

Copy link
Member

@devlead devlead May 12, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@patriksvensson yes agree, but we should do wait in debug command execute and not RunTarget to fully enable debugging otherwise non task based code can't be debugged. i.e. arguments/global variables/etc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@patriksvensson @devlead yep, a --debug-launch switch could be useful and should be added in another PR... I'll open up an issue to track that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devlead I agree. It should be done in command execute 😄

@mholo65 Perfect!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🍰

return true;
}
}
}
1 change: 1 addition & 0 deletions src/Cake/Commands/HelpCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public bool Execute(CakeOptions options)
_console.WriteLine(" -verbosity=value Specifies the amount of information to be displayed.");
_console.WriteLine(" ({0})",
string.Join(", ", Enum.GetNames(typeof(Verbosity))));
_console.WriteLine(" -debug Performs a debug.");
_console.WriteLine(" -showdescription Shows description about tasks.");
_console.WriteLine(" -dryrun Performs a dry run.");
_console.WriteLine(" -version Displays version information.");
Expand Down
Loading