Wednesday, June 19, 2013

Dart Breaking Change (But I Already Knew That)

‹prev | My Chain | next›

I received an email notification this afternoon that the ICE Code Editor build had failed. Since I had not committed anything today, this meant one of two things: there was a new version of Dart or I have to hunt down one of my project collaborators. Happily my project collaborators are safe.

It is pretty awesome how runs builds when there are new versions of Dart. Back in the day you were pretty much guaranteed that the build would break every time Dart was updated, but this happens less and less frequently now that the core language has stabilized. So what monumental shift occurred in the language today?

It seems that Dart is now a little more fussy about return values. I really like the hash rocket function return syntactic sugar in Dart. I like it so much that I will use it even when not returning a value:
  bool _frozen;
  /// Prevent further syncs to localStorage
  void freeze()=> _frozen = true;
It seems that Dart will no longer let me get away with that as the dartanalyzer now tells me:
[warning] The return type 'bool' is not a 'void', as defined by the method 'freeze' (ice-code-editor/lib/store.dart, line 139, col 19)

As woes go, this is does not amount to much as I can simply use the curly brace function syntax:
  bool _frozen;
  /// Prevent further syncs to localStorage
  void freeze() { _frozen = true; }
Problem solved.

Since there is a new version of Dart out, I take this as an opportunity to verify that everything works fine—even the package dependencies. We recently uploaded ICE to the Dart Pub package repository. The pub tool strongly recommended that we lock the dependencies in the package's pubspec.yaml:
name: ice_code_editor
version: 0.0.1
description: Code Editor + Preview
- Chris Strom <>
- Santiago Arias <>
- Srdjan Pejic
  unittest: ">=0.5.13 <0.5.14"
  js: ">=0.0.22 <0.0.23"
  crypto: ">=0.5.13 <0.5.14"
To grab the latest versions of these dependencies, I remove all version constraints for dependencies:
name: ice_code_editor
# ...
  unittest: any
  js: any
  crypto: any
Then I pub update to grab the latest versions of these dependencies:
➜  ice-code-editor git:(master) pub update
Resolving dependencies........
Downloading browser 0.5.20 from hosted...
Downloading crypto 0.5.20 from hosted...
Downloading meta 0.5.20 from hosted...
Downloading unittest 0.5.20 from hosted...
Dependencies updated!
With that, I am ready to run my test suite:
➜  ice-code-editor git:(master) ✗ ./test/ 
Analyzing lib/ice.dart...
No issues found.
[16440:16440:0619/] Running without the SUID sandbox! See for more information on developing with the sandbox on.

I should investigate that error someday, but today I am a little more concerned that my test suite never returns. It just hangs there.

So I try the main test file in the browser and find:
Exception: The null object does not have a method 'callSync'.

NoSuchMethodError : method not found: 'callSync'
Receiver: null
Arguments: [GrowableObjectArray len:0] dart:core-patch/object_patch.dart:19
Object.noSuchMethod dart:core-patch/object_patch.dart:19
_enterScope package:js/js.dart:749
_enterScopeIfNeeded package:js/js.dart:728
context package:js/js.dart:717
Editor._startJsAce package:ice_code_editor/editor.dart:218
Editor._startAce.<anonymous closure>
Aha! That rings a bell. Or two. Or 10.

The strong presence of the JavaScript package in this stack trace reminds my of an announcement on the dart mailing list about a change to Dart's JavaScript code. Not only did I see that announcement on the mailing list, but one of my #pairwithme pairs specifically told me about it as well. And yet I forgot anyway.

Good thing there is continuous integration to catch these things.

The announcement was to inform everyone in the community that the browser package's dart.js was undergoing significant change. Specifically, the maintainers removed a lot of code that was in there solely to support js-interop. Since most Dart code does not need to inter-operate with JavaScript, there is no sense in making pure Dart code pay js-interop code evaluation penalties.

For packages that do need js-interop, like ICE, there a simple solution. I need to add a second <script> tag to the HTML pages that hold ICE. This includes the testing context page:
  <script type="application/dart" src="ice_test.dart"></script>
  <script type='text/javascript'>
    // Custom code to notify the test runner when the build is complete...
  <script src="packages/browser/dart.js"></script>
  <script src="packages/browser/interop.js"></script>
I make that change everywhere else in the project (example pages, other test contexts, etc.) and that solves my problem. I again have a test suite with 115 passing tests and no complaints from dartanalyzer.

But what to do about the pubspec.yaml? I do not think that I have a choice but to re-introduce the version constraints. If I try to pub lish to Dart Pub, the pub command will whine noisily that I should have version constraints:
* Your dependency on "crypto" should have a version constraint. For example:
    crypto: ">=0.5.20 <0.5.21"
I am unsure that I understand why I am not including my pubspec.lock file in source code control or the pub package when I end up doing the same work (only less pretty) in my pubspec.yaml:
name: ice_code_editor
# ...
  unittest: ">=0.5.20 <0.5.21"
  js: ">=0.0.22 <0.0.23"
  crypto: ">=0.5.20 <0.5.21"
I do not see that I have a choice at the moment (other than to put up with pub nagging me). The documentation says that I should not include pubspec.lock for a library like ICE, so I add the new version constraints as above and call it a day.

In the end, I really could have saved myself some work had I acted immediately on the ample warning that I had about this change. That said, I am grateful to have around (not to mention a solid test suite) to catch these kinds of things.

Day #787

No comments:

Post a Comment