A brief introduction to the analysis server (and to me)

584 views
Skip to first unread message

Paul Berry

unread,
Jul 19, 2014, 10:24:23 AM7/19/14
to analyzer...@dartlang.org
Hello all--

I'm Paul Berry.  I'm one of the engineers working on the Dart Editor (the flagship IDE for Dart development) and the Dart Analysis Server (the mechanism that we hope to make available soon to support rich analysis and refactoring services in a large number of editors, including the Dart Editor itself). 

There's been a recent surge of interest in writing editor plugins that use the analysis server.  I'm currently aware of people in the community interested in writing plugins for vim, DevStudio, and sublime.  (I'm also planning to do a plugin for emacs).  This is really exciting, since the more popular editors we can support, the more users will be able to jump straight into Dart development without having to learn how to use an entirely new development environment.  So I thought I'd write up a brief overview of what the analysis server is and how to use it.  We're in the process of putting together a larger (and much more complete) specification document, but it's not ready yet, so hopefully this email can hold you over while you wait.


Getting and running the analysis server

The analysis server is a Dart command-line application.  It is located inside the Dart repository, in https://code.google.com/p/dart/source/browse/#svn%2Fbranches%2Fbleeding_edge%2Fdart%2Fpkg%2Fanalysis_server.  The main executable is located in bin/server.dart.  Due to a bug which I haven't fully characterized yet, the server seems to be sensitive to which working directory it's invoked from.  Until this is fixed (which will hopefully happen next week), I recommend starting the analysis server from the "dart" directory (https://code.google.com/p/dart/source/browse/#svn%2Fbranches%2Fbleeding_edge%2Fdart).

The analysis server depends on a number of other packages.  You can either get these packages by running "pub get" from the analysis_server directory, or, if you have a full local build of the dart SDK, you can point your package root at its "packages" directory.  So, for example, this is how I'm currently running the analysis server on my mac:

paulberry-macbookpro:dart paulberry$ sdk/bin/dart --package-root=xcodebuild/ReleaseIA32/packages pkg/analysis_server/bin/server.dart

In the long run our plan is to build the analysis server into the released SDK, so that users won't have to mess around with "pub get" or have a full local build of the SDK in order to use plugins that take advantage of it.


Communicating with the analysis server

The analysis server expects to communicate with its client using standard input and output.  Each line that is received on standard input is interpreted as a JSON request, and each line that it sends on standard output is either a JSON response to a specific request or a JSON notification.  (Note that unlike a typical use of JSON, line breaks are not permitted within a single JSON request--each request must be contained on a single line).

The detailed structure of the JSON requests, responses, and notifications hasn't quite been settled on yet, but I'll walk through an example to give you an idea of what to expect, and then I'll follow up with some pointers to where to get more information.  Feel free to ask questions on this list!


Example: Getting errors, warnings, and hints

In the example below, the words "SEND:" and "RECV:" are not part of the protocol--they are just to indicate which messages are sent to the server and which messages are received from the server.

RECV: {"event":"server.connected"}

This is always the first message from the server.  The client can watch for this to determine that the server started up successfully and is ready to analyze files.

SEND: {"id":"0","method":"analysis.setAnalysisRoots","params":{"included":["/var/folders/55/83dl8yk507bbspsg0b1vd5b0007dgy/T/analysisServerw7MAbh"],"excluded":[]}}

This request tells the server where to find the source code to analyze.  (The crazy pathname "/var/folders/55/83dl8yk507bbspsg0b1vd5b0007dgy/T/analysisServerw7MAbh" is because I captured this trace from an integration test, which puts all its files in a temporary directory).  The server will analyze all files that are descendants of that directory, as well as files in any dependent packages.  Note that the pathname is in a list--this allows the client to request that multiple directories should be analyzed.  Note also that the "id" string can be anything--it's just used to associate requests with responses.

RECV: {"id":"0"}

This is a response from the server acknowledging that the above request completed successfully.  Successful completion of the "analysis.setAnalysisRoots" request doesn't mean that everything has been analyzed--it just means that the server has updated its notion of where to go looking for files to analyze.

If the request had been malformed, the response would have contained an "error" field describing the problem.

(Note: some requests ask for data from the analysis server.  The responses to these requests will contain a "data" field containing the requested data).

RECV: {"params":{"analysis":{"analyzing":true}},"event":"server.status"}

This is a notification from the server telling the client that analysis has begun.  Clients might find it useful to put up a subtle visual indication that analysis is in progress so that the user knows what's happening.

RECV: {"params":{"errors":[],"file":"/var/folders/55/83dl8yk507bbspsg0b1vd5b0007dgy/T/analysisServerw7MAbh/test.dart"},"event":"analysis.errors"}

This is the first of several notifications telling the client what errors, warnings, and hints have been found for the file "test.dart".  The analysis server operates in several stages, and it reports back errors, warnings, and hints after each stage, so that the user can see results as soon as they're available.  This notification has an empty list for "errors", meaning that no errors have been detected in "test.dart" yet.

RECV: {"params":{"errors":[{"severity":"ERROR","type":"SYNTACTIC_ERROR","location":{"file":"/var/folders/55/83dl8yk507bbspsg0b1vd5b0007dgy/T/analysisServerw7MAbh/test.dart","offset":15,"length":1,"startLine":2,"startColumn":7},"message":"Expected to find ';'"}],"file":"/var/folders/55/83dl8yk507bbspsg0b1vd5b0007dgy/T/analysisServerw7MAbh/test.dart"},"event":"analysis.errors"}

Now more analysis has been done, and the analysis server has discovered a parse error.  Note that the parse error is associated with an offset (measured in unicode characters from the start of the file), a length (again measured in unicode characters), a line and column number (for the convenience of clients that keep track of file locations that way), and a human-readable error message.  Each error also has a severity and a type, so that the client can easily do things like display errors more prominently than warnings.

RECV: {"params":{"errors":[{"severity":"ERROR","type":"SYNTACTIC_ERROR","location":{"file":"/var/folders/55/83dl8yk507bbspsg0b1vd5b0007dgy/T/analysisServerw7MAbh/test.dart","offset":15,"length":1,"startLine":2,"startColumn":7},"message":"Expected to find ';'"}],"file":"/var/folders/55/83dl8yk507bbspsg0b1vd5b0007dgy/T/analysisServerw7MAbh/test.dart"},"event":"analysis.errors"}

Now even more analysis has been done, but there are no new errors.  Note that after each stage of analysis the server sends a complete list of the errors for the file that was analyzed.  So when the client receives this message, it can just throw out the previous list of errors for that file and start using the new list.  It doesn't need to accumulate them.

RECV: {"params":{"errors":[{"severity":"ERROR","type":"SYNTACTIC_ERROR","location":{"file":"/var/folders/55/83dl8yk507bbspsg0b1vd5b0007dgy/T/analysisServerw7MAbh/test.dart","offset":15,"length":1,"startLine":2,"startColumn":7},"message":"Expected to find ';'"}],"file":"/var/folders/55/83dl8yk507bbspsg0b1vd5b0007dgy/T/analysisServerw7MAbh/test.dart"},"event":"analysis.errors"}
RECV: {"params":{"errors":[{"severity":"ERROR","type":"SYNTACTIC_ERROR","location":{"file":"/var/folders/55/83dl8yk507bbspsg0b1vd5b0007dgy/T/analysisServerw7MAbh/test.dart","offset":15,"length":1,"startLine":2,"startColumn":7},"message":"Expected to find ';'"}],"file":"/var/folders/55/83dl8yk507bbspsg0b1vd5b0007dgy/T/analysisServerw7MAbh/test.dart"},"event":"analysis.errors"}

Analysis proceeds further.  In this particular example no more errors are detected.

RECV: {"params":{"analysis":{"analyzing":false}},"event":"server.status"}

This is a notification from the server that analysis is now complete.  At this point the server goes silent.

However, it's not completely idle.  The server watches the directory for changes, so if the contents of test.dart are changed, it will automatically be re-analyzed without the client needing to request anything.  If one of the files test.dart depends on is changed, and that change causes new errors/warnings/hints in test.dart (or causes errors/warnings/hints in test.dart to go away), then test.dart will automatically be re-analyzed.

Also, if a new file appears, it will automatically get analyzed (provided that it's a descendant of the directory that was specified in analysis.setAnalysisRoots).  If a file disappears from the filesystem, it will stop being analyzed, and some notification will be sent to the client (but we haven't precisely decided what that notification will look like yet).


How to find out what's implemented now

I generated the above example by going to the file pkg/analysis_server/test/integration/analysis_error_inttest.dart, adding the line "debugStdio();" to the test_detect_simple_error() method, and running the test.  You can do this to any of the integration tests in pkg/analysis_server/test/integration to see what messages are exchanged between client and server.  Until we are ready to publish the analysis server spec, this is probably one of your best sources of information about how to use the analysis server.  New tests are being added all the time, so check back frequently.

You can also look at the implementation of the analysis server (in particular the files pkg/analysis_server/lib/src/domain_*.dart should give you an idea of what other requests are currently supported).  Note that these files refer to constants defined in pkg/analysis_server/lib/src/constants.dart.


What's planned to be implemented

We are planning to entirely replace the analysis engine used by the Dart Editor with the analysis server.  So nearly all the features in the Dart Editor should eventually be available, in one way or another, through the analysis server.  This includes things like: computing errors/warnings/hints, automated refactoring, "quick fixes", computing the outline for a file, computing a class hierarchy, computing pop-up information about an identifier, jumping from an identifier to its definition, and searching for all usages of a function, method, or class.  In addition, the analysis server will be able to deal with files that are modified in the editor but haven't yet been saved to disk.  Most of the code to support these features has already been written (after all, these features currently work in the dart editor)--we just need to wire up the protocol so that they can be invoked from the analysis server.


Caveats

The protocol is still in flux, which means that the example in this email, and anything you learn from reading the code, is subject to change.  If you spot something that seems wrong, or you have a suggestion or a question about how something works, please feel free to ask!


Additional random information

Paul Jolly asked me some questions in an email before he joined this mailing list.  For the benefit of others, I'd like to answer them here:

  • Do you have any high level architecture-esque overviews of the setup? Whilst I get the general concept, I'm assuming there is there going to be a single, daemon, server process used by all editor instances? 
Hopefully the above explanation contains the information you're looking for.  Our assumption is that the user doesn't need a large number of clients, and we didn't want to have to worry about the security implications of opening up a socket that multiple clients could connect to (e.g. on a multi-user machine this could allow one user to snoop another's private files).  So we are assuming that if the user launches multiple editor instances, each one will start up and talk to its own server.

  • How would this setup work with multiple Dart installations? For example, in my early experimentation I'm flicking between dart1.6.0-dev.3.0 and dart1.5.3 on a per shell (and hence per editor) basis. One server instance per Dart version? This may be an edge case
We are planning a request 'analysis.updateSdks' that will allow the client to specify the location of the SDK.  It isn't implemented yet.

  • What sort of response time are you expecting for a completion request (with a warm cache)?
I don't know precisely, but in order to be reasonably usable I imagine it's going to need to be substantially less than a second.

  • I couldn't find (probably through lack of the proper search) any specification of how Dart source code is encoded. For example, Go is specified as Unicode text encoded in UTF-8. I ask because I'm assuming offsets are byte offsets in files? This is really a small detail in any case because Vim will be able to calculate any offset we like
I can't find anything in the spec about this either.  But the VM assumes all the input files are valid UTF-8 (and will complain if they are not).  I suspect dart2js does the same.

The analysis server assumes UTF-8 both for the files it analyzes and for the communication with the client.  And it measures all file offsets and lengths in units of unicode code points, not bytes.

So, short answer: everything is UTF-8.

  • I'm guessing you're anticipating use cases of unwritten files (buffers)? Vim + gocode handles this via temp files
Yes.  The way this will work is that the client will specify the contents of the unsaved file using the "analysis.updateContent" request, and thereafter the analysis server will ignore what's in the filesystem in favor of the data from the client.  The client can later specify (again using "analysis.updateContent") that the file is no longer in the unsaved state, and the analysis server will go back to reading the contents of the file from the filesystem.

I hope we can make this work for Vim.  If it's a problem, please let us know and we can brainstorm another technique.  However I'm hoping it doesn't come to that; I'd rather not support multiple ways of doing things unless we have to.


Hope this is helpful to get folks started!

Paul

Paul Jolly

unread,
Jul 21, 2014, 11:35:24 AM7/21/14
to analyzer...@dartlang.org
Hi PaulB, 

Thanks very much for the outline. Some questions inline.

The analysis server depends on a number of other packages.  You can either get these packages by running "pub get" from the analysis_server directory, or, if you have a full local build of the dart SDK, you can point your package root at its "packages" directory.  So, for example, this is how I'm currently running the analysis server on my mac:

paulberry-macbookpro:dart paulberry$ sdk/bin/dart --package-root=xcodebuild/ReleaseIA32/packages pkg/analysis_server/bin/server.dart

If it helps in your debugging of this issue:

$ dart --version
Dart VM version: 1.6.0-dev.3.0 (Thu Jul 10 23:30:54 2014) on "linux_x64"
$ pwd
/home/myitcv/dev/dart/bleeding_edge/dart
$ dart pkg/analysis_server/test/test_all.dart
unittest-suite-wait-for-done
...
All 326 tests passed.
unittest-suite-success
$ cd pkg/analysis_server/
$ pub run test/test_all
unittest-suite-wait-for-done
...
All 326 tests passed.
unittest-suite-success
$ dart test/test_all.dart
unittest-suite-wait-for-done
ERROR: analysis_server | notification.hover | dartDoc_clunky
  Test failed: Caught FileSystemException: Cannot open file, path = '/home/myitcv/dev/dart/bleeding_edge/dart/pkg/analysis_server/./lib/_internal/libraries.dart' (OS Error: No such file or directory, errno = 2)
  dart:io/file_impl.dart 553                               _File.throwIfError
  dart:io/file_impl.dart 405                               _File.openSync
  dart:io/file_impl.dart 464                               _File.readAsBytesSync
  dart:io/file_impl.dart 498                               _File.readAsStringSync
  package:analyzer/src/generated/java_io.dart 116:59       JavaFile.readAsStringSync
  package:analyzer/src/generated/sdk_io.dart 438:55        DirectoryBasedDartSdk.initialLibraryMap
  package:analyzer/src/generated/sdk_io.dart 229:36        DirectoryBasedDartSdk.DirectoryBasedDartSdk
  package:analyzer/src/generated/sdk_io.dart 199:16        DirectoryBasedDartSdk.defaultSdk
  package:analysis_server/src/analysis_server.dart 39:42   SHARED_SDK
  package:analysis_server/src/analysis_server.dart 146:24  AnalysisServer.AnalysisServer
  test/analysis_abstract.dart 229:18                       AbstractAnalysisTest.setUp
  test/analysis_hover_test.dart 48:16                      AnalysisHoverTest.setUp
  dart:mirrors-patch/mirrors_impl.dart 426                 _LocalInstanceMirror.invoke
  package:analysis_testing/reflective_tests.dart 78:45     _invokeSymbolIfExists
  package:analysis_testing/reflective_tests.dart 69:31     _runTest
  package:analysis_testing/reflective_tests.dart 52:24     runReflectiveTests.<fn>.<fn>
  package:unittest/src/test_case.dart 102:27               _run.<fn>
  dart:async/zone.dart 842                                 _rootRunUnary
  dart:async/zone.dart 748                                 _CustomZone.runUnary
  dart:async/future_impl.dart 488                          _Future._propagateToListeners.handleValueCallback
  dart:async/future_impl.dart 571                          _Future._propagateToListeners
  dart:async/future_impl.dart 331                          _Future._completeWithValue
  dart:async/future_impl.dart 393                          _Future._asyncComplete.<fn>
  dart:async/zone.dart 835                                 _rootRun
  dart:async/zone.dart 740                                 _CustomZone.run
  dart:async/zone.dart 648                                 _CustomZone.runGuarded
  dart:async/zone.dart 673                                 _CustomZone.bindCallback.<fn>
  dart:async/schedule_microtask.dart 23                    _asyncRunCallbackLoop
  dart:async/schedule_microtask.dart 32                    _asyncRunCallback
  dart:isolate-patch/isolate_patch.dart 126                _RawReceivePortImpl._handleMessage
...
325 PASSED, 0 FAILED, 1 ERRORS
Unhandled exception:
Exception: Some tests failed.
#0      SimpleConfiguration.onDone (package:unittest/src/simple_configuration.dart:189:9)
#1      _completeTests (package:unittest/unittest.dart:487:17)
#2      _runTest (package:unittest/unittest.dart:436:19)
#3      _nextTestCase (package:unittest/unittest.dart:376:11)
#4      _asyncRunCallbackLoop (dart:async/schedule_microtask.dart:23)
#5      _asyncRunCallback (dart:async/schedule_microtask.dart:32)
#6      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:126)
 
Communicating with the analysis server

The analysis server expects to communicate with its client using standard input and output.  Each line that is received on standard input is interpreted as a JSON request, and each line that it sends on standard output is either a JSON response to a specific request or a JSON notification.  (Note that unlike a typical use of JSON, line breaks are not permitted within a single JSON request--each request must be contained on a single line).

Out of interest, do you intend to support other channels of communication? I ask because I notice WebSocket related references in http_server.dart et al. But then I see your point on security below?
 
The detailed structure of the JSON requests, responses, and notifications hasn't quite been settled on yet, but I'll walk through an example to give you an idea of what to expect, and then I'll follow up with some pointers to where to get more information.  Feel free to ask questions on this list!

How to find out what's implemented now

I generated the above example by going to the file pkg/analysis_server/test/integration/analysis_error_inttest.dart, adding the line "debugStdio();" to the test_detect_simple_error() method, and running the test.  You can do this to any of the integration tests in pkg/analysis_server/test/integration to see what messages are exchanged between client and server.  Until we are ready to publish the analysis server spec, this is probably one of your best sources of information about how to use the analysis server.  New tests are being added all the time, so check back frequently.

You can also look at the implementation of the analysis server (in particular the files pkg/analysis_server/lib/src/domain_*.dart should give you an idea of what other requests are currently supported).  Note that these files refer to constants defined in pkg/analysis_server/lib/src/constants.dart.

Is there a continuous integration of the tests up and running somewhere public? I ask because I'm unable to get the integration tests running successfully on my local machine. Not looked into it very closely, but having a reference would help:

./test/test_all.dart  # OK
./test/computer/test_all.dart  # OK
./test/operation/test_all.dart  # OK
./test/integration/test_all.dart  # ** HANGS **
./test/search/test_all.dart  # OK
 
What's planned to be implemented

We are planning to entirely replace the analysis engine used by the Dart Editor with the analysis server.  So nearly all the features in the Dart Editor should eventually be available, in one way or another, through the analysis server.  This includes things like: computing errors/warnings/hints, automated refactoring, "quick fixes", computing the outline for a file, computing a class hierarchy, computing pop-up information about an identifier, jumping from an identifier to its definition, and searching for all usages of a function, method, or class.  In addition, the analysis server will be able to deal with files that are modified in the editor but haven't yet been saved to disk.  Most of the code to support these features has already been written (after all, these features currently work in the dart editor)--we just need to wire up the protocol so that they can be invoked from the analysis server.

This sounds great. Look forward to the wider specification document as and when that becomes available for review. Indeed given my, what must be, somewhat naive questions, the sooner the better!
 
Caveats

The protocol is still in flux, which means that the example in this email, and anything you learn from reading the code, is subject to change.  If you spot something that seems wrong, or you have a suggestion or a question about how something works, please feel free to ask!

See below with some initial thoughts about how I see the Vim implementation working.  

Additional random information

Paul Jolly asked me some questions in an email before he joined this mailing list.

Thanks for these various answers. Again, please see below.
 
I hope we can make this work for Vim.  If it's a problem, please let us know and we can brainstorm another technique.  However I'm hoping it doesn't come to that; I'd rather not support multiple ways of doing things unless we have to.

I hope we can make it work for Vim too!

The main issue we have to contend with is that Vim is single threaded and doesn't have the concept of callbacks (at least until NeoVim lands and becomes the default). Vim interacts with external commands via 'system' calls which are blocking. To my knowledge (if other Vim users can correct me here please do) Vim cannot therefore control an instance of the analysis server. (Siggi - apologies, this may have been the point you were referring to off list). Hence my question about whether the analysis server would operate as a daemon. With such a daemon, Vim would then communicate with the analysis server via a (yet to be written) client program which would take various command line arguments (supplied by Vim) and translate these into a server request. The client program would block awaiting the response, at which point control would return to the Vim user with the (translated) response.

For example, consider a completion request. In this toy example, code lives in /home/myitcv/test_compl such that:

$ cd /home/myitcv/test_compl
$ find -type f
./bin/test_compl.dart
./pubspec.yaml

Consider the following sequence of events:
  • <assume the analysis server is already running as a daemon process and that we have some means of communicating with it using a client program analysis_client>
  • I open the dart file for editing: vi bin/test_compl.dart
  • Vim detects we are editing a dart file and furthermore finds a pubspec.yaml file in the current directory. It invokes: analysis_client -m analysis.setAnalysisRoots /home/myitcv/test_compl
  • That call blocks until the analysis server responds to the client, at which point control returns to the user
  • I then edit the contents, leaving the cursor in the position shown by '^' below:
void main() {
  List<int> l = new List<int>();
  l.^
}
  • At this stage, the buffer (file) is unwritten; hence the bin/test_compl.dart is zero length
  • With the cursor in the position shown by '^', I initiate a an omnicomplete request via i_CTRL-X_CTRL-O (meaning, in insert mode, hold down Ctrl followed by x followed by o)
  • This triggers the (as yet unwritten) analysis_client completion plugin
  • We first detect the buffer is unwritten. We write the contents of the buffer to a temporary file, /tmp/tmp.A7nOOeubHG. Vim then invokes: analysis_client -m analysis.updateContent /tmp/tmp.A7nOOeubHG bin/test_compl.dart 
  • Again, Vim is blocked until the server responds (via our analysis_client call)
  • When we get a response back (assume success for now), Vim then immediately initiates the completion request: analysis_client -m completion.getSuggestions '{"offset":50, "file": "/home/myitcv/test_compl/bin/test_compl.dart"}'
  • Again, Vim is blocked until the server responds (via our analysis_client call)
  • The server response is returned to Vim, at which point the completion menu can be shown with the various options

Number of editor instances
This leads onto the working assumption on the number of editor instances (Vim in this case). I personally have multiple editors (terminal instances of Vim) open across multiple virtual screens at any one time. Having one analysis server instance per editor would seem to be too heavyweight for that sort of setup, not least because of the startup time of each server. Again, this was one of the thoughts behind having a single daemon process per user.

I understand the sentiment behind your security comment ("we didn't want to have to worry about the security implications of opening up a socket that multiple clients could connect to"). Indeed not to challenge its accuracy, but is this point perhaps slightly overdone? For example, I am creating a web app on my local machine, have loads of sockets open as a side effect of testing my web app, information leaking all over the place. Isn't it down to me to control this environment? Could we therefore not ship the analysis server with a similar health warning?

Communication interface
This then brings me onto the question of the communication interface exposed by the analysis server. If we are limited to stdin/stdout, this is going to make things quite messy if we do try for a single daemon instance shared between multiple editor instances. We could conceivably do something with named pipes... but I haven't thought through the full implications of that (suspect it may require another server process that keeps those named pipes open and accepts requests via another channel, RPC or similar... already this is sounding quite ugly)

Setting analysis root
One question (of many) that arises out of this example is how to determine the analysis root for an ad hoc edited file. Considering the completion example from above, I commented that Vim set the analysis root based on the existence of a pubspec.yaml file - this was an assumption. If instead we started editing test_compl.dart from within the bin/ directory, would it make sense for Vim to recursively search the current and then parent directories until it finds a pubspec.yaml file, and then use the directory containing the pubspec.yaml file as the analysis root?

With due apologies for the probably basic questions, and thanks again for this project.


Paul

Paul Berry

unread,
Jul 21, 2014, 7:43:53 PM7/21/14
to analyzer...@dartlang.org
On 18 July 2014 20:48, Paul Berry <paul...@google.com> wrote:
The analysis server depends on a number of other packages.  You can either get these packages by running "pub get" from the analysis_server directory, or...

UPDATE!  I was wrong about this.  Unfortunately, running "pub get" from the analysis_server directory will get you the latest published versions of the packages that the analysis server depends on.  However, since you are running the bleeding edge version of the analysis server, it may depend on features in those dependent packages that haven't been published yet (in particular, at the moment, it depends on unpublished features in the "analysis_services" package).  So this won't work.

Here's what you can do, in increasing order of difficulty:

1. Use a snapshot of the Dart analysis server, which I'm attaching to this message.  The only command line option it needs is the path to the SDK, supplied with the "--sdk=" option.  (Eventually there will be a part of the analysis server protocol to specify this path, but that's not implemented yet.)  So for example I'm running it like this:

$ ./dart snapshots/analysis_server.dart.snapshot --sdk=/Users/paulberry/dart/dart-repo/dart/sdk

Note that the "--sdk" option is new as of today.  If you don't use it, the analysis server will try to guess where the SDK is based on the directory it's launched from, but its guess isn't very good.

I'll try to publish updates to the snapshot periodically to this list so that nobody has to fall too far behind.  Please feel free to remind me if I forget.


2. Make local edits to the analysis server's pubspec.yaml file to point it to a local copy of the packages it depends on, rather than the published copies.  So, for example, to fix the dependency on analysis_services, change pubspec.yaml so that instead of saying:

...
dependencies:
  analysis_services: '>=0.4.0 <0.5.0'
  analyzer: '>=0.22.0-dev <0.23.0'
...

it says:

...
dependencies:
  analysis_services:
    path: ../analysis_services
  analyzer: '>=0.22.0-dev <0.23.0'
...

Then re-run "pub get" in the analysis_server directory, and you should be good to go.  Running the server now looks like this:

$ ./dart /Users/paulberry/dart/dart-repo/dart/pkg/analysis_server/bin/server.dart --sdk=/Users/paulberry/dart/dart-repo/dart/sdk

Note that in the future there may be dependencies other than analysis_services that you have to update in this way.

The advantage of this over the previous alternative is that you can stay up to date with the bleeding edge code without having to wait for me to publish a snapshot.


3. If you happen to have a full local build of the dart SDK, then the other technique I mentioned in my previous message (point your package root at its "packages" directory) should still work.  As a reminder, that looks something like this:

$ sdk/bin/dart --package-root=xcodebuild/ReleaseIA32/packages pkg/analysis_server/bin/server.dart --sdk=/Users/paulberry/dart/dart-repo/dart/sdk

Unfortunately, this is the highest difficulty option since you need to be able to build the SDK yourself before you can do it.


Let us know if you run into any trouble. 

Paul
analysis_server.dart.snapshot

Paul Berry

unread,
Jul 21, 2014, 8:06:40 PM7/21/14
to analyzer...@dartlang.org
Thanks for the stack trace.  This is similar to what I saw.  I've implemented the "--sdk=" option (see my other email on this thread, from a few minutes ago), which should let you work around this problem by specifying the exact location of the sdk.  In the long run you'll specify the location of the sdk using the "analysis.updateSdks" request, but that part of the API isn't implemented yet, so the "--sdk=" option is to hold us over until then.

 
Communicating with the analysis server

The analysis server expects to communicate with its client using standard input and output.  Each line that is received on standard input is interpreted as a JSON request, and each line that it sends on standard output is either a JSON response to a specific request or a JSON notification.  (Note that unlike a typical use of JSON, line breaks are not permitted within a single JSON request--each request must be contained on a single line).

Out of interest, do you intend to support other channels of communication? I ask because I notice WebSocket related references in http_server.dart et al. But then I see your point on security below?

I was hoping not to, but your comments about (a) vim being single threaded and lacking callbacks, and (b) wanting to have multiple instances of vim which all talk to the same analysis server, are making me wonder if this is a tenable plan.  I'll try to do some research and get back to you.

 
 
The detailed structure of the JSON requests, responses, and notifications hasn't quite been settled on yet, but I'll walk through an example to give you an idea of what to expect, and then I'll follow up with some pointers to where to get more information.  Feel free to ask questions on this list!

How to find out what's implemented now

I generated the above example by going to the file pkg/analysis_server/test/integration/analysis_error_inttest.dart, adding the line "debugStdio();" to the test_detect_simple_error() method, and running the test.  You can do this to any of the integration tests in pkg/analysis_server/test/integration to see what messages are exchanged between client and server.  Until we are ready to publish the analysis server spec, this is probably one of your best sources of information about how to use the analysis server.  New tests are being added all the time, so check back frequently.

You can also look at the implementation of the analysis server (in particular the files pkg/analysis_server/lib/src/domain_*.dart should give you an idea of what other requests are currently supported).  Note that these files refer to constants defined in pkg/analysis_server/lib/src/constants.dart.

Is there a continuous integration of the tests up and running somewhere public? I ask because I'm unable to get the integration tests running successfully on my local machine. Not looked into it very closely, but having a reference would help:

./test/test_all.dart  # OK
./test/computer/test_all.dart  # OK
./test/operation/test_all.dart  # OK
./test/integration/test_all.dart  # ** HANGS **
./test/search/test_all.dart  # OK
 

This is concerning.  Unfortunately we don't have a buildbot running the integration tests yet, and in fact we haven't even tried running the integration tests on anything but MacOS yet, so it's possible that you've found a bug.  Here's what I would recommend trying:

- First make sure that you can run the analysis server manually from the command line in the way the integration tests run it.  The integration tests run it from the "pkg/analysis_server/test/integration" folder, and the dart file they execute is pkg/analysis_server/bin/server.dart*.  They don't supply an "--sdk=" argument**.  This means that you'll need to use technique 2 from my email a few minutes ago.  If you can do this from a terminal and you get the familiar '{"event:"server.connected"}' message, then in theory the integration tests should work.

- Run the integration test files ("./test/integration/*_inttest.dart") individually to see if they all hang or just one of them.

- Let the integration tests run until they time out and see which ones are failing.  (Timeout is 2 minutes per test, and some of the *_inttest.dart files have several tests in them, so you'll have to be patient).

- Find one of the failing tests and insert the statement "debugStdio();" into it.  This might give us some clues as to where the test is stalling.

*Soon I hope to expand the integration tests so that they can work with any of the 3 techniques I described in my previous email (using a snapshot, running server.dart directly using a tweaked pubspec.yaml file, and running server.dart directly using a package root from the locally built sdk).

Of course, if you just want to wait until we've gotten our buildbots running, that's totally understandable :)
As I said above, I need to do some more research about these questions.  I'll get back to you as soon as I can.

 

Setting analysis root
One question (of many) that arises out of this example is how to determine the analysis root for an ad hoc edited file. Considering the completion example from above, I commented that Vim set the analysis root based on the existence of a pubspec.yaml file - this was an assumption. If instead we started editing test_compl.dart from within the bin/ directory, would it make sense for Vim to recursively search the current and then parent directories until it finds a pubspec.yaml file, and then use the directory containing the pubspec.yaml file as the analysis root?

Our intention is that you won't have to implement this recursive search algorithm.  If you specify an individual file (rather than a directory) as an analysis root, the server will do exactly the search you described, and then use the directory containing the pubspec.yaml file as the starting point for analysis.

Unfortunately, that feature isn't implemented yet.  As you might guess we've got rather a large to-do list and it hasn't bubbled to the top yet :)

Paul

Paul Jolly

unread,
Jul 22, 2014, 4:27:18 AM7/22/14
to analyzer...@dartlang.org
3. If you happen to have a full local build of the dart SDK, then the other technique I mentioned in my previous message (point your package root at its "packages" directory) should still work.  As a reminder, that looks something like this:

$ sdk/bin/dart --package-root=xcodebuild/ReleaseIA32/packages pkg/analysis_server/bin/server.dart --sdk=/Users/paulberry/dart/dart-repo/dart/sdk

Unfortunately, this is the highest difficulty option since you need to be able to build the SDK yourself before you can do it.

Thanks.

Chose option 3 which, in the long term, will definitely be the easiest. 

To pull together the various links I used to achieve this on Ubuntu 14.10 (utopic):
  • Follow manual Linux instructions on PreparingYourMachine from point 1 which starts "If using 64-bit Ubuntu...". This basically amounted to:
sudo apt-get install libc6-dev-i386 g++-multilib
cd $HOME/usr # this is where I want depot_tools
svn co http://src.chromium.org/svn/trunk/tools/depot_tools
# add $HOME/usr/depot_tools to PATH

Paul Jolly

unread,
Jul 22, 2014, 5:16:14 AM7/22/14
to analyzer...@dartlang.org
Thanks for the stack trace.  This is similar to what I saw.  I've implemented the "--sdk=" option (see my other email on this thread, from a few minutes ago), which should let you work around this problem by specifying the exact location of the sdk.  In the long run you'll specify the location of the sdk using the "analysis.updateSdks" request, but that part of the API isn't implemented yet, so the "--sdk=" option is to hold us over until then.

I have this up and running from bleeding_edge so will shout it there's any issues.

Communicating with the analysis server

The analysis server expects to communicate with its client using standard input and output.  Each line that is received on standard input is interpreted as a JSON request, and each line that it sends on standard output is either a JSON response to a specific request or a JSON notification.  (Note that unlike a typical use of JSON, line breaks are not permitted within a single JSON request--each request must be contained on a single line).

Out of interest, do you intend to support other channels of communication? I ask because I notice WebSocket related references in http_server.dart et al. But then I see your point on security below?

I was hoping not to, but your comments about (a) vim being single threaded and lacking callbacks, and (b) wanting to have multiple instances of vim which all talk to the same analysis server, are making me wonder if this is a tenable plan.  I'll try to do some research and get back to you.

Much appreciated. 

The detailed structure of the JSON requests, responses, and notifications hasn't quite been settled on yet, but I'll walk through an example to give you an idea of what to expect, and then I'll follow up with some pointers to where to get more information.  Feel free to ask questions on this list!

How to find out what's implemented now

I generated the above example by going to the file pkg/analysis_server/test/integration/analysis_error_inttest.dart, adding the line "debugStdio();" to the test_detect_simple_error() method, and running the test.  You can do this to any of the integration tests in pkg/analysis_server/test/integration to see what messages are exchanged between client and server.  Until we are ready to publish the analysis server spec, this is probably one of your best sources of information about how to use the analysis server.  New tests are being added all the time, so check back frequently.

You can also look at the implementation of the analysis server (in particular the files pkg/analysis_server/lib/src/domain_*.dart should give you an idea of what other requests are currently supported).  Note that these files refer to constants defined in pkg/analysis_server/lib/src/constants.dart.

Is there a continuous integration of the tests up and running somewhere public? I ask because I'm unable to get the integration tests running successfully on my local machine. Not looked into it very closely, but having a reference would help:

./test/test_all.dart  # OK
./test/computer/test_all.dart  # OK
./test/operation/test_all.dart  # OK
./test/integration/test_all.dart  # ** HANGS **
./test/search/test_all.dart  # OK
 

This is concerning.  Unfortunately we don't have a buildbot running the integration tests yet, and in fact we haven't even tried running the integration tests on anything but MacOS yet, so it's possible that you've found a bug.  Here's what I would recommend trying:

- First make sure that you can run the analysis server manually from the command line in the way the integration tests run it.  The integration tests run it from the "pkg/analysis_server/test/integration" folder, and the dart file they execute is pkg/analysis_server/bin/server.dart*.  They don't supply an "--sdk=" argument**.  This means that you'll need to use technique 2 from my email a few minutes ago.  If you can do this from a terminal and you get the familiar '{"event:"server.connected"}' message, then in theory the integration tests should work.

Using the debugStdio() statement I've tracked down the issue:
$ /home/myitcv/dev/dart/bleeding_edge/dart/sdk/bin/dart --package-root=/home/myitcv/dev/dart/bleeding_edge/dart/out/ReleaseX64/packages test_all.dart
unittest-suite-wait-for-done
SEND: {"id":"0","method":"analysis.setAnalysisRoots","params":{"included":["/tmp/analysisServerawL3Rv"],"excluded":[]}}
RECV: Uncaught Error: FileSystemException: Cannot open file, path = '/home/myitcv/dev/dart/bleeding_edge/dart/pkg/analysis_server/bin/packages/analysis_server/driver.dart' (OS Error: No such file or directory, errno = 2)
RECV: Stack Trace:
RECV: #0      _File.open.<anonymous closure> (dart:io/file_impl.dart:349)
RECV: #1      _RootZone.runUnary (dart:async/zone.dart:1088)
RECV: #2      _Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:488)
RECV: #3      _Future._propagateToListeners (dart:async/future_impl.dart:571)
RECV: #4      _Future._completeWithValue (dart:async/future_impl.dart:331)
RECV: #5      _Future._asyncComplete.<anonymous closure> (dart:async/future_impl.dart:393)
RECV: #6      _asyncRunCallbackLoop (dart:async/schedule_microtask.dart:23)
RECV: #7      _asyncRunCallback (dart:async/schedule_microtask.dart:32)
RECV: #8      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:126)
RECV:
RECV:
Left to run, this will then timeout after 120 seconds. Is there a way of detecting the uncaught error and aborting the test?

In any case, applying option 2 (on top of 3) then fixed the issue:
$ cat pubspec.yaml | grep analysis_services
  analysis_services:
    path: ../analysis_services
$ pwd
/home/myitcv/dev/dart/bleeding_edge/dart/pkg/analysis_server/test/integration
$ /home/myitcv/dev/dart/bleeding_edge/dart/sdk/bin/dart test_all.dart
unittest-suite-wait-for-done
PASS: analysis_server_integration | getHover
PASS: analysis_server_integration | getHover_noInfo
PASS: analysis_server_integration | detect_simple_error
PASS: analysis_server_integration | getVersion
PASS: analysis_server_integration | setSubscriptions_invalidService
PASS: analysis_server_integration | connected
PASS: analysis_server_integration | error
PASS: analysis_server_integration | status

All 8 tests passed.
unittest-suite-success
Of course, if you just want to wait until we've gotten our buildbots running, that's totally understandable :)

Indeed! I'm a big fan of buildbots; great for transparency but more than anything a great reference point for people like me trying to get up and running.

As I said above, I need to do some more research about these questions.  I'll get back to you as soon as I can.

Thanks. Happy to bounce some ideas around off-list if that's less noisy for others on the list and then come back with a proposal. 

Setting analysis root
One question (of many) that arises out of this example is how to determine the analysis root for an ad hoc edited file. Considering the completion example from above, I commented that Vim set the analysis root based on the existence of a pubspec.yaml file - this was an assumption. If instead we started editing test_compl.dart from within the bin/ directory, would it make sense for Vim to recursively search the current and then parent directories until it finds a pubspec.yaml file, and then use the directory containing the pubspec.yaml file as the analysis root?

Our intention is that you won't have to implement this recursive search algorithm.  If you specify an individual file (rather than a directory) as an analysis root, the server will do exactly the search you described, and then use the directory containing the pubspec.yaml file as the starting point for analysis.

Unfortunately, that feature isn't implemented yet.  As you might guess we've got rather a large to-do list and it hasn't bubbled to the top yet :)

Great news.

Thanks again

Siggi Cherem

unread,
Jul 22, 2014, 10:34:36 AM7/22/14
to analyzer...@dartlang.org
Yes, exactly - my understanding is that if we are willing to use python, rather than vimscript, then we can actually do asynchronous calls without a problem. This would require that users have vim with +python support, but that is probably OK.  We could then start the analysis server without necessarily having a daemon.

--
You received this message because you are subscribed to the Google Groups "Dart Analyzer Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to analyzer-discu...@dartlang.org.
Visit this group at http://groups.google.com/a/dartlang.org/group/analyzer-discuss/.

Paul Jolly

unread,
Jul 22, 2014, 11:01:35 AM7/22/14
to analyzer...@dartlang.org
The main issue we have to contend with is that Vim is single threaded and doesn't have the concept of callbacks (at least until NeoVim lands and becomes the default). Vim interacts with external commands via 'system' calls which are blocking. To my knowledge (if other Vim users can correct me here please do) Vim cannot therefore control an instance of the analysis server. (Siggi - apologies, this may have been the point you were referring to off list).

Yes, exactly - my understanding is that if we are willing to use python, rather than vimscript, then we can actually do asynchronous calls without a problem. This would require that users have vim with +python support, but that is probably OK.  We could then start the analysis server without necessarily having a daemon.

Do you have an example of a plugin where this works (reliably)? 

Worth mentioning eclim - an alternative means of solving the Vim problem, but would require the user to install all the requirements for the DartEditor (which would be rather self-defeating, given we're trying to provide standalone Vim support)

Siggi Cherem

unread,
Jul 22, 2014, 11:25:17 AM7/22/14
to analyzer...@dartlang.org
Other examples worth mentioning: 
 -  YouCompleteMe (YCM): we talked briefly off-list, but related to these points above it might be worth highlighting thay YCM itself is written with a client-server architecture and they will start their own server automatically. They also may start servers that support completions for specific languages. For example, I believe that's how they launch OmniSharp-server to support completion for C# (and I believe they start one per vim session in that case). It might be that most of the architecture we need is already there, so I'd consider requiring YCM for our vim plugin.

 - It might be worth looking also a bit into what the OmniSharp-client does. They seem to suggest using vim-dispatch for launching the server automatically, but otherwise they suggest that you start the server from the commandline and just connect to it from the client.

Paul Berry

unread,
Jul 22, 2014, 5:42:55 PM7/22/14
to analyzer...@dartlang.org
Yes.  I need to add some expectAsync() calls to the integration test helper functions to make sure the error is propagated all the way up and aborts the test.  It looks like this has also exposed a latent bug in the unittest module.  I think I can work around it but I've filed a bug anyway so that we can fix it for other clients of unittest (see dartbug.com/20153).

Thanks for taking the time to track this down.

Paul
Reply all
Reply to author
Forward
0 new messages