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

'StartDebugAdapter' command now accepts user's parameters (launch.json) #1466

Merged
merged 14 commits into from
Apr 3, 2020

Conversation

alekseiAlefirov
Copy link
Contributor

@alekseiAlefirov alekseiAlefirov commented Feb 25, 2020

Issue: scalameta/metals-vscode#217

"configurations": [
        {
          "type": "scala",
          "request": "launch",
          "name": "Foo",

          "mainClass": "bar.Foo",
          "buildTarget": "foo",
          "args": ["42"],
          "jvmOptions": ["..."]
      }
    ]

In this PR debug-adapter-start command is modified to accept more type of parameters like configuration above.
launch_debug

Client side PR: scalameta/metals-vscode#223

Copy link
Contributor

@tgodzik tgodzik left a comment

Choose a reason for hiding this comment

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

Really cool, I think this will be super useful. Left some comments, but I will also check the other PR.

@olafurpg olafurpg changed the title Added new command 'debug-resolve-class' for using with 'launch.json' Add new command 'debug-resolve-class' to use with 'launch.json' Feb 26, 2020
@alekseiAlefirov alekseiAlefirov changed the title Add new command 'debug-resolve-class' to use with 'launch.json' 'StartDebugAdapter' command now accepts user's parameters (launch.json) Feb 28, 2020
@olafurpg olafurpg mentioned this pull request Mar 13, 2020
@alekseiAlefirov alekseiAlefirov force-pushed the resolve-debug-class branch 2 times, most recently from c751eb0 to f57dc5a Compare March 16, 2020 15:54
@alekseiAlefirov
Copy link
Contributor Author

debug-resolve-params command removed, while debug-adapter-start modified to accept more type of parameters to support user-set debug configurations.
Still there are problems with newly created tests, presumably because TestingServer do not fully reflect real Metals setup.
Header description of this PR edited.

@mzarnowski
Copy link
Contributor

Also, increasing the timeout to 10000 fixed the failing tests on my machine. To remove the flakiness, you could wait until the workspace indexing finishes before proceeding to start the debug session - this would probably require adding some code around indexing to know when it finishes. The other solution could be to reuse the TestingClient.refreshModelHandler in a similar fashion it is used for CodeLenses, where we facing the same issue.

The underlying cause is that the BuildTargetClasses are fetched initially as a part of the indexing and server.initialize does not wait for the indexing to finish as there is no need to in most of the cases.

@alekseiAlefirov
Copy link
Contributor Author

Thank you for the review and the advice @marek1840 , will try that.
Yes, BuildTargetClasses don't get propagated with the actual data by the time, but I also have encountered following situations sometimes (it is really non-deterministic and hard to catch):

  • BuildTargets do not get updated as well
  • Seems like this indices get updated, but DebugServer.start just hangs - although it's really hard to reproduce, I think, it happens when I run metals-vscode debug (so, run debug inside debug) quite regularly. I am not sure, maybe I do something wrong, but just sharing this knowledge -- maybe you can also comment on this, as with BuildTargetClasses.

@mzarnowski
Copy link
Contributor

BuildTargets do not get updated as well

BuildTargets also get filled during indexing, so the issue should disappear after waiting for indexing to finish (by any of the means described in the previous comment). If the issue still happens after that, we can focus on it then.

As for the server hanging - I am at a loss but can try to debug it if you share the exact sequence of steps (I can at least try, even if it would mean repeating them a few times to encounter this singularity ;) ). Also, a thread dump could help a great deal in finding the cause.

@alekseiAlefirov
Copy link
Contributor Author

alekseiAlefirov commented Mar 18, 2020

if you share the exact sequence of steps

basically, you open metals-vscode project in VS Code, run debug, and in newly opened code instance open a simple project with a main class with one-line main method. Put a breakpoint onto this line, and run debug via codelens. For me it stucks somewhere never reaching the breakpoint and the pause button does not do anything. When I abort it, there's an error message saying no debug adapter found.

@alekseiAlefirov
Copy link
Contributor Author

@marek1840 I've made some reworks, and fixed flakiness of tests by including indexing build targets into MetalsLanguageServer#indexWorkspace and making use of indexingPromise in the tests.

@alekseiAlefirov
Copy link
Contributor Author

Don't see though, why it still fails on CI, the tests are being passed now on my machine 🤔

Copy link
Contributor

@mzarnowski mzarnowski left a comment

Choose a reason for hiding this comment

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

This branch was failing on my machine at first, but after second or third run it started passing and the error never appeared during 5-10 repetitions.

It is worth checking if the the query for build classes is issued before they are populated and whether they are populated at all.

tests/unit/src/test/scala/tests/DebugProtocolSuite.scala Outdated Show resolved Hide resolved
@ckipp01 ckipp01 added the feature-request Used for feature requests label Mar 26, 2020
added rebuild recovery for resolving classes
@alekseiAlefirov alekseiAlefirov marked this pull request as ready for review March 30, 2020 10:33
Copy link
Contributor

@tgodzik tgodzik left a comment

Choose a reason for hiding this comment

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

Looks to be shaping up nicely! I still how some comments, but overall concept and design looks good!

} { target =>
buildTargetClasses
.classesOf(target.getId())
.testClasses
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks like the only difference between the 2 functions is here and two lines below. Can w reuse the code and just add maybe a function Classes => Option[T]?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

okay, I've added

private def findClassAndBuildTarget[A](
      className: String,
      buildTarget: Option[String],
      findClassesByName: String => List[(A, b.BuildTargetIdentifier)],
      classesByBuildTarget: b.BuildTargetIdentifier => Iterable[A],
      getClassName: A => String
  ): Try[List[(A, b.BuildTarget)]]

to generalize them

|{
| mainClass: "com.foo.App",
| buildTarget: "foo",
| args: ["bar"]
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we able to also add environment variables? Also, let's add jvmOptions to the example.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the input for this Params class is based on ScalaMainClass params, and it does not take environment variables.
About jvmOpts -- can you propose one?

Copy link
Contributor

Choose a reason for hiding this comment

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

@marek1840 Would make sense to add them to BSP?
@alekseiAlefirov Maybe -Dfile.encoding=UTF-8?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added

@@ -152,7 +152,7 @@ private[debug] object DebugProxy {
endpoint: RemoteEndpoint,
name: String
): RemoteEndpoint = {
val trace = GlobalTrace.setup(name)
val trace = GlobalTrace.setupTracePrinter(name)
Copy link
Contributor

Choose a reason for hiding this comment

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

This should fixed the issues with missing logs.

buildTargetClasses: BuildTargetClasses
)(implicit ec: ExecutionContext): Future[A] = {
Future.fromTry(f()).recoverWith {
case _: ClassNotFoundException | _: BuildTargetNotFoundException =>
Copy link
Contributor

Choose a reason for hiding this comment

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

BuildTargetNotFoundException will not be needed here. User would need reimport the build to change them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good one, fixed

@@ -105,6 +114,85 @@ object DebugServer {
}
}

def resolveMainClassParams(
params: DebugUnresolvedMainClassParams,
buildTargetClassesFinder: BuildTargetClassesFinder,
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's maybe move these new methods to BuildTargetClassesFinder with other than params method parameters added to the constructor? Also, with client instead of showWarningMessage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not sure. This method takes some debug parameters to resolve them to other parameters and reports about running debug on one class, if multiple found. So it's more about debug, then about finding, which is extracted to Finder

Copy link
Contributor

Choose a reason for hiding this comment

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

Then maybe this should not actually be an object but maybe DebugProvider class? Start also takes a lot of parameters, which are not specific to the actual function invocation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

DebugProvider class created, all code from DebugServer object moved to it.

case _: ClassNotFoundException | _: BuildTargetNotFoundException =>
val allTargets = buildTargets.all.toSeq.map(_.info.getId())
for {
_ <- compilations.compileTargets(allTargets)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think even if we manage to find the build target we should still run compile on it.

So, do the same, but on single target first. If that fails, compile all targets.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure, added narrowed recovery for ClassNotFoundInBuildTargetException

Copy link
Contributor

@tgodzik tgodzik left a comment

Choose a reason for hiding this comment

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

Just two more comments! Sorry for being so nitpicky! 😅

}

session.asJavaObject
val debugSessionParamsParser = new JsonParser.Of[b.DebugSessionParams]
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's maybe move those parsers to an object, no need to recreate them each time.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

created DebugParametersJsonParsers with these parsers

@@ -105,6 +114,85 @@ object DebugServer {
}
}

def resolveMainClassParams(
params: DebugUnresolvedMainClassParams,
buildTargetClassesFinder: BuildTargetClassesFinder,
Copy link
Contributor

Choose a reason for hiding this comment

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

Then maybe this should not actually be an object but maybe DebugProvider class? Start also takes a lot of parameters, which are not specific to the actual function invocation.

@alekseiAlefirov
Copy link
Contributor Author

@tgodzik agreed on that DebugServer object needed to be a class. DebugProvider class created.

@@ -122,6 +123,8 @@ class MetalsLanguageServer(
private val languageClient = new DelegatingLanguageClient(NoopLanguageClient)
var userConfig: UserConfiguration = UserConfiguration()
val buildTargets: BuildTargets = new BuildTargets()
private val buildTargetClassesFinder =
Copy link
Contributor

Choose a reason for hiding this comment

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

This is unused now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thank you! fixed

Copy link
Contributor

@tgodzik tgodzik left a comment

Choose a reason for hiding this comment

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

One comment left, but otherwise I think this is ready.

Copy link
Contributor

@tgodzik tgodzik left a comment

Choose a reason for hiding this comment

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

Jus last comments, tested it all out and seems to work really good! Thanks for the awesome cotnribution!

params.testClass,
Option(params.buildTarget)
)
}).map {
Copy link
Contributor

Choose a reason for hiding this comment

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

This will fail on Nil, could you handle that case?

Option(params.buildTarget)
)
).map {
case (clazz, target) :: others =>
Copy link
Contributor

Choose a reason for hiding this comment

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

This will also fail on Nil, could you handle that case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

as I commented on BuildTargetClassFinder#findMainClassAndItsBuildTarget (above method def), it either returns Success with non-empty list, or Failure. So it should not be the case. I wish we had a type-level solution for that.

Copy link
Contributor

Choose a reason for hiding this comment

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

Solution for NonEmptyList? You have scalaz.NonEmptyList, cats.data.NonEmptyList, everything is already included in dependencies

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hmm, last time I checked (several months ago), cats just got eliminated from Metals, and it was said it's better to have less dependencies. Okay, thanks, @kpbochenek !

Copy link
Contributor Author

Choose a reason for hiding this comment

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

okay, seems like there's no cats used in the main code, and @tgodzik recommended not to start.
^ fixed with throw NoSuchElement(Exception) for the Nil case

Copy link
Contributor

@tgodzik tgodzik left a comment

Choose a reason for hiding this comment

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

LGTM!

@tgodzik
Copy link
Contributor

tgodzik commented Apr 3, 2020

@ckipp01 This might be helpful for coc-metals and vim-inspector -> you will able to run a simplified command without code lenses.

@tgodzik tgodzik merged commit ca3def6 into scalameta:master Apr 3, 2020
@ckipp01
Copy link
Member

ckipp01 commented Apr 3, 2020

Yes! I'll have to dive into this a bit more and then that'd be awesome. Because right now if it's reliant on code-lenses, then only nvim users will benefit.

@tgodzik
Copy link
Contributor

tgodzik commented Apr 3, 2020

Yes! I'll have to dive into this a bit more and then that'd be awesome. Because right now if it's reliant on code-lenses, then only nvim users will benefit.

Exactly, with this PR however we will no longer be reliant on code lenses.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request Used for feature requests
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants