Can compile and run syncbase on Android.

... But it doesn't seem to do anything.  I can't get any output, and the
Dart client crashes with "Proxy closed" error.

The echo client/server runs properly on Android, but I can't see any
output from the server, only from the client.

The tests still pass on Linux.  They take longer to run because the
files are served via HTTP now and not directly from the filesystem.  I
bumped the syncbase test timeout to accomodate this.

Change-Id: Ib3e273f5a5b63c747b2c4debaf2198cf535a1fe2
diff --git a/Makefile b/Makefile
index 8922652..7937733 100644
--- a/Makefile
+++ b/Makefile
@@ -8,6 +8,12 @@
 # Flags for Syncbase service running as Mojo service.
 # See v.io/x/ref/runtime/internal/mojo_util.go for the wonderful magic that
 # makes this work.
+# NOTE(nlacasse): Switch to --alsologtostderr=true to see syncbased logs in
+# mojo_shell stderr.
+# TODO(nlacasse): Passing these values in an environment variable won't work on
+# Android.  Consider passing these values as url params when loading the
+# service.  (Does that work?  Follow up with Mojons.)  Another possilibity is
+# to load the values from a configuration file.
 SYNCBASED_ADDR := 127.0.0.1:4002
 V23_MOJO_FLAGS := --v=5 --alsologtostderr=false --root-dir=/tmp/syncbase_mojo --v23.tcp.address=$(SYNCBASED_ADDR) --v23.permissions.literal={"Admin":{"In":["..."]},"Write":{"In":["..."]},"Read":{"In":["..."]},"Resolve":{"In":["..."]},"Debug":{"In":["..."]}} --v23.credentials=$(V23_ROOT)/experimental/projects/ether/creds
 
@@ -28,76 +34,92 @@
 	$(error V23_ROOT is not set)
 endif
 
-# NOTE(nlacasse): Running Go Mojo services requires passing the
-# --enable-multiprocess flag to mojo_shell.  This is because the Go runtime is
-#  very large, and can interfere with C++ memory if they are in the same
-#  process.
-MOJO_SHELL_FLAGS := -v --enable-multiprocess
-
 ifdef ANDROID
-	# TODO(nlacasse): Serve mojo resources over HTTP so they are accessible to
-	# Android.  Currently everything is served off the local filesystem, which
-	# will not work on Android.  Until then, Android support is disabled.
-	$(error Android is currently not supported.)
-
 	# Configure compiler and linker for Android.
-	GO_BIN := $(MOJO_DIR)/src/third_party/go/tool/android_arm/bin/go
-	GO_FLAGS := -tags=mojo -ldflags=-shared
+	export GOROOT := $(MOJO_DIR)/src/third_party/go/tool/android_arm
+	export CGO_ENABLED := 1
+	export GOOS := android
+	export GOARCH := arm
+	export GOARM := 7
+
+	ANDROID_NDK := $(V23_ROOT)/third_party/android/ndk-toolchain
+
+	ifeq ($(wildcard $(ANDROID_NDK)),)
+		$(error ERROR: $(ANDROID_NDK) does not exist.  Please install android profile with "v23 profile install android")
+	endif
+
+	export CC := $(ANDROID_NDK)/bin/arm-linux-androideabi-gcc
+	export CXX := $(ANDROID_NDK)/bin/arm-linux-androideabi-g++
 
 	MOJO_ANDROID_FLAGS := --android
 	MOJO_BUILD_DIR := $(MOJO_DIR)/src/out/android_Debug
 	MOJO_SHARED_LIB := $(PWD)/gen/lib/android/libsystem_thunk.a
 
-	SYNCBASE_LEVELDB_DIR := $(V23_ROOT)/third_party/cout/linux_amd64/leveldb
-	SYNCBASE_SNAPPY_DIR := $(V23_ROOT)/third_party/cout/linux_amd64/snappy
+	ETHER_BUILD_DIR := $(PWD)/gen/mojo/android
+
+	LEVELDB_OUT_DIR := $(PWD)/gen/lib/android/leveldb
+	SNAPPY_OUT_DIR := $(PWD)/gen/lib/android/snappy
 else
 	# Configure compiler and linker for Linux.
-	GO_BIN := $(MOJO_DIR)/src/third_party/go/tool/linux_amd64/bin/go
-	GO_FLAGS := -tags=mojo -ldflags=-shared -buildmode=c-shared
+	export GOROOT := $(MOJO_DIR)/src/third_party/go/tool/linux_amd64
 
 	MOJO_BUILD_DIR := $(MOJO_DIR)/src/out/Debug
 	MOJO_SHARED_LIB := $(PWD)/gen/lib/linux_amd64/libsystem_thunk.a
 
-	# TODO(nlacasse): Build leveldb and snappy libs for android, and put the
-	# correct paths below.
-	SYNCBASE_LEVELDB_DIR := $(V23_ROOT)/third_party/cout/linux_amd64/leveldb
-	SYNCBASE_SNAPPY_DIR := $(V23_ROOT)/third_party/cout/linux_amd64/snappy
+	ETHER_BUILD_DIR := $(PWD)/gen/mojo/linux_amd64
+
+	LEVELDB_OUT_DIR := $(V23_ROOT)/third_party/cout/linux_amd64/leveldb
+	SNAPPY_OUT_DIR := $(V23_ROOT)/third_party/cout/linux_amd64/snappy
 endif
 
-# Compiles a Go program and links against the Mojo C shared library.
+GOPATH := $(V23_GOPATH):$(MOJO_DIR):$(MOJO_DIR)/third_party/go:$(MOJO_BUILD_DIR)/gen/go:$(PWD)/gen/go
+CGO_CFLAGS := -I$(MOJO_DIR)/src
+CGO_CXXFLAGS := -I$(MOJO_DIR)/src
+CGO_LDFLAGS := -L$(dir $(MOJO_SHARED_LIB)) -lsystem_thunk
+
+# NOTE(nlacasse): Running Go Mojo services requires passing the
+# --enable-multiprocess flag to mojo_shell.  This is because the Go runtime is
+# very large, and can interfere with C++ memory if they are in the same
+# process.
+MOJO_SHELL_FLAGS := -v --enable-multiprocess \
+	--config-file $(PWD)/mojoconfig \
+	--config-alias ETHER_DIR=$(PWD) \
+	--config-alias ETHER_BUILD_DIR=$(ETHER_BUILD_DIR)
+
+ifeq ($(wildcard $(MOJO_BUILD_DIR)),)
+	$(error ERROR: $(MOJO_BUILD_DIR) does not exist.  Please see README.md for instructions on compiling Mojo resources.)
+endif
+
+LEVELDB_SRC_DIR := $(V23_ROOT)/third_party/csrc/leveldb
+ifeq ($(wildcard $(LEVELDB_SRC_DIR)),)
+	$(error ERROR: $(LEVELDB_SRC_DIR) does not exist.  Please install syncbase profile with "v23 profile install syncbase")
+endif
+
+SNAPPY_SRC_DIR := $(V23_ROOT)/third_party/csrc/snappy-1.1.2
+ifeq ($(wildcard $(SNAPPY_SRC_DIR)),)
+	$(error ERROR: $(SNAPPY_SRC_DIR) does not exist.  Please install syncbase profile with "v23 profile install syncbase")
+endif
+
+# Compiles a Go program and links against the Mojo C library.
 # $1 is input filename.
 # $2 is output filename.
-# See $(MOJO_DIR)/mojo/go/go.py for description of arguments to go.py (aka
-# MOGO_BIN).
-#
-# MOJO_GOPATH must be exported so it can be picked up by MOGO_BIN.
-export MOJO_GOPATH := $(V23_GOPATH):$(PWD)/gen/go:$(PWD)/go:$(MOJO_BUILD_DIR)/gen/go
-# TODO(nlacasse): It's strange that go.py takes CGO_CFLAGS and CGO_LDFLAGS as
-# arguments, but other cgo settings (like CGO_CXXFLAGS) must be set with env
-# variables.  I think these should all be env variables.
-export CGO_CXXFLAGS := -I$(SYNCBASE_LEVELDB_DIR)/include -I$(SYNCBASE_SNAPPY_DIR)/include
-MOGO_BIN := $(MOJO_DIR)/src/mojo/go/go.py
 define MOGO_BUILD
 	mkdir -p $(dir $2)
-	$(MOGO_BIN) $(MOJO_ANDROID_FLAGS) -- \
-		"$(GO_BIN)" \
-		"$(shell mktemp -d)" \
-		"$(PWD)/$(2)" \
-		"$(MOJO_DIR)/src" \
-		"$(PWD)/gen" \
-		"-I$(MOJO_DIR)/src -I$(SYNCBASE_LEVELDB_DIR)/include -I$(SYNCBASE_SNAPPY_DIR)/include" \
-		"-L$(dir $(MOJO_SHARED_LIB)) -lsystem_thunk -L$(SYNCBASE_LEVELDB_DIR)/lib -L$(SYNCBASE_SNAPPY_DIR)/lib" \
-		build $(GO_FLAGS) $1
+	GOPATH="$(GOPATH)" \
+	CGO_CFLAGS="$(CGO_CFLAGS)" \
+	CGO_CXXFLAGS="$(CGO_CXXFLAGS)" \
+	CGO_LDFLAGS="$(CGO_LDFLAGS)" \
+	$(GOROOT)/bin/go build -o $2 -tags=mojo -ldflags=-shared -buildmode=c-shared $1
+	rm $(basename $2).h
 endef
 
 # Generates go bindings from .mojom file.
 # $1 is input filename.
 # $2 is output directory.
 # $3 is language (go, dart, ...).
-MOJOM_BIN := $(MOJO_DIR)/src/mojo/public/tools/bindings/mojom_bindings_generator.py
 define MOJOM_GEN
 	mkdir -p $2
-	$(MOJOM_BIN) $1 -d . -o $2 -g $3
+	$(MOJO_DIR)/src/mojo/public/tools/bindings/mojom_bindings_generator.py $1 -d . -o $2 -g $3
 endef
 
 .DELETE_ON_ERROR:
@@ -116,14 +138,31 @@
 	./bin/principal seekblessings --v23.credentials creds
 	touch $@
 
-# Builds the shared library that Mojo services must be linked with.
+# Builds the library that Mojo services must be linked with.
 $(MOJO_SHARED_LIB):
-ifeq ($(wildcard $(MOJO_BUILD_DIR)),)
-	$(error ERROR: $(MOJO_BUILD_DIR) does not exist.  Please see README.md for instructions on compiling Mojo resources.)
-endif
 	mkdir -p $(dir $@)
 	ar rcs $@ $(MOJO_BUILD_DIR)/obj/mojo/public/platform/native/system.system_thunks.o
 
+ifdef ANDROID
+# Builds leveldb library for Android.
+$(LEVELDB_OUT_DIR)/lib/libleveldb.a: export TARGET_OS := OS_ANDROID_CROSSCOMPILE
+$(LEVELDB_OUT_DIR)/lib/libleveldb.a: export PREFIX := $(LEVELDB_OUT_DIR)/lib
+$(LEVELDB_OUT_DIR)/lib/libleveldb.a:
+	mkdir -p $(dir $@)
+	cd $(LEVELDB_SRC_DIR) && make clean && make all
+	# Delete the dynamic libraries, to prevent ld from linking leveldb
+	# dynamically.
+	rm $(LEVELDB_OUT_DIR)/lib/*.so*
+
+# Builds snappy library for Android.
+$(SNAPPY_OUT_DIR)/lib/libsnappy.a:
+	mkdir -p $(dir $@)
+	cd $(SNAPPY_SRC_DIR) && make clean && ./configure --prefix=$(SNAPPY_OUT_DIR) --build=x86_64-unknown-linux-gnu --host=arm-linux-androideabi --target=arm-linux-androideabi && make install
+	# Delete the dynamic libraries, to prevent ld from linking snappy
+	# dynamically.
+	rm $(SNAPPY_OUT_DIR)/lib/*.so*
+endif
+
 .PHONY: gen-mojom
 # TODO(nlacasse): The echo_client and echo_server are currently used to test
 # compilation and mojom binding generation.  We should remove them once they
@@ -132,29 +171,26 @@
 gen-mojom: dart/lib/gen/dart-pkg/mojom/lib/mojo/syncbase.mojom.dart gen/go/src/mojom/syncbase/syncbase.mojom.go
 
 dart/lib/gen/dart-pkg/mojom/lib/mojo/echo.mojom.dart: mojom/echo.mojom
-	$(call MOJOM_GEN,$<,dart/lib/gen,dart)
-	# TODO(nlacasse): Figure out why mojom_bindings_generator creates these bad
-	# symlinks on dart files.
-	rm -f dart/lib/gen/mojom/echo.mojom.dart
-
 dart/lib/gen/dart-pkg/mojom/lib/mojo/syncbase.mojom.dart: mojom/syncbase.mojom
+dart/lib/gen/dart-pkg/mojom/lib/mojo/echo.mojom.dart dart/lib/gen/dart-pkg/mojom/lib/mojo/syncbase.mojom.dart:
 	$(call MOJOM_GEN,$<,dart/lib/gen,dart)
 	# TODO(nlacasse): Figure out why mojom_bindings_generator creates these bad
 	# symlinks on dart files.
-	rm -f dart/lib/gen/mojom/syncbase.mojom.dart
+	rm -f dart/lib/gen/mojom/$(notdir $@)
 
 gen/go/src/mojom/echo/echo.mojom.go: mojom/echo.mojom
-	$(call MOJOM_GEN,$<,gen,go)
-	gofmt -w $@
-
 gen/go/src/mojom/syncbase/syncbase.mojom.go: mojom/syncbase.mojom
+gen/go/src/mojom/echo/echo.mojom.go gen/go/src/mojom/syncbase/syncbase.mojom.go:
 	$(call MOJOM_GEN,$<,gen,go)
 	gofmt -w $@
 
-gen/mojo/echo_server.mojo: $(GO_FILES) $(MOJO_SHARED_LIB) gen/go/src/mojom/echo/echo.mojom.go
+$(ETHER_BUILD_DIR)/echo_server.mojo: $(GO_FILES) $(MOJO_SHARED_LIB) gen/go/src/mojom/echo/echo.mojom.go
 	$(call MOGO_BUILD,$(PWD)/go/src/echo_server.go,$@)
 
-gen/mojo/syncbase_server.mojo: $(GO_FILES) $(V23_GO_FILES) $(MOJO_SHARED_LIB) gen/go/src/mojom/syncbase/syncbase.mojom.go
+$(ETHER_BUILD_DIR)/syncbase_server.mojo: CGO_CFLAGS += -I$(LEVELDB_SRC_DIR)/include -I$(SNAPPY_SRC_DIR)/include
+$(ETHER_BUILD_DIR)/syncbase_server.mojo: CGO_CXXFLAGS += -I$(LEVELDB_SRC_DIR)/include -I$(SNAPPY_SRC_DIR)/include
+$(ETHER_BUILD_DIR)/syncbase_server.mojo: CGO_LDFLAGS += -lsystem_thunk -L$(LEVELDB_OUT_DIR)/lib -lleveldb -L$(SNAPPY_OUT_DIR)/lib -lsnappy
+$(ETHER_BUILD_DIR)/syncbase_server.mojo: $(GO_FILES) $(V23_GO_FILES) $(MOJO_SHARED_LIB) $(LEVELDB_OUT_DIR)/lib/libleveldb.a $(SNAPPY_OUT_DIR)/lib/libsnappy.a gen/go/src/mojom/syncbase/syncbase.mojom.go
 	$(call MOGO_BUILD,v.io/syncbase/x/ref/services/syncbase/syncbased,$@)
 
 # Formats dart files to follow dart style conventions.
@@ -174,14 +210,16 @@
 dart/packages: dart/pubspec.yaml
 	cd dart && pub get
 
-# TODO(nlacasse): Remove this task and dart/bin/syncbase.dart when the tests
-# are stable enough to reliably test syncbase.
-.PHONY: run-syncbase-app
-run-syncbase-app: gen/mojo/syncbase_server.mojo dart/packages dart/lib/gen/dart-pkg/mojom/lib/mojo/syncbase.mojom.dart
-	V23_MOJO_FLAGS=$(V23_MOJO_FLAGS) $(MOJO_DIR)/src/mojo/devtools/common/mojo_run $(MOJO_SHELL_FLAGS) $(MOJO_ANDROID_FLAGS) $(PWD)/dart/bin/syncbase.dart
+.PHONY: run-syncbase-example
+run-syncbase-example: $(ETHER_BUILD_DIR)/syncbase_server.mojo dart/packages dart/lib/gen/dart-pkg/mojom/lib/mojo/syncbase.mojom.dart
+	V23_MOJO_FLAGS=$(V23_MOJO_FLAGS) $(MOJO_DIR)/src/mojo/devtools/common/mojo_run $(MOJO_SHELL_FLAGS) $(MOJO_ANDROID_FLAGS) https://mojo.v.io/syncbase_example.dart
+
+.PHONY: run-echo-example
+run-echo-example: $(ETHER_BUILD_DIR)/echo_server.mojo dart/packages dart/lib/gen/dart-pkg/mojom/lib/mojo/echo.mojom.dart
+	$(MOJO_DIR)/src/mojo/devtools/common/mojo_run $(MOJO_SHELL_FLAGS) $(MOJO_ANDROID_FLAGS) https://mojo.v.io/echo_example.dart
 
 .PHONY: test
-test: dart/packages gen/mojo/echo_server.mojo gen/mojo/syncbase_server.mojo gen-mojom
+test: dart/packages $(ETHER_BUILD_DIR)/echo_server.mojo $(ETHER_BUILD_DIR)/syncbase_server.mojo gen-mojom
 	V23_MOJO_FLAGS=$(V23_MOJO_FLAGS) $(MOJO_DIR)/src/mojo/devtools/common/mojo_test $(MOJO_SHELL_FLAGS) $(MOJO_ANDROID_FLAGS) --shell-path $(MOJO_DIR)/src/out/Debug/mojo_shell tests
 
 .PHONY: clean
diff --git a/dart/bin/echo_example.dart b/dart/bin/echo_example.dart
new file mode 100755
index 0000000..921be15
--- /dev/null
+++ b/dart/bin/echo_example.dart
@@ -0,0 +1,21 @@
+#!mojo mojo:dart_content_handler
+
+import 'package:ether/initialized_application.dart' show InitializedApplication;
+import 'package:ether/echo_client.dart' show EchoClient;
+
+main(List args) async {
+  InitializedApplication app = new InitializedApplication.fromHandle(args[0]);
+  await app.initialized;
+
+  String url = 'https://mojo.v.io/echo_server.mojo';
+
+  EchoClient c = new EchoClient(app, url);
+
+  String input = 'foobar';
+  String output = await c.echo(input);
+
+  print('in=$input out=$output match=${input == output}');
+
+  await c.close();
+  await app.close();
+}
diff --git a/dart/bin/syncbase.dart b/dart/bin/syncbase.dart
deleted file mode 100644
index 01c3f09..0000000
--- a/dart/bin/syncbase.dart
+++ /dev/null
@@ -1,22 +0,0 @@
-#!mojo mojo:dart_content_handler
-
-// TODO(nlacasse): Remove this file once the tests are sufficiently reliable to
-// test syncbase connectivity.
-
-import 'package:ether/initialized_application.dart' show InitializedApplication;
-import 'package:ether/syncbase_client.dart' show SyncbaseClient;
-
-main(List args) async {
-  InitializedApplication app = new InitializedApplication.fromHandle(args[0]);
-  await app.initialized;
-
-  // TODO(nlacasse): Switch to serving these files over http rather than
-  // directly from the filesystem, so they can be accessed by Android.
-  String url = 'file://' +
-      args[1].replaceFirst(
-          'dart/bin/syncbase.dart', 'gen/mojo/syncbase_server.mojo');
-
-  SyncbaseClient c = new SyncbaseClient(app, url);
-  bool exists = await c.app('foo').exists();
-  print('app(foo).exists(): $exists');
-}
diff --git a/dart/bin/syncbase_example.dart b/dart/bin/syncbase_example.dart
new file mode 100644
index 0000000..c5aef26
--- /dev/null
+++ b/dart/bin/syncbase_example.dart
@@ -0,0 +1,18 @@
+#!mojo mojo:dart_content_handler
+
+import 'package:ether/initialized_application.dart' show InitializedApplication;
+import 'package:ether/syncbase_client.dart' show SyncbaseClient;
+
+main(List args) async {
+  InitializedApplication app = new InitializedApplication.fromHandle(args[0]);
+  await app.initialized;
+
+  String url = 'https://mojo.v.io/syncbase_server.mojo';
+
+  SyncbaseClient c = new SyncbaseClient(app, url);
+  bool exists = await c.app('foo').exists();
+  print('app(foo).exists(): $exists');
+
+  await c.close();
+  await app.close();
+}
diff --git a/dart/test/echo_test.dart b/dart/test/echo_test.dart
index 593f507..00101eb 100644
--- a/dart/test/echo_test.dart
+++ b/dart/test/echo_test.dart
@@ -11,11 +11,7 @@
   InitializedApplication app = new InitializedApplication.fromHandle(args[0]);
   await app.initialized;
 
-  // TODO(nlacasse): Switch to serving these files over http rather than
-  // directly from the filesystem, so they can be accessed by Android.
-  String url = 'file://' +
-      args[1].replaceFirst(
-          'dart/test/echo_test.dart', 'gen/mojo/echo_server.mojo');
+  String url = 'https://mojo.v.io/echo_server.mojo';
 
   EchoClient c = new EchoClient(app, url);
 
diff --git a/dart/test/syncbase_test.dart b/dart/test/syncbase_test.dart
index 1a1f628..2b9fe72 100755
--- a/dart/test/syncbase_test.dart
+++ b/dart/test/syncbase_test.dart
@@ -17,11 +17,7 @@
   InitializedApplication app = new InitializedApplication.fromHandle(args[0]);
   await app.initialized;
 
-  // TODO(nlacasse): Switch to serving these files over http rather than
-  // directly from the filesystem, so they can be accessed by Android.
-  String url = 'file://' +
-      args[1].replaceFirst(
-          'dart/test/syncbase_test.dart', 'gen/mojo/syncbase_server.mojo');
+  String url = 'https://mojo.v.io/syncbase_server.mojo';
 
   SyncbaseClient c = new SyncbaseClient(app, url);
 
diff --git a/mojoconfig b/mojoconfig
new file mode 100644
index 0000000..e8232da
--- /dev/null
+++ b/mojoconfig
@@ -0,0 +1,45 @@
+# NOTE(nlacasse): This file was taken from $MOJO_DIR/mojoconfig and modified to
+# serve "https://mojo.v.io" assets from ETHER_DIR and ETHER_BUILD_DIR.
+
+# This is a configuration file for devtools (`mojo_run`, `mojo_test) running
+# within a Mojo checkout.
+
+# The content has to parse to a Python dictionary literal. Strings of the form
+# '@{ABC}' are aliases that will be substituted for their values before
+# evaluation:
+#  '@{BUILD_DIR}': path to the mojo output directory
+#  '@{ETHER_DIR}': path to root of ether project
+#  '@{ETHER_BUILD_DIR}': path to directory of built ether mojo files
+
+{
+  # Each dev server will be configured as specified and mapped for the
+  # indicated host using --map-origin.
+  'dev_servers': [
+    {
+      'host': 'https://core.mojoapps.io/',
+      # First matching prefix will apply. Within the directiories specified for
+      # a prefix, first location that contains the requested path will apply.
+      'mappings': [
+        ('packages/', [
+          '@{BUILD_DIR}/gen/dart-pkg/packages',
+          '@{ETHER_DIR}/dart/lib/gen/dart-pkg/packages'
+        ]),
+        ('', [
+          # We map two directiories, so that both exploded dart apps under
+          # checkout root and built apps in the build directory are available.
+          # For example, one could refer to the apps of either type using urls:
+          #  - https://core.mojoapps.io/spinning_cube.mojo
+          #  - https://core.mojoapps.io/examples/dart/device_info/main.dart
+          '@{BUILD_DIR}',
+          '.'
+        ]),
+      ],
+    },
+    {
+      'host': 'https://mojo.v.io',
+      'mappings': [
+        ('', ['@{ETHER_BUILD_DIR}', '@{ETHER_DIR}/dart/bin']),
+      ],
+    },
+  ],
+}
diff --git a/tests b/tests
index 277f3ce..861c96d 100644
--- a/tests
+++ b/tests
@@ -9,6 +9,6 @@
 	{
 		"test": "dart/test/syncbase_test.dart",
 		"type": "dart",
-		"timeout": 10
+		"timeout": 30
 	}
 ]