diff --git a/gpu/command_buffer/build_gles2_cmd_buffer.py b/gpu/command_buffer/build_gles2_cmd_buffer.py index 6c77e2f89e7..9887d8f5080 100755 --- a/gpu/command_buffer/build_gles2_cmd_buffer.py +++ b/gpu/command_buffer/build_gles2_cmd_buffer.py @@ -3300,7 +3300,7 @@ _FUNCTION_INFO = { 'type': 'Custom', 'impl_func': False, 'unit_test': False, - 'extension': 'CHROMIUM_resize', + 'extension': "CHROMIUM_resize", 'chromium': True, }, 'GetRequestableExtensionsCHROMIUM': { @@ -3463,24 +3463,24 @@ _FUNCTION_INFO = { 'type': 'GLcharN', 'decoder_func': 'DoInsertEventMarkerEXT', 'expectation': False, - 'extension': True, + 'extension': "EXT_debug_marker", }, 'PushGroupMarkerEXT': { 'type': 'GLcharN', 'decoder_func': 'DoPushGroupMarkerEXT', 'expectation': False, - 'extension': True, + 'extension': "EXT_debug_marker", }, 'PopGroupMarkerEXT': { 'decoder_func': 'DoPopGroupMarkerEXT', 'expectation': False, - 'extension': True, + 'extension': "EXT_debug_marker", 'impl_func': False, }, 'GenVertexArraysOES': { 'type': 'GENn', - 'extension': True, + 'extension': "OES_vertex_array_object", 'gl_test_func': 'glGenVertexArraysOES', 'resource_type': 'VertexArray', 'resource_types': 'VertexArrays', @@ -3489,7 +3489,7 @@ _FUNCTION_INFO = { }, 'BindVertexArrayOES': { 'type': 'Bind', - 'extension': True, + 'extension': "OES_vertex_array_object", 'gl_test_func': 'glBindVertexArrayOES', 'decoder_func': 'DoBindVertexArrayOES', 'gen_func': 'GenVertexArraysOES', @@ -3499,7 +3499,7 @@ _FUNCTION_INFO = { }, 'DeleteVertexArraysOES': { 'type': 'DELn', - 'extension': True, + 'extension': "OES_vertex_array_object", 'gl_test_func': 'glDeleteVertexArraysOES', 'resource_type': 'VertexArray', 'resource_types': 'VertexArrays', @@ -3508,7 +3508,7 @@ _FUNCTION_INFO = { }, 'IsVertexArrayOES': { 'type': 'Is', - 'extension': True, + 'extension': "OES_vertex_array_object", 'gl_test_func': 'glIsVertexArrayOES', 'decoder_func': 'DoIsVertexArrayOES', 'expectation': False, @@ -10634,24 +10634,13 @@ def main(argv): gen.WriteGLES2Header("gpu/GLES2/gl2chromium_autogen.h") mojo_gles2_prefix = ("mojo/public/c/gles2/gles2_call_visitor") gen.WriteMojoGLCallVisitor(mojo_gles2_prefix + "_autogen.h") - gen.WriteMojoGLCallVisitorForExtension( - mojo_gles2_prefix + "_chromium_texture_mailbox_autogen.h", - "CHROMIUM_texture_mailbox") - gen.WriteMojoGLCallVisitorForExtension( - mojo_gles2_prefix + "_chromium_sync_point_autogen.h", - "CHROMIUM_sync_point") - gen.WriteMojoGLCallVisitorForExtension( - mojo_gles2_prefix + "_chromium_sub_image_autogen.h", - "CHROMIUM_sub_image") - gen.WriteMojoGLCallVisitorForExtension( - mojo_gles2_prefix + "_chromium_miscellaneous_autogen.h", - "CHROMIUM_miscellaneous") - gen.WriteMojoGLCallVisitorForExtension( - mojo_gles2_prefix + "_chromium_resize_autogen.h", - "CHROMIUM_resize") - gen.WriteMojoGLCallVisitorForExtension( - mojo_gles2_prefix + "_occlusion_query_ext_autogen.h", - "occlusion_query_EXT") + mojo_extensions = ["CHROMIUM_texture_mailbox", "CHROMIUM_sync_point", + "CHROMIUM_sub_image", "CHROMIUM_miscellaneous", + "CHROMIUM_resize", "EXT_debug_marker", + "OES_vertex_array_object", "occlusion_query_EXT"] + for extension in mojo_extensions: + gen.WriteMojoGLCallVisitorForExtension( + mojo_gles2_prefix + "_" + extension.lower() + "_autogen.h", extension) Format(gen.generated_cpp_filenames) diff --git a/gpu/skia_bindings/gl_bindings_skia_cmd_buffer.cc b/gpu/skia_bindings/gl_bindings_skia_cmd_buffer.cc index c40eedb19e3..021afabe87e 100644 --- a/gpu/skia_bindings/gl_bindings_skia_cmd_buffer.cc +++ b/gpu/skia_bindings/gl_bindings_skia_cmd_buffer.cc @@ -30,6 +30,7 @@ GrGLInterface* CreateCommandBufferSkiaGLBinding() { functions->fBindTexture = glBindTexture; functions->fBindVertexArray = glBindVertexArrayOES; functions->fBlendEquation = glBlendEquation; + functions->fBlendBarrier = glBlendBarrierKHR; functions->fBlendColor = glBlendColor; functions->fBlendFunc = glBlendFunc; functions->fBufferData = glBufferData; diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java index 2d1b43d3de5..07d568e94d4 100644 --- a/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java @@ -167,6 +167,7 @@ public class InterfacesTest extends MojoTestCase { * Implementation of PingService. */ public class PingServiceImpl extends CapturingErrorHandler implements PingService { + @Override public void ping(PingResponse callback) { callback.call(); } diff --git a/mojo/application/content_handler_factory.cc b/mojo/application/content_handler_factory.cc index d260b23b902..12310015d05 100644 --- a/mojo/application/content_handler_factory.cc +++ b/mojo/application/content_handler_factory.cc @@ -11,6 +11,7 @@ #include "base/memory/weak_ptr.h" #include "base/thread_task_runner_handle.h" #include "base/threading/platform_thread.h" +#include "base/trace_event/trace_event.h" #include "mojo/application/application_runner_chromium.h" #include "mojo/common/message_pump_mojo.h" #include "mojo/public/cpp/application/application_connection.h" @@ -40,6 +41,8 @@ class ApplicationThread : public base::PlatformThread::Delegate { private: void ThreadMain() override { + TRACE_EVENT_INSTANT1("content_handler", "ThreadMain()", + TRACE_EVENT_SCOPE_THREAD, "url", response_->url.get()); base::PlatformThread::SetName(response_->url); handler_delegate_->RunApplication(application_request_.Pass(), response_.Pass()); @@ -79,6 +82,8 @@ class ContentHandlerImpl : public ContentHandler { // Overridden from ContentHandler: void StartApplication(InterfaceRequest application_request, URLResponsePtr response) override { + TRACE_EVENT_INSTANT1("content_handler", "StartApplication()", + TRACE_EVENT_SCOPE_THREAD, "url", response->url.get()); ApplicationThread* thread = new ApplicationThread( base::ThreadTaskRunnerHandle::Get(), base::Bind(&ContentHandlerImpl::OnThreadEnd, diff --git a/mojo/common/BUILD.gn b/mojo/common/BUILD.gn index 8e73bc5179a..460471b808e 100644 --- a/mojo/common/BUILD.gn +++ b/mojo/common/BUILD.gn @@ -39,6 +39,7 @@ component("common") { "//base/third_party/dynamic_annotations", "//mojo/public/c/system", "//mojo/public/cpp/bindings", + "//mojo/public/cpp/environment:environment", "//mojo/public/cpp/system", "//url", ] diff --git a/mojo/common/dart/BUILD.gn b/mojo/common/dart/BUILD.gn new file mode 100644 index 00000000000..296e3c9fc50 --- /dev/null +++ b/mojo/common/dart/BUILD.gn @@ -0,0 +1,17 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//mojo/public/dart/rules.gni") + +dartzip_package("dart") { + sources = [ + "lib/trace_controller_impl.dart", + "lib/tracing_helper.dart", + "pubspec.yaml", + ] + deps = [ + "//mojo/public/dart", + "//mojo/services/tracing/public/interfaces", + ] +} diff --git a/mojo/common/dart/lib/trace_controller_impl.dart b/mojo/common/dart/lib/trace_controller_impl.dart new file mode 100644 index 00000000000..6cbc634562a --- /dev/null +++ b/mojo/common/dart/lib/trace_controller_impl.dart @@ -0,0 +1,46 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:mojo/application.dart'; +import 'package:mojo/bindings.dart'; +import 'package:mojo/core.dart'; +import 'package:mojom/tracing/tracing.mojom.dart'; + +class TraceControllerImpl implements TraceController { + TraceControllerStub _stub; + TraceDataCollectorProxy _collector; + // TODO(rudominer) We currently ignore _categories. + String _categories; + + TraceControllerImpl.fromEndpoint(MojoMessagePipeEndpoint e) { + _stub = TraceControllerStub.newFromEndpoint(e); + _stub.impl = this; + } + + @override + void startTracing(String categories, TraceDataCollectorProxy collector) { + assert(_collector == null); + _collector = collector; + _categories = categories; + } + + @override + void stopTracing() { + assert(_collector != null); + _collector.close(); + _collector = null; + } + + bool isActive() { + return _collector != null; + } + + void sendTraceMessage(String message) { + if (_collector != null) { + _collector.ptr.dataCollected(message); + } + } +} diff --git a/mojo/common/dart/lib/tracing_helper.dart b/mojo/common/dart/lib/tracing_helper.dart new file mode 100644 index 00000000000..f776d9a3f9c --- /dev/null +++ b/mojo/common/dart/lib/tracing_helper.dart @@ -0,0 +1,109 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +library tracing; + +import 'trace_controller_impl.dart'; + +import 'dart:async'; +import 'dart:convert'; +import 'dart:core'; +import 'dart:io'; +import 'dart:isolate'; + +import 'package:mojo/application.dart'; +import 'package:mojo/bindings.dart'; +import 'package:mojo/core.dart'; +import 'package:mojom/tracing/tracing.mojom.dart'; + +// TracingHelper is used by Dart code running in the Mojo shell in order +// to perform tracing. +class TracingHelper { + TraceControllerImpl _impl; + String _tid; + + // Construct an instance of TracingHelper from within your application's + // |initialize()| method. |appName| will be used to form a thread identifier + // for use in trace messages. If |appName| is longer than 20 characters then + // only the last 20 characters of |appName| will be used. + TracingHelper(Application app, String appName) { + // We use only the last 20 characters of appName to form the tid so that + // the 9-digit Isolate hash code we are appending won't get truncated by the + // tracing UI. + if (appName.length > 20) { + appName = appName.substring(appName.length - 20); + } + _tid = "${appName}/${Isolate.current.hashCode.toString()}"; + ApplicationConnection connection = app.connectToApplication("mojo:tracing"); + connection.provideService(TraceControllerName, (e) { + assert(_impl == null); + _impl = new TraceControllerImpl.fromEndpoint(e); + }); + } + + bool isActive() { + return (_impl != null) && _impl.isActive(); + } + + // Invoke this at the beginning of a function whose duration you wish to + // trace. Invoke |end()| on the returned object. + FunctionTrace beginFunction(String functionName, {Map args}) { + assert(functionName != null); + if (isActive()) { + _sendTraceMessage(functionName, "B", args: args); + } else { + functionName = null; + } + return new _FunctionTraceImpl(this, functionName); + } + + void _endFunction(String functionName) { + _sendTraceMessage(functionName, "E"); + } + + void _sendTraceMessage(String name, String phase, + {Map args}) { + if (isActive()) { + var map = {}; + map["name"] = name; + map["ph"] = phase; + map["ts"] = getTimeTicksNow(); + map["pid"] = pid; + map["tid"] = _tid; + if (args != null) { + map["args"] = args; + } + _impl.sendTraceMessage(JSON.encode(map)); + } + } + + // A convenience method that wraps a closure in a begin-end pair of + // tracing calls. + dynamic trace(String functionName, closure(), {Map args}) { + FunctionTrace ft = beginFunction(functionName, args: args); + final returnValue = closure(); + ft.end(); + return returnValue; + } +} + +// A an instance of FunctionTrace is returned from |beginFunction()|. +// Invoke |end()| from every exit point in the function you are tracing. +abstract class FunctionTrace { + void end(); +} + +class _FunctionTraceImpl implements FunctionTrace { + TracingHelper _tracing; + String _functionName; + + _FunctionTraceImpl(this._tracing, this._functionName); + + @override + void end() { + if (_functionName != null) { + _tracing._endFunction(_functionName); + } + } +} diff --git a/mojo/common/dart/pubspec.lock b/mojo/common/dart/pubspec.lock new file mode 100644 index 00000000000..e7b03b5083b --- /dev/null +++ b/mojo/common/dart/pubspec.lock @@ -0,0 +1,3 @@ +# Generated by pub +# See http://pub.dartlang.org/doc/glossary.html#lockfile +packages: {} diff --git a/mojo/common/dart/pubspec.yaml b/mojo/common/dart/pubspec.yaml new file mode 100644 index 00000000000..7a0f17c02c1 --- /dev/null +++ b/mojo/common/dart/pubspec.yaml @@ -0,0 +1,2 @@ +author: Chromium Authors +name: common diff --git a/mojo/common/data_pipe_file_utils.cc b/mojo/common/data_pipe_file_utils.cc index 26cdb9fcd29..aa14225b3e1 100644 --- a/mojo/common/data_pipe_file_utils.cc +++ b/mojo/common/data_pipe_file_utils.cc @@ -6,70 +6,311 @@ #include +#include + +#include "base/files/file.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/files/scoped_file.h" #include "base/location.h" -#include "base/task_runner_util.h" #include "base/trace_event/trace_event.h" #include "mojo/common/data_pipe_utils_internal.h" +#include "mojo/public/cpp/environment/async_waiter.h" namespace mojo { namespace common { namespace { -size_t CopyToFileHelper(FILE* fp, const void* buffer, uint32_t num_bytes) { - return fwrite(buffer, 1, num_bytes, fp); +class CopyToFileHandler { + public: + CopyToFileHandler(ScopedDataPipeConsumerHandle source, + const base::FilePath& destination, + base::TaskRunner* task_runner, + const base::Callback& callback); + + private: + ~CopyToFileHandler(); + + void SendCallback(bool value); + void OpenFile(); + void OnHandleReady(MojoResult result); + void WriteToFile(); + + ScopedDataPipeConsumerHandle source_; + const base::FilePath destination_; + base::TaskRunner* file_task_runner_; + base::Callback callback_; + base::File file_; + scoped_ptr waiter_; + const void* buffer_; + uint32_t buffer_size_; + scoped_refptr main_runner_; + + DISALLOW_COPY_AND_ASSIGN(CopyToFileHandler); +}; + +CopyToFileHandler::CopyToFileHandler(ScopedDataPipeConsumerHandle source, + const base::FilePath& destination, + base::TaskRunner* task_runner, + const base::Callback& callback) + : source_(source.Pass()), + destination_(destination), + file_task_runner_(task_runner), + callback_(callback), + buffer_(nullptr), + buffer_size_(0u), + main_runner_(base::MessageLoop::current()->task_runner()) { + TRACE_EVENT_ASYNC_BEGIN1("data_pipe_utils", "CopyToFile", this, "destination", + destination.MaybeAsASCII()); + file_task_runner_->PostTask( + FROM_HERE, + base::Bind(&CopyToFileHandler::OpenFile, base::Unretained(this))); } -bool BlockingCopyFromFile(const base::FilePath& source, - ScopedDataPipeProducerHandle destination, - uint32_t skip) { - TRACE_EVENT1("data_pipe_utils", "BlockingCopyFromFile", "source", - source.MaybeAsASCII()); - base::File file(source, base::File::FLAG_OPEN | base::File::FLAG_READ); - if (!file.IsValid()) - return false; - if (file.Seek(base::File::FROM_BEGIN, skip) != skip) { - LOG(ERROR) << "Seek of " << skip << " in " << source.value() << " failed"; - return false; +CopyToFileHandler::~CopyToFileHandler() { + TRACE_EVENT_ASYNC_END0("data_pipe_utils", "CopyToFile", this); +} + +void CopyToFileHandler::SendCallback(bool value) { + DCHECK(main_runner_->RunsTasksOnCurrentThread()); + if (file_.IsValid()) { + // Need to close the file before calling the callback. + file_task_runner_->PostTaskAndReply( + FROM_HERE, base::Bind(&base::File::Close, base::Unretained(&file_)), + base::Bind(&CopyToFileHandler::SendCallback, base::Unretained(this), + value)); + return; } - for (;;) { - void* buffer = nullptr; - uint32_t buffer_num_bytes = 0; - MojoResult result = - BeginWriteDataRaw(destination.get(), &buffer, &buffer_num_bytes, - MOJO_WRITE_DATA_FLAG_NONE); + base::Callback callback = callback_; + delete this; + callback.Run(value); +} + +void CopyToFileHandler::OpenFile() { + DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); + file_.Initialize(destination_, + base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); + if (!file_.IsValid()) { + LOG(ERROR) << "Opening file '" << destination_.value() + << "' failed in CopyToFileHandler::OpenFile"; + main_runner_->PostTask(FROM_HERE, + base::Bind(&CopyToFileHandler::SendCallback, + base::Unretained(this), false)); + return; + } + main_runner_->PostTask(FROM_HERE, + base::Bind(&CopyToFileHandler::OnHandleReady, + base::Unretained(this), MOJO_RESULT_OK)); +} + +void CopyToFileHandler::OnHandleReady(MojoResult result) { + DCHECK(main_runner_->RunsTasksOnCurrentThread()); + if (result == MOJO_RESULT_OK) { + result = BeginReadDataRaw(source_.get(), &buffer_, &buffer_size_, + MOJO_READ_DATA_FLAG_NONE); if (result == MOJO_RESULT_OK) { - int bytes_read = - file.ReadAtCurrentPos(static_cast(buffer), buffer_num_bytes); - if (bytes_read >= 0) { - EndWriteDataRaw(destination.get(), bytes_read); - if (bytes_read == 0) { - // eof - return true; - } - } else { - // error - EndWriteDataRaw(destination.get(), 0); - return false; - } - } else if (result == MOJO_RESULT_SHOULD_WAIT) { - result = Wait(destination.get(), MOJO_HANDLE_SIGNAL_WRITABLE, - MOJO_DEADLINE_INDEFINITE, nullptr); - if (result != MOJO_RESULT_OK) { - // If the consumer handle was closed, then treat as EOF. - return result == MOJO_RESULT_FAILED_PRECONDITION; - } - } else { - // If the consumer handle was closed, then treat as EOF. - return result == MOJO_RESULT_FAILED_PRECONDITION; + file_task_runner_->PostTask( + FROM_HERE, + base::Bind(&CopyToFileHandler::WriteToFile, base::Unretained(this))); + return; } } -#if !defined(OS_WIN) - NOTREACHED(); - return false; -#endif + if (result == MOJO_RESULT_FAILED_PRECONDITION) { + SendCallback(true); + return; + } + if (result == MOJO_RESULT_SHOULD_WAIT) { + waiter_.reset(new AsyncWaiter( + source_.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind(&CopyToFileHandler::OnHandleReady, base::Unretained(this)))); + return; + } + SendCallback(false); +} + +void CopyToFileHandler::WriteToFile() { + DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); + uint32_t num_bytes = buffer_size_; + size_t num_bytes_written = + file_.WriteAtCurrentPos(static_cast(buffer_), num_bytes); + MojoResult result = EndReadDataRaw(source_.get(), num_bytes); + buffer_ = nullptr; + buffer_size_ = 0; + if (num_bytes_written != num_bytes) { + LOG(ERROR) << "Wrote fewer bytes (" << num_bytes_written + << ") than expected (" << num_bytes + << "), (pipe closed? out of disk space?)"; + main_runner_->PostTask(FROM_HERE, + base::Bind(&CopyToFileHandler::SendCallback, + base::Unretained(this), false)); + return; + } + if (result != MOJO_RESULT_OK) { + LOG(ERROR) << "EndReadDataRaw error (" << result << ")"; + main_runner_->PostTask(FROM_HERE, + base::Bind(&CopyToFileHandler::SendCallback, + base::Unretained(this), false)); + } + main_runner_->PostTask(FROM_HERE, + base::Bind(&CopyToFileHandler::OnHandleReady, + base::Unretained(this), result)); +} + +class CopyFromFileHandler { + public: + CopyFromFileHandler(const base::FilePath& source, + ScopedDataPipeProducerHandle destination, + uint32_t skip, + base::TaskRunner* task_runner, + const base::Callback& callback); + + private: + ~CopyFromFileHandler(); + + void SendCallback(bool value); + void OpenFile(); + void OnHandleReady(MojoResult result); + void ReadFromFile(); + + const base::FilePath source_; + ScopedDataPipeProducerHandle destination_; + uint32_t skip_; + base::TaskRunner* file_task_runner_; + base::Callback callback_; + base::File file_; + scoped_ptr waiter_; + void* buffer_; + uint32_t buffer_size_; + scoped_refptr main_runner_; + + DISALLOW_COPY_AND_ASSIGN(CopyFromFileHandler); +}; + +CopyFromFileHandler::CopyFromFileHandler( + const base::FilePath& source, + ScopedDataPipeProducerHandle destination, + uint32_t skip, + base::TaskRunner* task_runner, + const base::Callback& callback) + : source_(source), + destination_(destination.Pass()), + skip_(skip), + file_task_runner_(task_runner), + callback_(callback), + buffer_(nullptr), + buffer_size_(0u), + main_runner_(base::MessageLoop::current()->task_runner()) { + TRACE_EVENT_ASYNC_BEGIN1("data_pipe_utils", "CopyFromFile", this, "source", + source.MaybeAsASCII()); + file_task_runner_->PostTask( + FROM_HERE, + base::Bind(&CopyFromFileHandler::OpenFile, base::Unretained(this))); +} + +CopyFromFileHandler::~CopyFromFileHandler() { + TRACE_EVENT_ASYNC_END0("data_pipe_utils", "CopyFromFile", this); +} + +void CopyFromFileHandler::SendCallback(bool value) { + DCHECK(main_runner_->RunsTasksOnCurrentThread()); + if (file_.IsValid()) { + // Need to close the file before calling the callback. + file_task_runner_->PostTaskAndReply( + FROM_HERE, base::Bind(&base::File::Close, base::Unretained(&file_)), + base::Bind(&CopyFromFileHandler::SendCallback, base::Unretained(this), + value)); + return; + } + base::Callback callback = callback_; + delete this; + callback.Run(value); +} + +void CopyFromFileHandler::OpenFile() { + DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); + file_.Initialize(source_, base::File::FLAG_OPEN | base::File::FLAG_READ); + if (!file_.IsValid()) { + LOG(ERROR) << "Opening file '" << source_.value() + << "' failed in CopyFromFileHandler::OpenFile"; + main_runner_->PostTask(FROM_HERE, + base::Bind(&CopyFromFileHandler::SendCallback, + base::Unretained(this), false)); + return; + } + if (file_.Seek(base::File::FROM_BEGIN, skip_) != skip_) { + LOG(ERROR) << "Seek of " << skip_ << " failed"; + main_runner_->PostTask(FROM_HERE, + base::Bind(&CopyFromFileHandler::SendCallback, + base::Unretained(this), false)); + return; + } + main_runner_->PostTask(FROM_HERE, + base::Bind(&CopyFromFileHandler::OnHandleReady, + base::Unretained(this), MOJO_RESULT_OK)); +} + +void CopyFromFileHandler::OnHandleReady(MojoResult result) { + DCHECK(main_runner_->RunsTasksOnCurrentThread()); + if (result == MOJO_RESULT_OK) { + result = BeginWriteDataRaw(destination_.get(), &buffer_, &buffer_size_, + MOJO_READ_DATA_FLAG_NONE); + if (result == MOJO_RESULT_OK) { + file_task_runner_->PostTask(FROM_HERE, + base::Bind(&CopyFromFileHandler::ReadFromFile, + base::Unretained(this))); + + return; + } + } + if (result == MOJO_RESULT_SHOULD_WAIT) { + waiter_.reset( + new AsyncWaiter(destination_.get(), MOJO_HANDLE_SIGNAL_WRITABLE, + base::Bind(&CopyFromFileHandler::OnHandleReady, + base::Unretained(this)))); + return; + } + SendCallback(false); +} + +void CopyFromFileHandler::ReadFromFile() { + DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); + DCHECK_LT(buffer_size_, + static_cast(std::numeric_limits::max())); + int num_bytes = buffer_size_; + int num_bytes_read = + file_.ReadAtCurrentPos(static_cast(buffer_), num_bytes); + MojoResult result = + EndWriteDataRaw(destination_.get(), std::max(0, num_bytes_read)); + buffer_ = nullptr; + buffer_size_ = 0; + if (num_bytes_read == -1) { + LOG(ERROR) << "Error while reading from file."; + main_runner_->PostTask(FROM_HERE, + base::Bind(&CopyFromFileHandler::SendCallback, + base::Unretained(this), false)); + return; + } + if (result != MOJO_RESULT_OK) { + LOG(ERROR) << "EndWriteDataRaw error (" << result << ")"; + main_runner_->PostTask(FROM_HERE, + base::Bind(&CopyFromFileHandler::SendCallback, + base::Unretained(this), false)); + return; + } + if (num_bytes_read != num_bytes) { + // Reached EOF. Stop the process. + main_runner_->PostTask(FROM_HERE, + base::Bind(&CopyFromFileHandler::SendCallback, + base::Unretained(this), true)); + return; + } + main_runner_->PostTask(FROM_HERE, + base::Bind(&CopyFromFileHandler::OnHandleReady, + base::Unretained(this), result)); +} + +size_t CopyToFileHelper(FILE* fp, const void* buffer, uint32_t num_bytes) { + return fwrite(buffer, 1, num_bytes, fp); } } // namespace @@ -92,10 +333,7 @@ void CopyToFile(ScopedDataPipeConsumerHandle source, const base::FilePath& destination, base::TaskRunner* task_runner, const base::Callback& callback) { - base::PostTaskAndReplyWithResult( - task_runner, FROM_HERE, - base::Bind(&BlockingCopyToFile, base::Passed(&source), destination), - callback); + new CopyToFileHandler(source.Pass(), destination, task_runner, callback); } void CopyFromFile(const base::FilePath& source, @@ -103,10 +341,8 @@ void CopyFromFile(const base::FilePath& source, uint32_t skip, base::TaskRunner* task_runner, const base::Callback& callback) { - base::PostTaskAndReplyWithResult(task_runner, FROM_HERE, - base::Bind(&BlockingCopyFromFile, source, - base::Passed(&destination), skip), - callback); + new CopyFromFileHandler(source, destination.Pass(), skip, task_runner, + callback); } } // namespace common diff --git a/mojo/dart/http_load_test/runner.py b/mojo/dart/http_load_test/runner.py index 35769cff41e..3f72f39994e 100755 --- a/mojo/dart/http_load_test/runner.py +++ b/mojo/dart/http_load_test/runner.py @@ -12,6 +12,7 @@ import sys MOJO_SHELL = 'mojo_shell' + def main(build_dir, dart_exe, tester_script, tester_dir): shell_exe = os.path.join(build_dir, MOJO_SHELL) subprocess.check_call([ @@ -38,6 +39,7 @@ if __name__ == '__main__': required=True, help="Path to dart executable.") args = parser.parse_args() - tester_dir = os.path.dirname(os.path.realpath(__file__)) - tester_dart_script = os.path.join(tester_dir, 'tester.dart'); - sys.exit(main(args.build_dir, args.dart_exe, tester_dart_script, tester_dir)) + tester_directory = os.path.dirname(os.path.realpath(__file__)) + tester_dart_script = os.path.join(tester_directory, 'tester.dart') + sys.exit(main(args.build_dir, args.dart_exe, tester_dart_script, + tester_directory)) diff --git a/mojo/dart/mojom/BUILD.gn b/mojo/dart/mojom/BUILD.gn index 0c435bb457d..c29b19a5a27 100644 --- a/mojo/dart/mojom/BUILD.gn +++ b/mojo/dart/mojom/BUILD.gn @@ -9,6 +9,7 @@ dart_pkg("mojom") { sources = [ "CHANGELOG.md", "README.md", + "lib/src/options.dart", "lib/src/utils.dart", "pubspec.yaml", ] diff --git a/mojo/dart/mojom/README.md b/mojo/dart/mojom/README.md index d7889aa260a..eb63e4e0d2a 100644 --- a/mojo/dart/mojom/README.md +++ b/mojo/dart/mojom/README.md @@ -1,7 +1,18 @@ mojom ==== -This package is a placeholder for generated mojom bindings. +This package is a placeholder for generated mojom bindings. It contains a script +lib/generate.dart. + +This script generates Mojo bindings for a Dart package. Dart packages will be +populated according to the DartPackage annotations in .mojom files. Any .mojom +files that don't have an annotation will have their bindings generated into a +local copy of the 'mojom' package. Annotations specifying the host package will +cause generation into the host package's lib/ directory. For every other +DartPackage annotation, the bindings will be generated into the named package, +either into the global package cache if a package of that name has already been +fetched, or into a local directory created under the current package's packages/ +directory. Generated Mojo bindings in other pub packages should be installed into this package by saying the following after `pub get`: @@ -15,3 +26,16 @@ their contents will be installed to this package as well: ``` $ dart -p packages packages/mojom/generate.dart -a ``` + +Full options: + +``` +$ dart packages/mojom/generate.dart [-p package-root] + [-a additional-dirs] + [-m mojo-sdk] + [-g] # Generate from .mojom files + [-d] # Download from .mojoms files + [-i] # Ignore duplicates + [-v] # verbose + [-f] # Fake (dry) run +``` diff --git a/mojo/dart/mojom/lib/generate.dart b/mojo/dart/mojom/lib/generate.dart index 1b03cb36f72..5a78ba87fca 100644 --- a/mojo/dart/mojom/lib/generate.dart +++ b/mojo/dart/mojom/lib/generate.dart @@ -2,19 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -/// This script should be run by every project that consumes Mojom IDL -/// interfaces. It populates the 'mojom' package with the generated Dart -/// bindings for the Mojom IDL files. -/// -/// From a consuming project, it should be invoked as follows: -/// -/// $ dart packages/mojom/generate.dart [-p package-root] -/// [-a additional-dirs] -/// [-m mojo-sdk] -/// [-g] # Generate from .mojom files -/// [-d] # Download from .mojoms files -/// [-v] # verbose -/// [-f] # Fake (dry) run +/// This script generates Mojo bindings for a Dart package. See README.md for +/// details. library generate; @@ -25,14 +14,16 @@ import 'dart:io'; import 'package:args/args.dart' as args; import 'package:path/path.dart' as path; +part 'src/options.dart'; part 'src/utils.dart'; +bool errorOnDuplicate; bool verbose; bool dryRun; -Map duplicateDetection = new Map(); +Map duplicateDetection; /// Searches for .mojom.dart files under [mojomDirectory] and copies them to -/// the 'mojom' packages. +/// [data.currentPackage]. copyAction(PackageIterData data, Directory mojomDirectory) async { await for (var mojom in mojomDirectory.list(recursive: true)) { if (mojom is! File) continue; @@ -40,12 +31,13 @@ copyAction(PackageIterData data, Directory mojomDirectory) async { if (verbose) print("Found $mojom"); final relative = path.relative(mojom.path, from: mojomDirectory.path); - final dest = path.join(data.mojomPackage.path, relative); + final dest = path.join(data.currentPackage.path, relative); final destDirectory = new Directory(path.dirname(dest)); - if (duplicateDetection.containsKey(dest)) { + if (errorOnDuplicate && duplicateDetection.containsKey(dest)) { String original = duplicateDetection[dest]; - throw new GenerationError('Conflict: Both ${original} and ${mojom.path} supply ${dest}'); + throw new GenerationError( + 'Conflict: Both ${original} and ${mojom.path} supply ${dest}'); } duplicateDetection[dest] = mojom.path; @@ -65,24 +57,30 @@ copyAction(PackageIterData data, Directory mojomDirectory) async { /// Searches for .mojom files under [mojomDirectory], generates .mojom.dart /// files for them, and copies them to the 'mojom' package. generateAction(GenerateIterData data, Directory mojomDirectory) async { + final packageRoot = data.currentPackage.parent; await for (var mojom in mojomDirectory.list(recursive: true)) { if (mojom is! File) continue; if (!isMojom(mojom.path)) continue; if (verbose) print("Found $mojom"); - final script = path.join(data.mojoSdk.path, - 'tools', 'bindings', 'mojom_bindings_generator.py'); + final script = path.join( + data.mojoSdk.path, 'tools', 'bindings', 'mojom_bindings_generator.py'); final sdkInc = path.normalize(path.join(data.mojoSdk.path, '..', '..')); - final outputDir = await data.mojomPackage.createTemp(); + final outputDir = await data.currentPackage.createTemp(); final output = outputDir.path; final arguments = [ - '--use_bundled_pylibs', - '-g', 'dart', - '-o', output, - // TODO(zra): Are other include paths needed? - '-I', sdkInc, - '-I', mojomDirectory.path, - mojom.path]; + '--use_bundled_pylibs', + '-g', + 'dart', + '-o', + output, + // TODO(zra): Are other include paths needed? + '-I', + sdkInc, + '-I', + mojomDirectory.path, + mojom.path + ]; if (verbose || dryRun) { print('Generating $mojom'); @@ -93,12 +91,20 @@ generateAction(GenerateIterData data, Directory mojomDirectory) async { if (result.exitCode != 0) { throw new GenerationError("$script failed:\n${result.stderr}"); } - // Generated .mojom.dart is under $output/dart-gen/mojom/lib/X - // Move X to $mojomPackage. Then rm -rf $output - final generatedDirName = path.join(output, 'dart-gen', 'mojom', 'lib'); - final generatedDir = new Directory(generatedDirName); - await copyAction(data, generatedDir); + // Generated .mojom.dart is under $output/dart-pkg/$PACKAGE/lib/$X + // Move $X to $PACKAGE_ROOT/$PACKAGE/$X + final generatedDirName = path.join(output, 'dart-pkg'); + final generatedDir = new Directory(generatedDirName); + await for (var genpack in generatedDir.list()) { + if (genpack is! Directory) continue; + var libDir = new Directory(path.join(genpack.path, 'lib')); + var name = path.relative(genpack.path, from: generatedDirName); + var copyData = new GenerateIterData(data.mojoSdk); + copyData.currentPackage = + new Directory(path.join(packageRoot.path, name)); + await copyAction(copyData, libDir); + } await outputDir.delete(recursive: true); } @@ -118,7 +124,7 @@ generateAction(GenerateIterData data, Directory mojomDirectory) async { /// ... /// /// Lines beginning with '#' are ignored. -downloadAction(GenerateIterData data, Directory packageDirectory) async { +downloadAction(GenerateIterData _, Directory packageDirectory) async { var mojomsPath = path.join(packageDirectory.path, '.mojoms'); var mojomsFile = new File(mojomsPath); if (!await mojomsFile.exists()) return; @@ -144,8 +150,7 @@ downloadAction(GenerateIterData data, Directory packageDirectory) async { } repoRoot = rootWords[1]; if (verbose) print("Found repo root: $repoRoot"); - if (!repoRoot.startsWith('http://') && - !repoRoot.startsWith('https://')) { + if (!repoRoot.startsWith('http://') && !repoRoot.startsWith('https://')) { throw new DownloadError( 'Mojom repo "root" should be an http or https URL: $line'); } @@ -173,124 +178,50 @@ downloadAction(GenerateIterData data, Directory packageDirectory) async { } } -/// Ensures that the directories in [additionalPaths] are absolute and exist, -/// and creates Directories for them, which are returned. -Future> validateAdditionalDirs(Iterable additionalPaths) async { - var additionalDirs = []; - for (var mojomPath in additionalPaths) { - final mojomDir = new Directory(mojomPath); - if (!mojomDir.isAbsolute) { - throw new CommandLineError( - "All --additional-mojom-dir parameters must be absolute paths."); - } - if (!(await mojomDir.exists())) { - throw new CommandLineError( - "The additional mojom directory $mojomDir must exist"); - } - additionalDirs.add(mojomDir); - } - if (verbose) print("additional_mojom_dirs = $additionalDirs"); - return additionalDirs; -} - -class GenerateOptions { - final Directory packages; - final Directory mojomPackage; - final Directory mojoSdk; - final List additionalDirs; - final bool download; - final bool generate; - GenerateOptions( - this.packages, this.mojomPackage, this.mojoSdk, this.additionalDirs, - this.download, this.generate); -} - -Future parseArguments(List arguments) async { - final parser = new args.ArgParser() - ..addOption('additional-mojom-dir', - abbr: 'a', - allowMultiple: true, - help: 'Absolute path to an additional directory containing mojom.dart' - 'files to put in the mojom package. May be specified multiple times.') - ..addFlag('download', - abbr: 'd', - defaultsTo: false, - help: 'Searches packages for a .mojoms file, and downloads .mojom files' - 'as speficied in that file. Implies -g.') - ..addFlag('fake', - abbr: 'f', - defaultsTo: false, - help: 'Print the operations that would have been run, but' - 'do not run anything.') - ..addFlag('generate', - abbr: 'g', - defaultsTo: false, - help: 'Generate Dart bindings for .mojom files.') - ..addOption('mojo-sdk', - abbr: 'm', - defaultsTo: Platform.environment['MOJO_SDK'], - help: 'Absolute path to the Mojo SDK, which can also be specified ' - 'with the environment variable MOJO_SDK.') - ..addOption('package-root', - abbr: 'p', - defaultsTo: path.join(Directory.current.path, 'packages'), - help: 'An absolute path to an application\'s package root') - ..addFlag('verbose', abbr: 'v', defaultsTo: false); - final result = parser.parse(arguments); - verbose = result['verbose']; - dryRun = result['fake']; - - final packages = new Directory(result['package-root']); - if (!packages.isAbsolute) { - throw new CommandLineError( - "The --package-root parameter must be an absolute path."); - } - if (verbose) print("packages = $packages"); - if (!(await packages.exists())) { - throw new CommandLineError( - "The packages directory $packages must exist"); +/// The "mojom" entry in [packages] is a symbolic link to the mojom package in +/// the global pub cache directory. Because we might need to write package +/// specific .mojom.dart files into the mojom package, we need to make a local +/// copy of it. +copyMojomPackage(Directory packages) async { + var link = new Link(path.join(packages.path, "mojom")); + if (!await link.exists()) { + // If the "mojom" entry in packages is not a symbolic link, then do nothing. + return; } - final mojomPackage = new Directory(path.join(packages.path, 'mojom')); - if (verbose) print("mojom package = $mojomPackage"); - if (!(await mojomPackage.exists())) { - throw new CommandLineError( - "The mojom package directory $mojomPackage must exist"); - } + var realpath = await link.resolveSymbolicLinks(); + var realDir = new Directory(realpath); + var mojomDir = new Directory(path.join(packages.path, "mojom")); - final download = result['download']; - final generate = result['generate'] || download; - var mojoSdk = null; - if (generate) { - final mojoSdkPath = result['mojo-sdk']; - if (mojoSdkPath == null) { - throw new CommandLineError( - "The Mojo SDK directory must be specified with the --mojo-sdk flag or" - "the MOJO_SDK environment variable."); - } - mojoSdk = new Directory(mojoSdkPath); - if (verbose) print("Mojo SDK = $mojoSdk"); - if (!(await mojoSdk.exists())) { - throw new CommandLineError( - "The specified Mojo SDK directory $mojoSdk must exist."); + await link.delete(); + await mojomDir.create(); + await for (var file in realDir.list(recursive: true)) { + if (file is File) { + var relative = path.relative(file.path, from: realDir.path); + var destPath = path.join(mojomDir.path, relative); + var destDir = new Directory(path.dirname(destPath)); + await destDir.create(recursive: true); + await file.copy(path.join(mojomDir.path, relative)); } } - - final additionalDirs = - await validateAdditionalDirs(result['additional-mojom-dir']); - - return new GenerateOptions( - packages, mojomPackage, mojoSdk, additionalDirs, download, generate); } main(List arguments) async { var options = await parseArguments(arguments); + duplicateDetection = new Map(); + errorOnDuplicate = options.errorOnDuplicate; + verbose = options.verbose; + dryRun = options.dryRun; - // Copy any pregenerated files form packages. - await mojomDirIter( - options.packages, - new PackageIterData(options.mojomPackage), - copyAction); + // mojoms without a DartPackage annotation, and pregenerated mojoms from + // [options.additionalDirs] will go into the mojom package, so we make a local + // copy of it so we don't pollute the global pub cache. + // + // TODO(zra): Fail if a mojom has no DartPackage annotation, and remove the + // need for [options.additionalDirs]. + if (!dryRun) { + await copyMojomPackage(options.packages); + } // Download .mojom files. These will be picked up by the generation step // below. @@ -300,14 +231,15 @@ main(List arguments) async { // Generate mojom files. if (options.generate) { - await mojomDirIter( - options.packages, - new GenerateIterData(options.mojoSdk, options.mojomPackage), + await mojomDirIter(options.packages, new GenerateIterData(options.mojoSdk), generateAction); } - // Copy pregenerated files from specified external directories. - final data = new GenerateIterData(options.mojoSdk, options.mojomPackage); + // TODO(zra): As mentioned above, this should go away. + // Copy pregenerated files from specified external directories into the + // mojom package. + final data = new GenerateIterData(options.mojoSdk); + data.currentPackage = options.mojomPackage; for (var mojomDir in options.additionalDirs) { await copyAction(data, mojomDir); if (options.generate) { diff --git a/mojo/dart/mojom/lib/src/options.dart b/mojo/dart/mojom/lib/src/options.dart new file mode 100644 index 00000000000..63ff6834170 --- /dev/null +++ b/mojo/dart/mojom/lib/src/options.dart @@ -0,0 +1,123 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of generate; + +class GenerateOptions { + final Directory packages; + final Directory mojoSdk; + final Directory mojomPackage; + final List additionalDirs; + final bool download; + final bool generate; + final bool errorOnDuplicate; + final bool verbose; + final bool dryRun; + GenerateOptions(this.packages, this.mojomPackage, this.mojoSdk, + this.additionalDirs, this.download, this.generate, this.errorOnDuplicate, + this.verbose, this.dryRun); +} + +/// Ensures that the directories in [additionalPaths] are absolute and exist, +/// and creates Directories for them, which are returned. +Future> validateAdditionalDirs(Iterable additionalPaths) async { + var additionalDirs = []; + for (var mojomPath in additionalPaths) { + final mojomDir = new Directory(mojomPath); + if (!mojomDir.isAbsolute) { + throw new CommandLineError( + "All --additional-mojom-dir parameters must be absolute paths."); + } + if (!(await mojomDir.exists())) { + throw new CommandLineError( + "The additional mojom directory $mojomDir must exist"); + } + additionalDirs.add(mojomDir); + } + return additionalDirs; +} + +Future parseArguments(List arguments) async { + final parser = new args.ArgParser() + ..addOption('additional-mojom-dir', + abbr: 'a', + allowMultiple: true, + help: 'Absolute path to an additional directory containing mojom.dart' + 'files to put in the mojom package. May be specified multiple times.') + ..addFlag('download', + abbr: 'd', + defaultsTo: false, + help: 'Searches packages for a .mojoms file, and downloads .mojom files' + 'as speficied in that file. Implies -g.') + ..addFlag('fake', + abbr: 'f', + defaultsTo: false, + help: 'Print the operations that would have been run, but' + 'do not run anything.') + ..addFlag('generate', + abbr: 'g', + defaultsTo: false, + help: 'Generate Dart bindings for .mojom files.') + ..addFlag('ignore-duplicates', + abbr: 'i', + defaultsTo: false, + help: 'Ignore generation of a .mojom.dart file into the same location ' + 'as an existing file. By default this is an error') + ..addOption('mojo-sdk', + abbr: 'm', + defaultsTo: Platform.environment['MOJO_SDK'], + help: 'Absolute path to the Mojo SDK, which can also be specified ' + 'with the environment variable MOJO_SDK.') + ..addOption('package-root', + abbr: 'p', + defaultsTo: path.join(Directory.current.path, 'packages'), + help: 'An absolute path to an application\'s package root') + ..addFlag('verbose', abbr: 'v', defaultsTo: false); + final result = parser.parse(arguments); + bool verbose = result['verbose']; + bool dryRun = result['fake']; + bool errorOnDuplicate = !result['ignore-duplicates']; + + final packages = new Directory(result['package-root']); + if (!packages.isAbsolute) { + throw new CommandLineError( + "The --package-root parameter must be an absolute path."); + } + if (verbose) print("packages = $packages"); + if (!(await packages.exists())) { + throw new CommandLineError("The packages directory $packages must exist"); + } + + final mojomPackage = new Directory(path.join(packages.path, 'mojom')); + if (verbose) print("mojom package = $mojomPackage"); + if (!(await mojomPackage.exists())) { + throw new CommandLineError( + "The mojom package directory $mojomPackage must exist"); + } + + final download = result['download']; + final generate = result['generate'] || download; + var mojoSdk = null; + if (generate) { + final mojoSdkPath = result['mojo-sdk']; + if (mojoSdkPath == null) { + throw new CommandLineError( + "The Mojo SDK directory must be specified with the --mojo-sdk flag or" + "the MOJO_SDK environment variable."); + } + mojoSdk = new Directory(mojoSdkPath); + if (verbose) print("Mojo SDK = $mojoSdk"); + if (!(await mojoSdk.exists())) { + throw new CommandLineError( + "The specified Mojo SDK directory $mojoSdk must exist."); + } + } + + final additionalDirs = + await validateAdditionalDirs(result['additional-mojom-dir']); + if (verbose) print("additional_mojom_dirs = $additionalDirs"); + + return new GenerateOptions(packages, mojomPackage, mojoSdk, additionalDirs, + download, generate, errorOnDuplicate, verbose, dryRun); +} diff --git a/mojo/dart/mojom/lib/src/utils.dart b/mojo/dart/mojom/lib/src/utils.dart index 65ba2485a7a..c62c1bab785 100644 --- a/mojo/dart/mojom/lib/src/utils.dart +++ b/mojo/dart/mojom/lib/src/utils.dart @@ -30,17 +30,15 @@ class DownloadError extends Error { /// The base type of data passed to actions for [mojomDirIter]. class PackageIterData { - final Directory _mojomPackage; - PackageIterData(this._mojomPackage); - Directory get mojomPackage => _mojomPackage; + Directory currentPackage; + PackageIterData(this.currentPackage); } /// Data for [mojomDirIter] that includes the path to the Mojo SDK for bindings /// generation. class GenerateIterData extends PackageIterData { final Directory _mojoSdk; - GenerateIterData(this._mojoSdk, Directory mojomPackage) - : super(mojomPackage); + GenerateIterData(this._mojoSdk) : super(null); Directory get mojoSdk => _mojoSdk; } @@ -51,6 +49,9 @@ packageDirIter( Directory packages, PackageIterData data, MojomAction action) async { await for (var package in packages.list()) { if (package is Directory) { + if (data != null) { + data.currentPackage = package; + } await action(data, package); } } @@ -64,7 +65,6 @@ packageDirIter( mojomDirIter( Directory packages, PackageIterData data, MojomAction action) async { await packageDirIter(packages, data, (d, p) async { - if (p.path == d.mojomPackage.path) return; if (verbose) print("package = $p"); final mojomDirectory = new Directory(path.join(p.path, 'mojom')); if (verbose) print("looking for = $mojomDirectory"); @@ -94,7 +94,7 @@ Future getUrl(HttpClient httpClient, String url) async { fileString.write(contents); } return fileString.toString(); - } catch(e) { + } catch (e) { throw new DownloadError("$e"); } } diff --git a/mojo/dart/mojom/test/generate_test.dart b/mojo/dart/mojom/test/generate_test.dart index e11678a3e42..0ddc862a9ee 100644 --- a/mojo/dart/mojom/test/generate_test.dart +++ b/mojo/dart/mojom/test/generate_test.dart @@ -9,6 +9,7 @@ import 'package:path/path.dart' as path; import 'package:unittest/unittest.dart'; final mojomContents = ''' +[DartPackage="generated"] module generated; struct Transform { @@ -18,6 +19,7 @@ struct Transform { '''; final dldMojomContents1 = ''' +[DartPackage="downloaded"] module downloaded; struct Downloaded1 { @@ -26,6 +28,7 @@ struct Downloaded1 { '''; final dldMojomContents2 = ''' +[DartPackage="downloaded"] module downloaded; struct Downloaded2 { @@ -48,22 +51,28 @@ main() async { final scriptPath = path.dirname(Platform.script.path); final testPackagePath = path.join(scriptPath, 'test_packages'); final testMojomPath = path.join(testPackagePath, 'mojom'); + final testMojomLinkPath = path.join(scriptPath, 'mojom_link_target'); + final testMojomLibPath = path.join(testMojomLinkPath, 'lib'); + final fakeGeneratePath = path.join(testMojomLibPath, 'generate.dart'); final pregenPath = path.join(testPackagePath, 'pregen'); final pregenFilePath = path.join(pregenPath, 'mojom', 'pregen', 'pregen.mojom.dart'); final additionalRootPath = path.join(scriptPath, 'additional_dir'); - final additionalPath = path.join( - additionalRootPath, 'additional', 'additional.mojom.dart'); + final additionalPath = + path.join(additionalRootPath, 'additional', 'additional.mojom.dart'); + + final generatedPackagePath = path.join(testPackagePath, 'generated'); final downloadedPackagePath = path.join(testPackagePath, 'downloaded'); final dotMojomsPath = path.join(downloadedPackagePath, '.mojoms'); setUp(() async { - await new Directory(testMojomPath).create(recursive: true); await new File(pregenFilePath).create(recursive: true); await new File(additionalPath).create(recursive: true); + await new File(fakeGeneratePath).create(recursive: true); + await new Link(testMojomPath).create(testMojomLibPath); final generatedMojomFile = new File(path.join(testPackagePath, 'generated', 'mojom', 'generated', 'public', 'interfaces', 'generated.mojom')); @@ -76,46 +85,54 @@ main() async { tearDown(() async { await new Directory(additionalRootPath).delete(recursive: true); await new Directory(testPackagePath).delete(recursive: true); + await new Directory(testMojomLinkPath).delete(recursive: true); }); group('No Download', () { - test('Copy', () async { + test('No-op', () async { await generate.main(['-p', testPackagePath, '-m', mojoSdk]); - final pregenFile = new File( - path.join(testMojomPath, 'pregen', 'pregen.mojom.dart')); - expect(await pregenFile.exists(), isTrue); + final mojomPackageDir = new Directory(testMojomPath); + final generateFile = new File(path.join(testMojomPath, 'generate.dart')); + expect(await mojomPackageDir.exists(), isTrue); + expect(await generateFile.exists(), isTrue); }); - test('Copy and Additional', () async { - await generate.main(['-p', testPackagePath, '-m', mojoSdk, - '-a', additionalRootPath]); + test('Additional', () async { + await generate.main( + ['-p', testPackagePath, '-m', mojoSdk, '-a', additionalRootPath]); + final mojomPackageDir = new Directory(testMojomPath); + final generateFile = new File(path.join(testMojomPath, 'generate.dart')); final additionalFile = new File( path.join(testMojomPath, 'additional', 'additional.mojom.dart')); + expect(await mojomPackageDir.exists(), isTrue); + expect(await generateFile.exists(), isTrue); expect(await additionalFile.exists(), isTrue); }); - test('Copy and Generate', () async { + test('Generated', () async { await generate.main(['-g', '-p', testPackagePath, '-m', mojoSdk]); final generatedFile = new File( - path.join(testMojomPath, 'generated', 'generated.mojom.dart')); + path.join(generatedPackagePath, 'generated', 'generated.mojom.dart')); expect(await generatedFile.exists(), isTrue); }); test('All', () async { await generate.main([ - '-g', '-p', testPackagePath, '-m', mojoSdk, - '-a', additionalRootPath]); - - final pregenFile = new File( - path.join(testMojomPath, 'pregen', 'pregen.mojom.dart')); - expect(await pregenFile.exists(), isTrue); + '-g', + '-p', + testPackagePath, + '-m', + mojoSdk, + '-a', + additionalRootPath + ]); final additionalFile = new File( path.join(testMojomPath, 'additional', 'additional.mojom.dart')); expect(await additionalFile.exists(), isTrue); final generatedFile = new File( - path.join(testMojomPath, 'generated', 'generated.mojom.dart')); + path.join(generatedPackagePath, 'generated', 'generated.mojom.dart')); expect(await generatedFile.exists(), isTrue); }); }); @@ -149,8 +166,8 @@ main() async { "root: http://localhost:${httpServer.port}\n" "path/to/mojom/download_one.mojom\n"); await generate.main(['-p', testPackagePath, '-m', mojoSdk, '-d', '-g']); - final downloadedFile = new File( - path.join(testMojomPath, 'downloaded', 'download_one.mojom.dart')); + final downloadedFile = new File(path.join( + downloadedPackagePath, 'downloaded', 'download_one.mojom.dart')); expect(await downloadedFile.exists(), isTrue); await mojomsFile.delete(); }); @@ -163,11 +180,11 @@ main() async { "path/to/mojom/download_one.mojom\n" "path/to/mojom/download_two.mojom\n"); await generate.main(['-p', testPackagePath, '-m', mojoSdk, '-d', '-g']); - final downloaded1File = new File( - path.join(testMojomPath, 'downloaded', 'download_one.mojom.dart')); + final downloaded1File = new File(path.join( + downloadedPackagePath, 'downloaded', 'download_one.mojom.dart')); expect(await downloaded1File.exists(), isTrue); - final downloaded2File = new File( - path.join(testMojomPath, 'downloaded', 'download_two.mojom.dart')); + final downloaded2File = new File(path.join( + downloadedPackagePath, 'downloaded', 'download_two.mojom.dart')); expect(await downloaded2File.exists(), isTrue); await mojomsFile.delete(); }); @@ -181,11 +198,11 @@ main() async { "root: http://localhost:${httpServer.port}\n" "path/to/mojom/download_two.mojom\n"); await generate.main(['-p', testPackagePath, '-m', mojoSdk, '-d', '-g']); - final downloaded1File = new File( - path.join(testMojomPath, 'downloaded', 'download_one.mojom.dart')); + final downloaded1File = new File(path.join( + downloadedPackagePath, 'downloaded', 'download_one.mojom.dart')); expect(await downloaded1File.exists(), isTrue); - final downloaded2File = new File( - path.join(testMojomPath, 'downloaded', 'download_two.mojom.dart')); + final downloaded2File = new File(path.join( + downloadedPackagePath, 'downloaded', 'download_two.mojom.dart')); expect(await downloaded2File.exists(), isTrue); await mojomsFile.delete(); }); @@ -193,15 +210,14 @@ main() async { test('simple-comment', () async { final mojomsFile = new File(dotMojomsPath); await mojomsFile.create(recursive: true); - await mojomsFile.writeAsString( - "# Comments are allowed\n" + await mojomsFile.writeAsString("# Comments are allowed\n" " root: http://localhost:${httpServer.port}\n\n\n\n" " # Here too\n" " path/to/mojom/download_one.mojom\n" "# And here\n"); await generate.main(['-p', testPackagePath, '-m', mojoSdk, '-d', '-g']); - final downloadedFile = new File( - path.join(testMojomPath, 'downloaded', 'download_one.mojom.dart')); + final downloadedFile = new File(path.join( + downloadedPackagePath, 'downloaded', 'download_one.mojom.dart')); expect(await downloadedFile.exists(), isTrue); await mojomsFile.delete(); }); @@ -224,7 +240,7 @@ main() async { }); group('Failures', () { - test('Bad Package Root',() async { + test('Bad Package Root', () async { final dummyPackageRoot = path.join(scriptPath, 'dummyPackageRoot'); var fail = false; try { @@ -250,8 +266,8 @@ main() async { final dummyAdditional = path.join(scriptPath, 'dummyAdditional'); var fail = false; try { - await generate.main(['-a', dummyAdditional, '-p', testPackagePath, - '-m', mojoSdk]); + await generate.main( + ['-a', dummyAdditional, '-p', testPackagePath, '-m', mojoSdk]); } on generate.CommandLineError { fail = true; } @@ -262,8 +278,8 @@ main() async { final dummyAdditional = 'dummyAdditional'; var fail = false; try { - await generate.main(['-a', dummyAdditional, '-p', testPackagePath, - '-m', mojoSdk]); + await generate.main( + ['-a', dummyAdditional, '-p', testPackagePath, '-m', mojoSdk]); } on generate.CommandLineError { fail = true; } @@ -299,8 +315,7 @@ main() async { test('Download No Server', () async { final mojomsFile = new File(dotMojomsPath); await mojomsFile.create(recursive: true); - await mojomsFile.writeAsString( - "root: http://localhots\n" + await mojomsFile.writeAsString("root: http://localhots\n" "path/to/mojom/download_one.mojom\n"); var fail = false; try { @@ -315,8 +330,7 @@ main() async { test('.mojoms no root', () async { final mojomsFile = new File(dotMojomsPath); await mojomsFile.create(recursive: true); - await mojomsFile.writeAsString( - "path/to/mojom/download_one.mojom\n"); + await mojomsFile.writeAsString("path/to/mojom/download_one.mojom\n"); var fail = false; try { await generate.main(['-p', testPackagePath, '-m', mojoSdk, '-d', '-g']); @@ -330,8 +344,7 @@ main() async { test('.mojoms blank root', () async { final mojomsFile = new File(dotMojomsPath); await mojomsFile.create(recursive: true); - await mojomsFile.writeAsString( - "root:\n" + await mojomsFile.writeAsString("root:\n" "path/to/mojom/download_one.mojom\n"); var fail = false; try { @@ -346,8 +359,7 @@ main() async { test('.mojoms root malformed', () async { final mojomsFile = new File(dotMojomsPath); await mojomsFile.create(recursive: true); - await mojomsFile.writeAsString( - "root: gobledygook\n" + await mojomsFile.writeAsString("root: gobledygook\n" "path/to/mojom/download_one.mojom\n"); var fail = false; try { @@ -362,8 +374,7 @@ main() async { test('.mojoms root without mojom', () async { final mojomsFile = new File(dotMojomsPath); await mojomsFile.create(recursive: true); - await mojomsFile.writeAsString( - "root: http://localhost\n" + await mojomsFile.writeAsString("root: http://localhost\n" "root: http://localhost\n" "path/to/mojom/download_one.mojom\n"); var fail = false; diff --git a/mojo/devtools/common/README.md b/mojo/devtools/common/README.md index 7a2745b12da..eb7d5c683a1 100644 --- a/mojo/devtools/common/README.md +++ b/mojo/devtools/common/README.md @@ -21,10 +21,60 @@ Additionally, `remote_adb_setup` script helps to configure adb on a remote machine to communicate with a device attached to a local machine, forwarding the ports used by `mojo_run`. +### Runner + +`mojo_run` allows you to run a Mojo shell either on the host, or on an attached +Android device. + +```sh +mojo_run APP_URL # Run on the host. +mojo_run APP_URL --android # Run on Android device. +mojo_run "APP_URL APP_ARGUMENTS" # Run an app with startup arguments +``` + +Unless running within a Mojo checkout, we need to indicate the path to the shell +binary: + +```sh +mojo_run --shell-path path/to/shell/binary APP_URL +``` + +Some applications are running embedded inside a window manager. To start such an +app, you have to first start the window manager app, then have it embed the app +you are interested in. It is done as follows using the default window manager: + +```sh +mojo_run "mojo:window_manager APP_URL" +``` + +By default, `mojo_run` uses `mojo:kiosk_wm` as the default window manager. It +can be changed using the `--window-manager` flag. + +#### Sky apps + +To run a [Sky](https://github.com/domokit/sky_engine) app, you need to build +`sky_viewer.mojo` in a Sky checkout, and indicate the path to the binary using +the `--map-url` parameter: + +```sh +mojo_run --map-url mojo:sky_viewer=/path/to/sky/viewer "mojo:window_manager APP_URL" +``` + +If the app does not declare a shebang indicating that it needs to be run in +`sky_viewer`, pass `--sky` to map `sky_viewer` as a default content handler for +dart apps: + +```sh +mojo_run --map-url mojo:sky_viewer=/path/to/sky/viewer "mojo:window_manager APP_URL" --sky +``` + +Note that Sky apps will need the --use-osmesa flag to run +over [chromoting](https://support.google.com/chrome/answer/1649523?hl=en): + ### Debugger -The `mojo_debug` script allows you to interactively inspect a running shell, -collect performance traces and attach a gdb debugger. +`mojo_debug` allows you to interactively inspect a running shell, collect +performance traces and attach a gdb debugger. #### Tracing To collect [performance @@ -65,6 +115,10 @@ after changing threads), use the `current` option: (gdb) update-symbols current ``` +If you want to debug the startup of your application, you can pass +`--wait-for-debugger` to `mojo_run` to have the Mojo Shell stop and wait to be +attached by `gdb` before continuing. + #### Android crash stacks When Mojo shell crashes on Android ("Unfortunately, Mojo shell has stopped.") due to a crash in native code, `mojo_debug` can be used to find and symbolize @@ -74,14 +128,6 @@ the stack trace present in the device log: mojo_debug device stack ``` -### devtoolslib - -**devtoolslib** is a Python module containing the core scripting functionality -for running Mojo apps: shell abstraction with implementations for Android and -Linux and support for apptest frameworks. The executable scripts in devtools are -based on this module. One can also choose to embed the functionality provided by -**devtoolslib** in their own wrapper. - ## Development The library is canonically developed [in the mojo diff --git a/mojo/devtools/common/devtoolslib/android_shell.py b/mojo/devtools/common/devtoolslib/android_shell.py index 7a7436f17e9..e07ce773a43 100644 --- a/mojo/devtools/common/devtoolslib/android_shell.py +++ b/mojo/devtools/common/devtoolslib/android_shell.py @@ -18,6 +18,7 @@ import time from devtoolslib.http_server import start_http_server from devtoolslib.shell import Shell +from devtoolslib.utils import overrides # Tags used by mojo shell Java logging. @@ -81,7 +82,7 @@ class AndroidShell(Shell): self.additional_logcat_tags = logcat_tags self.verbose_pipe = verbose_pipe if verbose_pipe else open(os.devnull, 'w') - def _AdbCommand(self, args): + def _adb_command(self, args): """Forms an adb command from the given arguments, prepending the adb path and adding a target device specifier, if needed. """ @@ -91,18 +92,18 @@ class AndroidShell(Shell): adb_command.extend(args) return adb_command - def _ReadFifo(self, fifo_path, pipe, on_fifo_closed, max_attempts=5): + def _read_fifo(self, fifo_path, pipe, on_fifo_closed, max_attempts=5): """Reads |fifo_path| on the device and write the contents to |pipe|. Calls |on_fifo_closed| when the fifo is closed. This method will try to find the path up to |max_attempts|, waiting 1 second between each attempt. If it cannot find |fifo_path|, a exception will be raised. """ - fifo_command = self._AdbCommand( + fifo_command = self._adb_command( ['shell', 'test -e "%s"; echo $?' % fifo_path]) - def Run(): - def _WaitForFifo(): + def _run(): + def _wait_for_fifo(): for _ in xrange(max_attempts): if subprocess.check_output(fifo_command)[0] == '0': return @@ -110,23 +111,23 @@ class AndroidShell(Shell): if on_fifo_closed: on_fifo_closed() raise Exception("Unable to find fifo.") - _WaitForFifo() + _wait_for_fifo() stdout_cat = subprocess.Popen( - self._AdbCommand(['shell', 'cat', fifo_path]), stdout=pipe) + self._adb_command(['shell', 'cat', fifo_path]), stdout=pipe) atexit.register(_exit_if_needed, stdout_cat) stdout_cat.wait() if on_fifo_closed: on_fifo_closed() - thread = threading.Thread(target=Run, name="StdoutRedirector") + thread = threading.Thread(target=_run, name="StdoutRedirector") thread.start() - def _FindAvailableDevicePort(self): + def _find_available_device_port(self): netstat_output = subprocess.check_output( - self._AdbCommand(['shell', 'netstat'])) + self._adb_command(['shell', 'netstat'])) return _find_available_port(netstat_output) - def _ForwardDevicePortToHost(self, device_port, host_port): + def _forward_device_port_to_host(self, device_port, host_port): """Maps the device port to the host port. If |device_port| is 0, a random available port is chosen. @@ -138,22 +139,22 @@ class AndroidShell(Shell): # value), but if we can run adb as root, we have to do it now, because # restarting adbd as root clears any port mappings. See # https://github.com/domokit/devtools/issues/20. - self._RunAdbAsRoot() + self._run_adb_as_root() if device_port == 0: # TODO(ppi): Should we have a retry loop to handle the unlikely races? - device_port = self._FindAvailableDevicePort() - subprocess.check_call(self._AdbCommand([ + device_port = self._find_available_device_port() + subprocess.check_call(self._adb_command([ "reverse", "tcp:%d" % device_port, "tcp:%d" % host_port])) - def _UnmapPort(): - unmap_command = self._AdbCommand([ + def _unmap_port(): + unmap_command = self._adb_command([ "reverse", "--remove", "tcp:%d" % device_port]) subprocess.Popen(unmap_command) - atexit.register(_UnmapPort) + atexit.register(_unmap_port) return device_port - def _ForwardHostPortToDevice(self, host_port, device_port): + def _forward_host_port_to_device(self, host_port, device_port): """Maps the host port to the device port. If |host_port| is 0, a random available port is chosen. @@ -161,30 +162,30 @@ class AndroidShell(Shell): The host port. """ assert device_port - self._RunAdbAsRoot() + self._run_adb_as_root() if host_port == 0: # TODO(ppi): Should we have a retry loop to handle the unlikely races? host_port = _find_available_host_port() - subprocess.check_call(self._AdbCommand([ + subprocess.check_call(self._adb_command([ "forward", 'tcp:%d' % host_port, 'tcp:%d' % device_port])) - def _UnmapPort(): - unmap_command = self._AdbCommand([ + def _unmap_port(): + unmap_command = self._adb_command([ "forward", "--remove", "tcp:%d" % device_port]) subprocess.Popen(unmap_command) - atexit.register(_UnmapPort) + atexit.register(_unmap_port) return host_port - def _RunAdbAsRoot(self): + def _run_adb_as_root(self): if self.adb_running_as_root is not None: return self.adb_running_as_root if ('cannot run as root' not in subprocess.check_output( - self._AdbCommand(['root']))): + self._adb_command(['root']))): # Wait for adbd to restart. subprocess.check_call( - self._AdbCommand(['wait-for-device']), + self._adb_command(['wait-for-device']), stdout=self.verbose_pipe) self.adb_running_as_root = True else: @@ -192,13 +193,13 @@ class AndroidShell(Shell): return self.adb_running_as_root - def _IsShellPackageInstalled(self): + def _is_shell_package_installed(self): # Adb should print one line if the package is installed and return empty # string otherwise. - return len(subprocess.check_output(self._AdbCommand([ + return len(subprocess.check_output(self._adb_command([ 'shell', 'pm', 'list', 'packages', _MOJO_SHELL_PACKAGE_NAME]))) > 0 - def CheckDevice(self): + def check_device(self): """Verifies if the device configuration allows adb to run. If a target device was indicated in the constructor, it checks that the @@ -211,7 +212,7 @@ class AndroidShell(Shell): |result| is False and None otherwise. """ adb_devices_output = subprocess.check_output( - self._AdbCommand(['devices'])) + self._adb_command(['devices'])) # Skip the header line, strip empty lines at the end. device_list = [line.strip() for line in adb_devices_output.split('\n')[1:] if line.strip()] @@ -235,7 +236,7 @@ class AndroidShell(Shell): return True, None - def InstallApk(self, shell_apk_path): + def install_apk(self, shell_apk_path): """Installs the apk on the device. This method computes checksum of the APK and skips the installation if the @@ -248,14 +249,14 @@ class AndroidShell(Shell): device_sha1_path = '/sdcard/%s/%s.sha1' % (_MOJO_SHELL_PACKAGE_NAME, 'MojoShell') apk_sha1 = hashlib.sha1(open(shell_apk_path, 'rb').read()).hexdigest() - device_apk_sha1 = subprocess.check_output(self._AdbCommand([ + device_apk_sha1 = subprocess.check_output(self._adb_command([ 'shell', 'cat', device_sha1_path])) do_install = (apk_sha1 != device_apk_sha1 or - not self._IsShellPackageInstalled()) + not self._is_shell_package_installed()) if do_install: subprocess.check_call( - self._AdbCommand(['install', '-r', shell_apk_path, '-i', + self._adb_command(['install', '-r', shell_apk_path, '-i', _MOJO_SHELL_PACKAGE_NAME]), stdout=self.verbose_pipe) @@ -263,15 +264,15 @@ class AndroidShell(Shell): with tempfile.NamedTemporaryFile() as fp: fp.write(apk_sha1) fp.flush() - subprocess.check_call(self._AdbCommand(['push', fp.name, + subprocess.check_call(self._adb_command(['push', fp.name, device_sha1_path]), stdout=self.verbose_pipe) else: - # To ensure predictable state after running InstallApk(), we need to stop + # To ensure predictable state after running install_apk(), we need to stop # the shell here, as this is what "adb install" implicitly does. - self.StopShell() + self.stop_shell() - def StartShell(self, + def start_shell(self, arguments, stdout=None, on_application_stop=None): @@ -284,12 +285,12 @@ class AndroidShell(Shell): stdout: Valid argument for subprocess.Popen() or None. """ if not self.stop_shell_registered: - atexit.register(self.StopShell) + atexit.register(self.stop_shell) self.stop_shell_registered = True STDOUT_PIPE = "/data/data/%s/stdout.fifo" % _MOJO_SHELL_PACKAGE_NAME - cmd = self._AdbCommand(['shell', 'am', 'start', + cmd = self._adb_command(['shell', 'am', 'start', '-S', '-a', 'android.intent.action.VIEW', '-n', '%s/.MojoShellActivity' % @@ -299,13 +300,13 @@ class AndroidShell(Shell): if stdout or on_application_stop: # We need to run as root to access the fifo file we use for stdout # redirection. - if self._RunAdbAsRoot(): + if self._run_adb_as_root(): # Remove any leftover fifo file after the previous run. - subprocess.check_call(self._AdbCommand( + subprocess.check_call(self._adb_command( ['shell', 'rm', '-f', STDOUT_PIPE])) parameters.append('--fifo-path=%s' % STDOUT_PIPE) - self._ReadFifo(STDOUT_PIPE, stdout, on_application_stop) + self._read_fifo(STDOUT_PIPE, stdout, on_application_stop) else: _logger.warning("Running without root access, full stdout of the " "shell won't be available.") @@ -319,18 +320,18 @@ class AndroidShell(Shell): subprocess.check_call(cmd, stdout=self.verbose_pipe) - def StopShell(self): + def stop_shell(self): """Stops the mojo shell.""" - subprocess.check_call(self._AdbCommand(['shell', + subprocess.check_call(self._adb_command(['shell', 'am', 'force-stop', _MOJO_SHELL_PACKAGE_NAME])) - def CleanLogs(self): + def clean_logs(self): """Cleans the logs on the device.""" - subprocess.check_call(self._AdbCommand(['logcat', '-c'])) + subprocess.check_call(self._adb_command(['logcat', '-c'])) - def ShowLogs(self, include_native_logs=True): + def show_logs(self, include_native_logs=True): """Displays the log for the mojo shell. Returns: @@ -342,19 +343,19 @@ class AndroidShell(Shell): if self.additional_logcat_tags is not None: tags.extend(self.additional_logcat_tags.split(",")) logcat = subprocess.Popen( - self._AdbCommand(['logcat', '-s', ' '.join(tags)]), + self._adb_command(['logcat', '-s', ' '.join(tags)]), stdout=sys.stdout) atexit.register(_exit_if_needed, logcat) return logcat - def ForwardObservatoryPorts(self): + def forward_observatory_ports(self): """Forwards the ports used by the dart observatories to the host machine. """ - logcat = subprocess.Popen(self._AdbCommand(['logcat']), + logcat = subprocess.Popen(self._adb_command(['logcat']), stdout=subprocess.PIPE) atexit.register(_exit_if_needed, logcat) - def _ForwardObservatoriesAsNeeded(): + def _forward_observatories_as_needed(): while True: line = logcat.stdout.readline() if not line: @@ -363,80 +364,49 @@ class AndroidShell(Shell): line) if match: device_port = int(match.group(1)) - host_port = self._ForwardHostPortToDevice(0, device_port) + host_port = self._forward_host_port_to_device(0, device_port) print ("Dart observatory available at the host at http://127.0.0.1:%d" % host_port) - logcat_watch_thread = threading.Thread(target=_ForwardObservatoriesAsNeeded) + logcat_watch_thread = threading.Thread( + target=_forward_observatories_as_needed) logcat_watch_thread.start() - def ServeLocalDirectory(self, local_dir_path, port=0, - additional_mappings=None): - """Serves the content of the local (host) directory, making it available to - the shell under the url returned by the function. - - The server will run on a separate thread until the program terminates. The - call returns immediately. - - Args: - local_dir_path: path to the directory to be served - port: port at which the server will be available to the shell - additional_mappings: List of tuples (prefix, local_base_path) mapping - URLs that start with |prefix| to local directory at |local_base_path|. - The prefixes should skip the leading slash. - - Returns: - The url that the shell can use to access the content of |local_dir_path|. - """ + @overrides(Shell) + def serve_local_directory(self, local_dir_path, port=0): assert local_dir_path - server_address = start_http_server(local_dir_path, host_port=port, - additional_mappings=additional_mappings) + mappings = [('', [local_dir_path])] + server_address = start_http_server(mappings, host_port=port) - return 'http://127.0.0.1:%d/' % self._ForwardDevicePortToHost( + return 'http://127.0.0.1:%d/' % self._forward_device_port_to_host( port, server_address[1]) - def ForwardHostPortToShell(self, host_port): - """Forwards a port on the host machine to the same port wherever the shell - is running. + @overrides(Shell) + def serve_local_directories(self, mappings, port=0): + assert mappings + server_address = start_http_server(mappings, host_port=port) - This is a no-op if the shell is running locally. - """ - self._ForwardHostPortToDevice(host_port, host_port) + return 'http://127.0.0.1:%d/' % self._forward_device_port_to_host( + port, server_address[1]) - def Run(self, arguments): - """Runs the shell with given arguments until shell exits, passing the stdout - mingled with stderr produced by the shell onto the stdout. + @overrides(Shell) + def forward_host_port_to_shell(self, host_port): + self._forward_host_port_to_device(host_port, host_port) - Returns: - Exit code retured by the shell or None if the exit code cannot be - retrieved. - """ - self.CleanLogs() - self.ForwardObservatoryPorts() + @overrides(Shell) + def run(self, arguments): + self.clean_logs() + self.forward_observatory_ports() # If we are running as root, don't carry over the native logs from logcat - # we will have these in the stdout. - p = self.ShowLogs(include_native_logs=(not self._RunAdbAsRoot())) - self.StartShell(arguments, sys.stdout, p.terminate) + p = self.show_logs(include_native_logs=(not self._run_adb_as_root())) + self.start_shell(arguments, sys.stdout, p.terminate) p.wait() return None - def RunAndGetOutput(self, arguments, timeout=None): - """Runs the shell with given arguments until shell exits and returns the - output. - - Args: - arguments: list of arguments for the shell - timeout: maximum running time in seconds, after which the shell will be - terminated - - Returns: - A tuple of (return_code, output, did_time_out). |return_code| is the exit - code returned by the shell or None if the exit code cannot be retrieved. - |output| is the stdout mingled with the stderr produced by the shell. - |did_time_out| is True iff the shell was terminated because it exceeded - the |timeout| and False otherwise. - """ + @overrides(Shell) + def run_and_get_output(self, arguments, timeout=None): class Results: """Workaround for Python scoping rules that prevent assigning to variables from the outer scope. @@ -447,7 +417,7 @@ class AndroidShell(Shell): (r, w) = os.pipe() with os.fdopen(r, "r") as rf: with os.fdopen(w, "w") as wf: - self.StartShell(arguments, wf, wf.close) + self.start_shell(arguments, wf, wf.close) Results.output = rf.read() run_thread = threading.Thread(target=do_run) @@ -455,6 +425,6 @@ class AndroidShell(Shell): run_thread.join(timeout) if run_thread.is_alive(): - self.StopShell() + self.stop_shell() return None, Results.output, True return None, Results.output, False diff --git a/mojo/devtools/common/devtoolslib/apptest.py b/mojo/devtools/common/devtoolslib/apptest.py index 44c604306c8..59e23e2e007 100644 --- a/mojo/devtools/common/devtoolslib/apptest.py +++ b/mojo/devtools/common/devtoolslib/apptest.py @@ -53,7 +53,8 @@ def run_apptest(shell, shell_args, apptest_url, apptest_args, timeout, _logger.debug("Starting: " + command_line) start_time = time.time() - (exit_code, output, did_time_out) = shell.RunAndGetOutput(arguments, timeout) + (exit_code, output, did_time_out) = shell.run_and_get_output(arguments, + timeout) run_time = time.time() - start_time _logger.debug("Completed: " + command_line) diff --git a/mojo/devtools/common/devtoolslib/apptest_gtest.py b/mojo/devtools/common/devtoolslib/apptest_gtest.py index 4515e896751..fefeab73247 100644 --- a/mojo/devtools/common/devtoolslib/apptest_gtest.py +++ b/mojo/devtools/common/devtoolslib/apptest_gtest.py @@ -79,7 +79,7 @@ def get_fixtures(shell, shell_args, apptest): arguments.append("--args-for=%s %s" % (apptest, "--gtest_list_tests")) arguments.append(apptest) - (exit_code, output, did_time_out) = shell.RunAndGetOutput(arguments) + (exit_code, output, did_time_out) = shell.run_and_get_output(arguments) if exit_code or did_time_out: command_line = "mojo_shell " + " ".join(["%r" % x for x in arguments]) print "Failed to get test fixtures: %r" % command_line diff --git a/mojo/devtools/common/devtoolslib/http_server.py b/mojo/devtools/common/devtoolslib/http_server.py index 5199f3ee116..482e2db5310 100644 --- a/mojo/devtools/common/devtoolslib/http_server.py +++ b/mojo/devtools/common/devtoolslib/http_server.py @@ -14,6 +14,7 @@ import os.path import shutil import socket import threading +import tempfile import SimpleHTTPServer import SocketServer @@ -49,13 +50,15 @@ class _SilentTCPServer(SocketServer.TCPServer): SocketServer.TCPServer.handle_error(self, request, client_address) -def _GetHandlerClassForPath(mappings): +def _get_handler_class_for_path(mappings): """Creates a handler override for SimpleHTTPServer. Args: - mappings: List of tuples (prefix, local_base_path), mapping path prefixes - without the leading slash to local filesystem directory paths. The first - matching prefix will be used each time. + mappings: List of tuples (prefix, local_base_path_list) mapping URLs that + start with |prefix| to one or more local directories enumerated in + |local_base_path_list|. The prefixes should skip the leading slash. + The first matching prefix and the first location that contains the + requested file will be used each time. """ for prefix, _ in mappings: assert not prefix.startswith('/'), ('Prefixes for the http server mappings ' @@ -64,17 +67,20 @@ def _GetHandlerClassForPath(mappings): class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): """Handler for SocketServer.TCPServer that will serve the files from local directiories over http. + + A new instance is created for each request. """ def __init__(self, *args, **kwargs): self.etag = None + self.gzipped_file = None SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, *args, **kwargs) def get_etag(self): if self.etag: return self.etag - path = self.translate_path(self.path) + path = self.translate_path(self.path, False) if not os.path.isfile(path): return None @@ -130,29 +136,34 @@ def _GetHandlerClassForPath(mappings): return SimpleHTTPServer.SimpleHTTPRequestHandler.end_headers(self) - def translate_path(self, path): + # pylint: disable=W0221 + def translate_path(self, path, gzipped=True): # Parent translate_path() will strip away the query string and fragment # identifier, but also will prepend the cwd to the path. relpath() gives # us the relative path back. normalized_path = os.path.relpath( SimpleHTTPServer.SimpleHTTPRequestHandler.translate_path(self, path)) - for prefix, local_base_path in mappings: + for prefix, local_base_path_list in mappings: if normalized_path.startswith(prefix): - result = os.path.join(local_base_path, normalized_path[len(prefix):]) - if os.path.isfile(result): - gz_result = result + '.gz' - if (not os.path.isfile(gz_result) or - os.path.getmtime(gz_result) <= os.path.getmtime(result)): - with open(result, 'rb') as f: - with gzip.open(gz_result, 'wb') as zf: - shutil.copyfileobj(f, zf) - result = gz_result - return result - - # This class is only used internally, and we're adding a catch-all '' - # prefix at the end of |mappings|. - assert False + for local_base_path in local_base_path_list: + candidate = os.path.join(local_base_path, + normalized_path[len(prefix):]) + if os.path.isfile(candidate): + if gzipped: + if not self.gzipped_file: + self.gzipped_file = tempfile.NamedTemporaryFile(delete=False) + with open(candidate, 'rb') as source: + with gzip.GzipFile(fileobj=self.gzipped_file) as target: + shutil.copyfileobj(source, target) + self.gzipped_file.close() + return self.gzipped_file.name + return candidate + else: + self.send_response(404) + return None + self.send_response(404) + return None def guess_type(self, path): # This is needed so that exploded Sky apps without shebang can still run @@ -167,29 +178,31 @@ def _GetHandlerClassForPath(mappings): """Override the base class method to disable logging.""" pass + def __del__(self): + if self.gzipped_file: + os.remove(self.gzipped_file.name) + RequestHandler.protocol_version = 'HTTP/1.1' return RequestHandler -def start_http_server(local_dir_path, host_port=0, additional_mappings=None): +def start_http_server(mappings, host_port=0): """Starts an http server serving files from |local_dir_path| on |host_port|. Args: - local_dir_path: Path to the local filesystem directory to be served over - http under. + mappings: List of tuples (prefix, local_base_path_list) mapping URLs that + start with |prefix| to one or more local directories enumerated in + |local_base_path_list|. The prefixes should skip the leading slash. + The first matching prefix and the first location that contains the + requested file will be used each time. host_port: Port on the host machine to run the server on. Pass 0 to use a system-assigned port. - additional_mappings: List of tuples (prefix, local_base_path) mapping - URLs that start with |prefix| to local directory at |local_base_path|. - The prefixes should skip the leading slash. Returns: Tuple of the server address and the port on which it runs. """ - assert local_dir_path - mappings = additional_mappings if additional_mappings else [] - mappings.append(('', local_dir_path)) - handler_class = _GetHandlerClassForPath(mappings) + assert mappings + handler_class = _get_handler_class_for_path(mappings) try: httpd = _SilentTCPServer(('127.0.0.1', host_port), handler_class) @@ -198,14 +211,11 @@ def start_http_server(local_dir_path, host_port=0, additional_mappings=None): http_thread = threading.Thread(target=httpd.serve_forever) http_thread.daemon = True http_thread.start() - print 'Started http://%s:%d to host %s.' % (httpd.server_address[0], - httpd.server_address[1], - local_dir_path) return httpd.server_address except socket.error as v: error_code = v[0] print 'Failed to start http server for %s on port %d: %s.' % ( - local_dir_path, host_port, os.strerror(error_code)) + str(mappings), host_port, os.strerror(error_code)) if error_code == errno.EADDRINUSE: print (' Run `fuser %d/tcp` to find out which process is using the port.' % host_port) diff --git a/mojo/devtools/common/devtoolslib/http_server_unittest.py b/mojo/devtools/common/devtoolslib/http_server_unittest.py new file mode 100644 index 00000000000..f279712ce63 --- /dev/null +++ b/mojo/devtools/common/devtoolslib/http_server_unittest.py @@ -0,0 +1,140 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import StringIO +import gzip +import imp +import os.path +import shutil +import sys +import tempfile +import unittest +import urllib2 + +try: + imp.find_module('devtoolslib') +except ImportError: + sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from devtoolslib import http_server + + +class HttpServerTest(unittest.TestCase): + """Tests for the http_server module.""" + + def setUp(self): + """Creates a tree of temporary directories and files of the form described + below. + + parent_dir + hello_dir + hello_subdir + hello_file + other_dir + other_file + """ + self.parent_dir = tempfile.mkdtemp() + self.parent_file = tempfile.NamedTemporaryFile(delete=False, + dir=self.parent_dir) + self.hello_dir = tempfile.mkdtemp(dir=self.parent_dir) + self.hello_subdir = tempfile.mkdtemp(dir=self.hello_dir) + self.hello_file = tempfile.NamedTemporaryFile(delete=False, + dir=self.hello_subdir) + self.hello_file.write('hello') + self.hello_file.close() + + self.other_dir = tempfile.mkdtemp(dir=self.parent_dir) + self.other_file = tempfile.NamedTemporaryFile(delete=False, + dir=self.other_dir) + + def tearDown(self): + shutil.rmtree(self.parent_dir) + + def test_mappings(self): + """Maps two directories and verifies that the server serves files placed + there. + """ + mappings = [ + ('hello/', [self.hello_dir]), + ('other/', [self.other_dir]), + ] + server_address = ('http://%s:%u/' % + http_server.start_http_server(mappings)) + + hello_relpath = os.path.relpath(self.hello_file.name, self.hello_dir) + hello_response = urllib2.urlopen(server_address + 'hello/' + + hello_relpath) + self.assertEquals(200, hello_response.getcode()) + + other_relpath = os.path.relpath(self.other_file.name, self.other_dir) + other_response = urllib2.urlopen(server_address + 'other/' + + other_relpath) + self.assertEquals(200, other_response.getcode()) + + def test_unmapped_path(self): + """Verifies that the server returns 404 when a request for unmapped url + prefix is made. + """ + mappings = [ + ('hello/', [self.hello_dir]), + ] + server_address = ('http://%s:%u/' % + http_server.start_http_server(mappings)) + + error_code = None + try: + urllib2.urlopen(server_address + 'unmapped/abc') + except urllib2.HTTPError as error: + error_code = error.code + self.assertEquals(404, error_code) + + def test_multiple_paths(self): + """Verfies mapping multiple local paths under the same url prefix.""" + mappings = [ + ('singularity/', [self.hello_dir, self.other_dir]), + ] + server_address = ('http://%s:%u/' % + http_server.start_http_server(mappings)) + + hello_relpath = os.path.relpath(self.hello_file.name, self.hello_dir) + hello_response = urllib2.urlopen(server_address + 'singularity/' + + hello_relpath) + self.assertEquals(200, hello_response.getcode()) + + other_relpath = os.path.relpath(self.other_file.name, self.other_dir) + other_response = urllib2.urlopen(server_address + 'singularity/' + + other_relpath) + self.assertEquals(200, other_response.getcode()) + + # Verify that a request for a file not present under any of the mapped + # directories results in 404. + error_code = None + try: + urllib2.urlopen(server_address + 'singularity/unavailable') + except urllib2.HTTPError as error: + error_code = error.code + self.assertEquals(404, error_code) + + def test_gzip(self): + """Verifies the gzip content encoding of the files being served.""" + mappings = [ + ('hello/', [self.hello_dir]), + ] + server_address = ('http://%s:%u/' % + http_server.start_http_server(mappings)) + + hello_relpath = os.path.relpath(self.hello_file.name, self.hello_dir) + hello_response = urllib2.urlopen(server_address + 'hello/' + + hello_relpath) + self.assertEquals(200, hello_response.getcode()) + self.assertTrue('Content-Encoding' in hello_response.info()) + self.assertEquals('gzip', hello_response.info().get('Content-Encoding')) + + content = gzip.GzipFile( + fileobj=StringIO.StringIO(hello_response.read())).read() + self.assertEquals('hello', content) + + +if __name__ == "__main__": + unittest.main() diff --git a/mojo/devtools/common/devtoolslib/linux_shell.py b/mojo/devtools/common/devtoolslib/linux_shell.py index a8c25dfa105..dedee5b3f5c 100644 --- a/mojo/devtools/common/devtoolslib/linux_shell.py +++ b/mojo/devtools/common/devtoolslib/linux_shell.py @@ -5,8 +5,9 @@ import subprocess import threading -from devtoolslib.shell import Shell from devtoolslib import http_server +from devtoolslib.shell import Shell +from devtoolslib.utils import overrides class LinuxShell(Shell): @@ -22,62 +23,26 @@ class LinuxShell(Shell): self.executable_path = executable_path self.command_prefix = command_prefix if command_prefix else [] - def ServeLocalDirectory(self, local_dir_path, port=0, - additional_mappings=None): - """Serves the content of the local (host) directory, making it available to - the shell under the url returned by the function. + @overrides(Shell) + def serve_local_directory(self, local_dir_path, port=0): + mappings = [('', [local_dir_path])] + return 'http://%s:%d/' % http_server.start_http_server(mappings, port) - The server will run on a separate thread until the program terminates. The - call returns immediately. + @overrides(Shell) + def serve_local_directories(self, mappings, port=0): + return 'http://%s:%d/' % http_server.start_http_server(mappings, port) - Args: - local_dir_path: path to the directory to be served - port: port at which the server will be available to the shell - additional_mappings: List of tuples (prefix, local_base_path) mapping - URLs that start with |prefix| to local directory at |local_base_path|. - The prefixes should skip the leading slash. - - Returns: - The url that the shell can use to access the content of |local_dir_path|. - """ - return 'http://%s:%d/' % http_server.start_http_server(local_dir_path, port, - additional_mappings) - - def ForwardHostPortToShell(self, host_port): - """Forwards a port on the host machine to the same port wherever the shell - is running. - - This is a no-op if the shell is running locally. - """ + @overrides(Shell) + def forward_host_port_to_shell(self, host_port): pass - def Run(self, arguments): - """Runs the shell with given arguments until shell exits, passing the stdout - mingled with stderr produced by the shell onto the stdout. - - Returns: - Exit code retured by the shell or None if the exit code cannot be - retrieved. - """ + @overrides(Shell) + def run(self, arguments): command = self.command_prefix + [self.executable_path] + arguments return subprocess.call(command, stderr=subprocess.STDOUT) - def RunAndGetOutput(self, arguments, timeout=None): - """Runs the shell with given arguments until shell exits and returns the - output. - - Args: - arguments: list of arguments for the shell - timeout: maximum running time in seconds, after which the shell will be - terminated - - Returns: - A tuple of (return_code, output, did_time_out). |return_code| is the exit - code returned by the shell or None if the exit code cannot be retrieved. - |output| is the stdout mingled with the stderr produced by the shell. - |did_time_out| is True iff the shell was terminated because it exceeded - the |timeout| and False otherwise. - """ + @overrides(Shell) + def run_and_get_output(self, arguments, timeout=None): command = self.command_prefix + [self.executable_path] + arguments p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) diff --git a/mojo/devtools/common/devtoolslib/paths.py b/mojo/devtools/common/devtoolslib/paths.py index 073783db8f7..502636a06d7 100644 --- a/mojo/devtools/common/devtoolslib/paths.py +++ b/mojo/devtools/common/devtoolslib/paths.py @@ -8,13 +8,14 @@ These functions allow devtools scripts to work out-of-the-box with regular Mojo checkouts. """ +import collections import os.path import sys -def find_ancestor_with(relpath): +def find_ancestor_with(relpath, start_path=None): """Returns the lowest ancestor of this file that contains |relpath|.""" - cur_dir_path = os.path.abspath(os.path.dirname(__file__)) + cur_dir_path = start_path or os.path.abspath(os.path.dirname(__file__)) while True: if os.path.exists(os.path.join(cur_dir_path, relpath)): return cur_dir_path @@ -26,13 +27,23 @@ def find_ancestor_with(relpath): return None -def infer_mojo_paths(is_android, is_debug, target_cpu): - """Infers the locations of select build output artifacts in a regular Mojo - checkout. +def find_within_ancestors(target_relpath, start_path=None): + """Returns the absolute path to |target_relpath| in the lowest ancestor of + |start_path| that contains it. + """ + ancestor = find_ancestor_with(target_relpath, start_path) + if not ancestor: + return None + return os.path.join(ancestor, target_relpath) + + +def infer_paths(is_android, is_debug, target_cpu): + """Infers the locations of select build output artifacts in a regular + Chromium-like checkout. This should grow thinner or disappear as we introduce + per-repo config files, see https://github.com/domokit/devtools/issues/28. Returns: - Tuple of path dictionary, error message. Only one of the two will be - not-None. + Defaultdict with the inferred paths. """ build_dir = (('android_' if is_android else '') + (target_cpu + '_' if target_cpu else '') + @@ -40,23 +51,19 @@ def infer_mojo_paths(is_android, is_debug, target_cpu): out_build_dir = os.path.join('out', build_dir) root_path = find_ancestor_with(out_build_dir) + paths = collections.defaultdict(lambda: None) if not root_path: - return None, ('Failed to find build directory: ' + out_build_dir) + return paths - paths = {} - paths['root'] = root_path build_dir_path = os.path.join(root_path, out_build_dir) - paths['build'] = build_dir_path + paths['build_dir_path'] = build_dir_path if is_android: - paths['shell'] = os.path.join(build_dir_path, 'apks', 'MojoShell.apk') - paths['adb'] = os.path.join(root_path, 'third_party', 'android_tools', + paths['shell_path'] = os.path.join(build_dir_path, 'apks', 'MojoShell.apk') + paths['adb_path'] = os.path.join(root_path, 'third_party', 'android_tools', 'sdk', 'platform-tools', 'adb') else: - paths['shell'] = os.path.join(build_dir_path, 'mojo_shell') - - paths['sky_packages'] = os.path.join(build_dir_path, 'gen', 'dart-pkg', - 'packages') - return paths, None + paths['shell_path'] = os.path.join(build_dir_path, 'mojo_shell') + return paths # Based on Chromium //tools/find_depot_tools.py. diff --git a/mojo/devtools/common/devtoolslib/shell.py b/mojo/devtools/common/devtoolslib/shell.py index 16f4669cd22..c142611f158 100644 --- a/mojo/devtools/common/devtoolslib/shell.py +++ b/mojo/devtools/common/devtoolslib/shell.py @@ -6,8 +6,7 @@ class Shell(object): """Represents an abstract Mojo shell.""" - def ServeLocalDirectory(self, local_dir_path, port=0, - additional_mappings=None): + def serve_local_directory(self, local_dir_path, port=0): """Serves the content of the local (host) directory, making it available to the shell under the url returned by the function. @@ -17,16 +16,33 @@ class Shell(object): Args: local_dir_path: path to the directory to be served port: port at which the server will be available to the shell - additional_mappings: List of tuples (prefix, local_base_path) mapping - URLs that start with |prefix| to local directory at |local_base_path|. - The prefixes should skip the leading slash. Returns: The url that the shell can use to access the content of |local_dir_path|. """ raise NotImplementedError() - def ForwardHostPortToShell(self, host_port): + def serve_local_directories(self, mappings, port=0): + """Serves the content of the local (host) directories, making it available + to the shell under the url returned by the function. + + The server will run on a separate thread until the program terminates. The + call returns immediately. + + Args: + mappings: List of tuples (prefix, local_base_path_list) mapping URLs that + start with |prefix| to one or more local directories enumerated in + |local_base_path_list|. The prefixes should skip the leading slash. + The first matching prefix and the first location that contains the + requested file will be used each time. + port: port at which the server will be available to the shell + + Returns: + The url that the shell can use to access the server. + """ + raise NotImplementedError() + + def forward_host_port_to_shell(self, host_port): """Forwards a port on the host machine to the same port wherever the shell is running. @@ -34,7 +50,7 @@ class Shell(object): """ raise NotImplementedError() - def Run(self, arguments): + def run(self, arguments): """Runs the shell with given arguments until shell exits, passing the stdout mingled with stderr produced by the shell onto the stdout. @@ -44,7 +60,7 @@ class Shell(object): """ raise NotImplementedError() - def RunAndGetOutput(self, arguments, timeout=None): + def run_and_get_output(self, arguments, timeout=None): """Runs the shell with given arguments until shell exits and returns the output. diff --git a/mojo/devtools/common/devtoolslib/shell_arguments.py b/mojo/devtools/common/devtoolslib/shell_arguments.py index 1f2a97f6d91..3863935b4b0 100644 --- a/mojo/devtools/common/devtoolslib/shell_arguments.py +++ b/mojo/devtools/common/devtoolslib/shell_arguments.py @@ -2,8 +2,10 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -"""Functions that configure the shell before it is run manipulating its argument -list. +"""Produces configured shell abstractions. + +This module knows how to produce a configured shell abstraction based on +shell_config.ShellConfig. """ import os.path @@ -12,12 +14,12 @@ import urlparse from devtoolslib.android_shell import AndroidShell from devtoolslib.linux_shell import LinuxShell +from devtoolslib.shell_config import ShellConfigurationException # When spinning up servers for local origins, we want to use predictable ports # so that caching works between subsequent runs with the same command line. _LOCAL_ORIGIN_PORT = 31840 _MAPPINGS_BASE_PORT = 31841 -_SKY_SERVER_PORT = 9998 def _is_web_url(dest): @@ -34,7 +36,7 @@ def _host_local_url_destination(shell, dest_file, port): if not os.path.exists(directory): raise ValueError('local path passed as --map-url destination ' 'does not exist') - server_url = shell.ServeLocalDirectory(directory, port) + server_url = shell.serve_local_directory(directory, port) return server_url + os.path.relpath(dest_file, directory) @@ -44,7 +46,7 @@ def _host_local_origin_destination(shell, dest_dir, port): Returns: Url of the hosted directory. """ - return shell.ServeLocalDirectory(dest_dir, port) + return shell.serve_local_directory(dest_dir, port) def _rewrite(mapping, host_destination_functon, shell, port): @@ -100,45 +102,25 @@ def _apply_mappings(shell, original_arguments, map_urls, map_origins): return args -def _configure_sky(shell, root_path, sky_packages_path, sky_target): - """Configures additional mappings and a server needed to run the given Sky +def _configure_sky(shell_args): + """Maps mojo:sky_viewer as a content handler for dart applications. app. Args: - root_path: Local path to the root from which Sky apps will be served. - sky_packages_path: Local path to the root from which Sky packages will be - served. - sky_target: Path to the Sky app to be run, relative to |root_path|. + shell_args: Current list of shell arguments. Returns: - Arguments that need to be appended to the shell argument list. + Updated list of shell arguments. """ - # Configure a server to serve the checkout root at / (so that Sky examples - # are accessible using a root-relative path) and Sky packages at /packages. - # This is independent from the server that potentially serves the origin - # directory containing the mojo: apps. - additional_mappings = [ - ('packages/', sky_packages_path), - ] - server_url = shell.ServeLocalDirectory(root_path, port=_SKY_SERVER_PORT, - additional_mappings=additional_mappings) - - args = [] # Configure the content type mappings for the sky_viewer. This is needed - # only for the Sky apps that do not declare mojo:sky_viewer in a shebang, - # and it is unfortunate as it configures the shell to map all items of the - # application/dart content-type as Sky apps. + # only for the Sky apps that do not declare mojo:sky_viewer in a shebang. # TODO(ppi): drop this part once we can rely on the Sky files declaring # correct shebang. - args = append_to_argument(args, '--content-handlers=', + shell_args = append_to_argument(shell_args, '--content-handlers=', 'text/sky,mojo:sky_viewer') - args = append_to_argument(args, '--content-handlers=', + shell_args = append_to_argument(shell_args, '--content-handlers=', 'application/dart,mojo:sky_viewer') - - # Configure the window manager to embed the sky_viewer. - sky_url = server_url + sky_target - args.append('mojo:window_manager %s' % sky_url) - return args + return shell_args def configure_local_origin(shell, local_dir, fixed_port=True): @@ -149,7 +131,7 @@ def configure_local_origin(shell, local_dir, fixed_port=True): The list of arguments to be appended to the shell argument list. """ - origin_url = shell.ServeLocalDirectory( + origin_url = shell.serve_local_directory( local_dir, _LOCAL_ORIGIN_PORT if fixed_port else 0) return ["--origin=" + origin_url] @@ -184,89 +166,78 @@ def append_to_argument(arguments, key, value, delimiter=","): return arguments -def add_shell_arguments(parser): - """Adds argparse arguments allowing to configure shell abstraction using - configure_shell() below. - """ - # Arguments configuring the shell run. - parser.add_argument('--shell-path', help='Path of the Mojo shell binary.') - parser.add_argument('--android', help='Run on Android', - action='store_true') - parser.add_argument('--origin', help='Origin for mojo: URLs. This can be a ' - 'web url or a local directory path.') - parser.add_argument('--map-url', action='append', - help='Define a mapping for a url in the format ' - '=') - parser.add_argument('--map-origin', action='append', - help='Define a mapping for a url origin in the format ' - '=') - parser.add_argument('-v', '--verbose', action="store_true", - help="Increase output verbosity") - - android_group = parser.add_argument_group('Android-only', - 'These arguments apply only when --android is passed.') - android_group.add_argument('--adb-path', help='Path of the adb binary.') - android_group.add_argument('--target-device', help='Device to run on.') - android_group.add_argument('--logcat-tags', help='Comma-separated list of ' - 'additional logcat tags to display.') - - desktop_group = parser.add_argument_group('Desktop-only', - 'These arguments apply only when running on desktop.') - desktop_group.add_argument('--use-osmesa', action='store_true', - help='Configure the native viewport service ' - 'for off-screen rendering.') - - -class ShellConfigurationException(Exception): - """Represents an error preventing creating a functional shell abstraction.""" - pass - - -def configure_shell(config_args, shell_args): - """ - Produces a shell abstraction configured using the parsed arguments defined in - add_shell_arguments(). +def _configure_dev_server(shell, shell_args, dev_server_config): + """Sets up a dev server on the host according to |dev_server_config|. Args: - config_args: Parsed arguments added using add_shell_arguments(). + shell: The shell that is being configured. + shell_arguments: Current list of shell arguments. + dev_server_config: Instance of shell_config.DevServerConfig describing the + dev server to be set up. + + Returns: + The updated argument list. + """ + server_url = shell.serve_local_directories(dev_server_config.mappings) + shell_args.append('--map-origin=%s=%s' % (dev_server_config.host, server_url)) + print "Configured %s locally to serve:" % (dev_server_config.host) + for mapping_prefix, mapping_path in dev_server_config.mappings: + print " /%s -> %s" % (mapping_prefix, mapping_path) + return shell_args + + +def get_shell(shell_config, shell_args): + """ + Produces a shell abstraction configured according to |shell_config|. + + Args: + shell_config: Instance of shell_config.ShellConfig. shell_args: Additional raw shell arguments to be passed to the shell. We need to take these into account as some parameters need to appear only once on the argument list (e.g. url-mappings) so we need to coalesce any overrides and the existing value into just one argument. Returns: - A tuple of (shell, shell_args). + A tuple of (shell, shell_args). |shell| is the configured shell abstraction, + |shell_args| is updated list of shell arguments. Throws: ShellConfigurationException if shell abstraction could not be configured. """ - if config_args.android: - verbose_pipe = sys.stdout if config_args.verbose else None + if shell_config.android: + verbose_pipe = sys.stdout if shell_config.verbose else None - shell = AndroidShell(config_args.adb_path, config_args.target_device, - logcat_tags=config_args.logcat_tags, + shell = AndroidShell(shell_config.adb_path, shell_config.target_device, + logcat_tags=shell_config.logcat_tags, verbose_pipe=verbose_pipe) - device_status, error = shell.CheckDevice() + + device_status, error = shell.check_device() if not device_status: raise ShellConfigurationException('Device check failed: ' + error) - if config_args.shell_path: - shell.InstallApk(config_args.shell_path) + if shell_config.shell_path: + shell.install_apk(shell_config.shell_path) else: - if not config_args.shell_path: + if not shell_config.shell_path: raise ShellConfigurationException('Can not run without a shell binary. ' 'Please pass --shell-path.') - shell = LinuxShell(config_args.shell_path) - if config_args.use_osmesa: + shell = LinuxShell(shell_config.shell_path) + if shell_config.use_osmesa: shell_args.append('--args-for=mojo:native_viewport_service --use-osmesa') - shell_args = _apply_mappings(shell, shell_args, config_args.map_url, - config_args.map_origin) + shell_args = _apply_mappings(shell, shell_args, shell_config.map_url_list, + shell_config.map_origin_list) - if config_args.origin: - if _is_web_url(config_args.origin): - shell_args.append('--origin=' + config_args.origin) + if shell_config.origin: + if _is_web_url(shell_config.origin): + shell_args.append('--origin=' + shell_config.origin) else: - shell_args.extend(configure_local_origin(shell, config_args.origin, - fixed_port=True)) + shell_args.extend(configure_local_origin(shell, shell_config.origin, + fixed_port=True)) + + if shell_config.sky: + shell_args = _configure_sky(shell_args) + + for dev_server_config in shell_config.dev_servers: + shell_args = _configure_dev_server(shell, shell_args, dev_server_config) return shell, shell_args diff --git a/mojo/devtools/common/devtoolslib/shell_arguments_unittest.py b/mojo/devtools/common/devtoolslib/shell_arguments_unittest.py index d1b5f37720d..f86b163ce8b 100644 --- a/mojo/devtools/common/devtoolslib/shell_arguments_unittest.py +++ b/mojo/devtools/common/devtoolslib/shell_arguments_unittest.py @@ -17,7 +17,7 @@ from devtoolslib import shell_arguments class AppendToArgumentTest(unittest.TestCase): """Tests AppendToArgument().""" - def testAppendToEmpty(self): + def test_append_to_empty(self): arguments = [] key = '--something=' value = 'val' @@ -25,7 +25,7 @@ class AppendToArgumentTest(unittest.TestCase): self.assertEquals(expected_result, shell_arguments.append_to_argument( arguments, key, value)) - def testAppendToNonEmpty(self): + def test_append_to_non_empty(self): arguments = ['--other'] key = '--something=' value = 'val' @@ -33,7 +33,7 @@ class AppendToArgumentTest(unittest.TestCase): self.assertEquals(expected_result, shell_arguments.append_to_argument( arguments, key, value)) - def testAppendToExisting(self): + def test_append_to_existing(self): arguments = ['--something=old_val'] key = '--something=' value = 'val' diff --git a/mojo/devtools/common/devtoolslib/shell_config.py b/mojo/devtools/common/devtoolslib/shell_config.py new file mode 100644 index 00000000000..2b55d548717 --- /dev/null +++ b/mojo/devtools/common/devtoolslib/shell_config.py @@ -0,0 +1,196 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Configuration for the shell abstraction. + +This module declares ShellConfig and knows how to compute it from command-line +arguments, applying any default paths inferred from the checkout, configuration +file, etc. +""" + +import ast + +from devtoolslib import paths + + +class ShellConfigurationException(Exception): + """Represents an error preventing creating a functional shell abstraction.""" + pass + + +class ShellConfig(object): + """Configuration for the shell abstraction.""" + + def __init__(self): + self.android = None + self.shell_path = None + self.origin = None + self.map_url_list = [] + self.map_origin_list = [] + self.dev_servers = [] + self.sky = None + self.verbose = None + + # Android-only. + self.adb_path = None + self.target_device = None + self.logcat_tags = None + + # Desktop-only. + self.use_osmesa = None + + +class DevServerConfig(object): + """Configuration for a development server running on a host and available to + the shell. + """ + def __init__(self): + self.host = None + self.mappings = None + + +def add_shell_arguments(parser): + """Adds argparse arguments allowing to configure shell abstraction using + configure_shell() below. + """ + # Arguments configuring the shell run. + parser.add_argument('--android', help='Run on Android', + action='store_true') + parser.add_argument('--shell-path', help='Path of the Mojo shell binary.') + parser.add_argument('--origin', help='Origin for mojo: URLs. This can be a ' + 'web url or a local directory path.') + parser.add_argument('--map-url', action='append', + help='Define a mapping for a url in the format ' + '=') + parser.add_argument('--map-origin', action='append', + help='Define a mapping for a url origin in the format ' + '=') + parser.add_argument('--sky', action='store_true', + help='Maps mojo:sky_viewer as the content handler for ' + 'dart apps.') + parser.add_argument('-v', '--verbose', action="store_true", + help="Increase output verbosity") + + android_group = parser.add_argument_group('Android-only', + 'These arguments apply only when --android is passed.') + android_group.add_argument('--adb-path', help='Path of the adb binary.') + android_group.add_argument('--target-device', help='Device to run on.') + android_group.add_argument('--logcat-tags', help='Comma-separated list of ' + 'additional logcat tags to display.') + + desktop_group = parser.add_argument_group('Desktop-only', + 'These arguments apply only when running on desktop.') + desktop_group.add_argument('--use-osmesa', action='store_true', + help='Configure the native viewport service ' + 'for off-screen rendering.') + + config_file_group = parser.add_argument_group('Configuration file', + 'These arguments allow to modify the behavior regarding the mojoconfig ' + 'file.') + config_file_group.add_argument('--config-file', type=file, + help='Path of the configuration file to use.') + config_file_group.add_argument('--no-config-file', action='store_true', + help='Pass to skip automatic discovery of the ' + 'mojoconfig file.') + + # Arguments allowing to indicate the build directory we are targeting when + # running within a Chromium-like checkout (e.g. Mojo checkout). These will go + # away once we have devtools config files, see + # https://github.com/domokit/devtools/issues/28. + chromium_checkout_group = parser.add_argument_group( + 'Chromium-like checkout configuration', + 'These arguments allow to infer paths to tools and build results ' + 'when running within a Chromium-like checkout') + debug_group = chromium_checkout_group.add_mutually_exclusive_group() + debug_group.add_argument('--debug', help='Debug build (default)', + default=True, action='store_true') + debug_group.add_argument('--release', help='Release build', default=False, + dest='debug', action='store_false') + chromium_checkout_group.add_argument('--target-cpu', + help='CPU architecture to run for.', + choices=['x64', 'x86', 'arm']) + + +def _discover_config_file(): + config_file_path = paths.find_within_ancestors('mojoconfig') + if not config_file_path: + return None + return open(config_file_path, 'r') + + +def _read_config_file(config_file, aliases): + spec = config_file.read() + for alias_pattern, alias_value in aliases: + spec = spec.replace(alias_pattern, alias_value) + return ast.literal_eval(spec) + + +def get_shell_config(script_args): + """Processes command-line options defined in add_shell_arguments(), applying + any inferred default paths and produces an instance of ShellConfig. + + Returns: + An instance of ShellConfig. + """ + # Infer paths based on the Chromium configuration options + # (--debug/--release, etc.), if running within a Chromium-like checkout. + inferred_paths = paths.infer_paths(script_args.android, script_args.debug, + script_args.target_cpu) + shell_config = ShellConfig() + + shell_config.android = script_args.android + shell_config.shell_path = (script_args.shell_path or + inferred_paths['shell_path']) + shell_config.origin = script_args.origin + shell_config.map_url_list = script_args.map_url + shell_config.map_origin_list = script_args.map_origin + shell_config.sky = script_args.sky + shell_config.verbose = script_args.verbose + + # Android-only. + shell_config.adb_path = (script_args.adb_path or inferred_paths['adb_path']) + shell_config.target_device = script_args.target_device + shell_config.logcat_tags = script_args.logcat_tags + + # Desktop-only. + shell_config.use_osmesa = script_args.use_osmesa + + if (shell_config.android and not shell_config.origin and + inferred_paths['build_dir_path']): + shell_config.origin = inferred_paths['build_dir_path'] + + # Read the mojoconfig file. + config_file = script_args.config_file + if not script_args.no_config_file: + config_file = config_file or _discover_config_file() + + if config_file: + with config_file: + config_file_aliases = [] + if inferred_paths['build_dir_path']: + config_file_aliases.append(('@{BUILD_DIR}', + inferred_paths['build_dir_path'])) + + config = None + try: + if script_args.verbose: + print 'Reading config file from: ' + config_file.name + config = _read_config_file(config_file, config_file_aliases) + except SyntaxError: + raise ShellConfigurationException('Failed to parse the mojoconfig ' + 'file.') + + if 'dev_servers' in config: + try: + for dev_server_spec in config['dev_servers']: + dev_server_config = DevServerConfig() + dev_server_config.host = dev_server_spec['host'] + dev_server_config.mappings = [] + for prefix, path in dev_server_spec['mappings']: + dev_server_config.mappings.append((prefix, path)) + shell_config.dev_servers.append(dev_server_config) + except (ValueError, KeyError): + raise ShellConfigurationException('Failed to parse dev_servers in ' + 'the mojoconfig file.') + return shell_config diff --git a/mojo/devtools/common/devtoolslib/utils.py b/mojo/devtools/common/devtoolslib/utils.py new file mode 100644 index 00000000000..7bf2e1c6412 --- /dev/null +++ b/mojo/devtools/common/devtoolslib/utils.py @@ -0,0 +1,16 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Python utils.""" + + +def overrides(parent_class): + """Inherits the docstring from the method of the same name in the indicated + parent class. + """ + def overriding(method): + assert(method.__name__ in dir(parent_class)) + method.__doc__ = getattr(parent_class, method.__name__).__doc__ + return method + return overriding diff --git a/mojo/devtools/common/mojo_run b/mojo/devtools/common/mojo_run index 778dd704d19..554eeabb909 100755 --- a/mojo/devtools/common/mojo_run +++ b/mojo/devtools/common/mojo_run @@ -7,8 +7,8 @@ import argparse import logging import sys -from devtoolslib import paths from devtoolslib import shell_arguments +from devtoolslib import shell_config _USAGE = ("mojo_run " "[--args-for=] " @@ -16,7 +16,8 @@ _USAGE = ("mojo_run " "[--enable-external-applications] " "[--disable-cache] " "[--enable-multiprocess] " - "[] " + "[--wait-for-debugger] " + "[--sky |] " """ A is a Mojo URL or a Mojo URL and arguments within quotes. @@ -46,7 +47,7 @@ def _configure_debugger(shell): Arguments that need to be appended to the shell argument list in order to run with the debugger. """ - shell.ForwardHostPortToShell(_MOJO_DEBUGGER_PORT) + shell.forward_host_port_to_shell(_MOJO_DEBUGGER_PORT) return ['mojo:debugger %d' % _MOJO_DEBUGGER_PORT] @@ -54,59 +55,21 @@ def main(): logging.basicConfig() parser = argparse.ArgumentParser(usage=_USAGE, description=_DESCRIPTION) + shell_config.add_shell_arguments(parser) - # Arguments allowing to indicate the configuration we are targeting when - # running within a Chromium-like checkout. These will go away once we have - # devtools config files, see https://github.com/domokit/devtools/issues/28. - chromium_config_group = parser.add_argument_group('Chromium configuration', - 'These arguments allow to infer paths to tools and build results ' - 'when running withing a Chromium-like checkout') - debug_group = chromium_config_group.add_mutually_exclusive_group() - debug_group.add_argument('--debug', help='Debug build (default)', - default=True, action='store_true') - debug_group.add_argument('--release', help='Release build', default=False, - dest='debug', action='store_false') - chromium_config_group.add_argument('--target-cpu', - help='CPU architecture to run for.', - choices=['x64', 'x86', 'arm']) - - shell_arguments.add_shell_arguments(parser) parser.add_argument('--no-debugger', action="store_true", help='Do not spawn mojo:debugger.') parser.add_argument('--window-manager', default=_DEFAULT_WINDOW_MANAGER, help='Window manager app to be mapped as ' 'mojo:window_manager. By default it is ' + _DEFAULT_WINDOW_MANAGER) - parser.add_argument('--sky', - help='Loads the given Sky file.') script_args, shell_args = parser.parse_known_args() - # Infer paths based on the config if running within a Chromium-like checkout. - mojo_paths, _ = paths.infer_mojo_paths(script_args.android, - script_args.debug, - script_args.target_cpu) - if mojo_paths: - if script_args.android and not script_args.adb_path: - script_args.adb_path = mojo_paths['adb'] - if script_args.android and not script_args.origin: - script_args.origin = mojo_paths['build'] - if not script_args.shell_path: - script_args.shell_path = mojo_paths['shell'] - - if script_args.verbose: - print 'Running within a Chromium-style checkout.' - print ' - using the locally built shell at: ' + script_args.shell_path - if script_args.origin: - print ' - using the origin: ' + script_args.origin - if script_args.android: - print ' - using the adb path: ' + script_args.adb_path - elif script_args.verbose: - print 'Running outside a Chromium-style checkout.' - try: - shell, shell_args = shell_arguments.configure_shell(script_args, shell_args) - except shell_arguments.ShellConfigurationException as e: + config = shell_config.get_shell_config(script_args) + shell, shell_args = shell_arguments.get_shell(config, shell_args) + except shell_config.ShellConfigurationException as e: print e return 1 @@ -121,19 +84,10 @@ def main(): 'mojo:window_manager=%s' % script_args.window_manager) - if script_args.sky: - if not mojo_paths: - print 'Running with --sky is not supported outside of the Mojo checkout.' - # See https://github.com/domokit/devtools/issues/27. - return 1 - shell_args.extend(shell_arguments._configure_sky(shell, mojo_paths['root'], - mojo_paths['sky_packages'], - script_args.sky)) - if script_args.verbose: print "Shell arguments: " + str(shell_args) - shell.Run(shell_args) + shell.run(shell_args) return 0 diff --git a/mojo/devtools/common/mojo_test b/mojo/devtools/common/mojo_test index d59baccde2d..2cf84b9ba26 100755 --- a/mojo/devtools/common/mojo_test +++ b/mojo/devtools/common/mojo_test @@ -15,6 +15,7 @@ import sys from devtoolslib import apptest_dart from devtoolslib import apptest_gtest from devtoolslib import shell_arguments +from devtoolslib import shell_config _DESCRIPTION = """Runner for Mojo application tests. @@ -55,15 +56,14 @@ def main(): description=_DESCRIPTION) parser.add_argument("test_list_file", type=file, help="a file listing apptests to run") + shell_config.add_shell_arguments(parser) - # Common shell configuration arguments. - shell_arguments.add_shell_arguments(parser) - script_args, common_shell_args = parser.parse_known_args() + script_args, shell_args = parser.parse_known_args() try: - shell, common_shell_args = shell_arguments.configure_shell( - script_args, common_shell_args) - except shell_arguments.ShellConfigurationException as e: + config = shell_config.get_shell_config(script_args) + shell, common_shell_args = shell_arguments.get_shell(config, shell_args) + except shell_config.ShellConfigurationException as e: print e return 1 diff --git a/mojo/edk/system/connection_manager_unittest.cc b/mojo/edk/system/connection_manager_unittest.cc index 7b409c123ce..d8584b9f3b7 100644 --- a/mojo/edk/system/connection_manager_unittest.cc +++ b/mojo/edk/system/connection_manager_unittest.cc @@ -464,20 +464,81 @@ TEST_F(ConnectionManagerTest, ConnectSlavesTwice) { h1.reset(); h2.reset(); - // TODO(vtl): FIXME -- this will break when I implemented - // SUCCESS_CONNECT_REUSE_CONNECTION. ProcessIdentifier second_peer2 = kInvalidProcessIdentifier; - EXPECT_EQ(ConnectionManager::Result::SUCCESS_CONNECT_NEW_CONNECTION, + EXPECT_EQ(ConnectionManager::Result::SUCCESS_CONNECT_REUSE_CONNECTION, slave2.Connect(connection_id, &second_peer2, &is_first, &h2)); EXPECT_EQ(peer2, second_peer2); EXPECT_TRUE(is_first); + EXPECT_FALSE(h2.is_valid()); ProcessIdentifier second_peer1 = kInvalidProcessIdentifier; - EXPECT_EQ(ConnectionManager::Result::SUCCESS_CONNECT_NEW_CONNECTION, + EXPECT_EQ(ConnectionManager::Result::SUCCESS_CONNECT_REUSE_CONNECTION, slave1.Connect(connection_id, &second_peer1, &is_first, &h1)); EXPECT_EQ(peer1, second_peer1); EXPECT_FALSE(is_first); + EXPECT_FALSE(h1.is_valid()); + + slave2.Shutdown(); + slave1.Shutdown(); + master.Shutdown(); +} + +TEST_F(ConnectionManagerTest, OverlappingSlaveConnects) { + MasterConnectionManager master(platform_support()); + master.Init(base::MessageLoop::current()->task_runner(), + &master_process_delegate()); + + MockSlaveProcessDelegate slave1_process_delegate; + SlaveConnectionManager slave1(platform_support()); + ProcessIdentifier slave1_id = + ConnectSlave(&master, &slave1_process_delegate, &slave1, "slave1"); + EXPECT_TRUE(IsValidSlaveProcessIdentifier(slave1_id)); + + MockSlaveProcessDelegate slave2_process_delegate; + SlaveConnectionManager slave2(platform_support()); + ProcessIdentifier slave2_id = + ConnectSlave(&master, &slave2_process_delegate, &slave2, "slave2"); + EXPECT_TRUE(IsValidSlaveProcessIdentifier(slave2_id)); + EXPECT_NE(slave1_id, slave2_id); + + ConnectionIdentifier connection_id1 = master.GenerateConnectionIdentifier(); + EXPECT_TRUE(slave1.AllowConnect(connection_id1)); + EXPECT_TRUE(slave2.AllowConnect(connection_id1)); + + ConnectionIdentifier connection_id2 = master.GenerateConnectionIdentifier(); + EXPECT_TRUE(slave1.AllowConnect(connection_id2)); + EXPECT_TRUE(slave2.AllowConnect(connection_id2)); + + ProcessIdentifier peer1 = kInvalidProcessIdentifier; + bool is_first = false; + embedder::ScopedPlatformHandle h1; + EXPECT_EQ(ConnectionManager::Result::SUCCESS_CONNECT_NEW_CONNECTION, + slave1.Connect(connection_id1, &peer1, &is_first, &h1)); + EXPECT_EQ(slave2_id, peer1); + EXPECT_TRUE(is_first); + ProcessIdentifier peer2 = kInvalidProcessIdentifier; + embedder::ScopedPlatformHandle h2; + EXPECT_EQ(ConnectionManager::Result::SUCCESS_CONNECT_NEW_CONNECTION, + slave2.Connect(connection_id2, &peer2, &is_first, &h2)); + EXPECT_EQ(slave1_id, peer2); + EXPECT_TRUE(is_first); + EXPECT_TRUE(ArePlatformHandlesConnected(h1.get(), h2.get())); + h1.reset(); + h2.reset(); + ProcessIdentifier second_peer1 = kInvalidProcessIdentifier; + EXPECT_EQ(ConnectionManager::Result::SUCCESS_CONNECT_REUSE_CONNECTION, + slave1.Connect(connection_id2, &second_peer1, &is_first, &h1)); + EXPECT_EQ(peer1, second_peer1); + EXPECT_FALSE(is_first); + EXPECT_FALSE(h1.is_valid()); + ProcessIdentifier second_peer2 = kInvalidProcessIdentifier; + EXPECT_EQ(ConnectionManager::Result::SUCCESS_CONNECT_REUSE_CONNECTION, + slave2.Connect(connection_id1, &second_peer2, &is_first, &h2)); + EXPECT_EQ(peer2, second_peer2); + EXPECT_FALSE(is_first); + EXPECT_FALSE(h2.is_valid()); + slave2.Shutdown(); slave1.Shutdown(); master.Shutdown(); diff --git a/mojo/edk/system/master_connection_manager.cc b/mojo/edk/system/master_connection_manager.cc index 384be226e86..92572122c4d 100644 --- a/mojo/edk/system/master_connection_manager.cc +++ b/mojo/edk/system/master_connection_manager.cc @@ -12,6 +12,7 @@ #include "base/synchronization/waitable_event.h" #include "mojo/edk/embedder/master_process_delegate.h" #include "mojo/edk/embedder/platform_channel_pair.h" +#include "mojo/edk/embedder/platform_handle.h" #include "mojo/edk/embedder/platform_handle_vector.h" #include "mojo/edk/system/connection_manager_messages.h" #include "mojo/edk/system/message_in_transit.h" @@ -60,7 +61,8 @@ MessageInTransit::Subtype ConnectionManagerResultToMessageInTransitSubtype( // |MasterConnectionManager::Helper| is not thread-safe, and must only be used // on its |owner_|'s private thread. -class MasterConnectionManager::Helper final : public RawChannel::Delegate { +class MOJO_SYSTEM_IMPL_EXPORT MasterConnectionManager::Helper final + : public RawChannel::Delegate { public: Helper(MasterConnectionManager* owner, ProcessIdentifier process_identifier, @@ -164,9 +166,6 @@ void MasterConnectionManager::Helper::OnReadMessage( &data.peer_process_identifier, &data.is_first, &platform_handle); DCHECK_NE(result, Result::SUCCESS); - // TODO(vtl): FIXME -- currently, nothing should generate - // SUCCESS_CONNECT_REUSE_CONNECTION. - CHECK_NE(result, Result::SUCCESS_CONNECT_REUSE_CONNECTION); // Success acks for "connect" have the peer process identifier as data // (and also a platform handle in the case of "new connection" -- handled // further below). @@ -220,9 +219,9 @@ void MasterConnectionManager::Helper::FatalError() { owner_->OnError(process_identifier_); // WARNING: This destroys us. } -// MasterConnectionManager::PendingConnectionInfo ------------------------------ +// MasterConnectionManager::PendingConnectInfo --------------------------------- -struct MasterConnectionManager::PendingConnectionInfo { +struct MOJO_SYSTEM_IMPL_EXPORT MasterConnectionManager::PendingConnectInfo { // States: // - This is created upon a first "allow connect" (with |first| set // immediately). We then wait for a second "allow connect". @@ -233,28 +232,86 @@ struct MasterConnectionManager::PendingConnectionInfo { // I.e., the valid state transitions are: // AWAITING_SECOND_ALLOW_CONNECT -> AWAITING_CONNECTS_FROM_BOTH // -> {AWAITING_CONNECT_FROM_FIRST,AWAITING_CONNECT_FROM_SECOND} - enum State { + enum class State { AWAITING_SECOND_ALLOW_CONNECT, AWAITING_CONNECTS_FROM_BOTH, AWAITING_CONNECT_FROM_FIRST, AWAITING_CONNECT_FROM_SECOND }; - explicit PendingConnectionInfo(ProcessIdentifier first) - : state(AWAITING_SECOND_ALLOW_CONNECT), + explicit PendingConnectInfo(ProcessIdentifier first) + : state(State::AWAITING_SECOND_ALLOW_CONNECT), first(first), second(kInvalidProcessIdentifier) { DCHECK_NE(first, kInvalidProcessIdentifier); } - ~PendingConnectionInfo() {} + ~PendingConnectInfo() {} State state; ProcessIdentifier first; ProcessIdentifier second; +}; - // Valid in AWAITING_CONNECT_FROM_{FIRST, SECOND} states. - embedder::ScopedPlatformHandle remaining_handle; +// MasterConnectionManager::ProcessConnections --------------------------------- + +class MasterConnectionManager::ProcessConnections { + public: + enum class ConnectionStatus { NONE, PENDING, RUNNING }; + + ProcessConnections() {} + ~ProcessConnections() { + // TODO(vtl): Log a warning if there are connections pending? (This might be + // very spammy, since the |MasterConnectionManager| may have many + // |ProcessConnections|. + for (auto& p : process_connections_) + p.second.CloseIfNecessary(); + } + + // If |pending_platform_handle| is non-null and the status is |PENDING| this + // will "return"/pass the stored pending platform handle. Warning: In that + // case, this has the side effect of changing the state to |RUNNING|. + ConnectionStatus GetConnectionStatus( + ProcessIdentifier to_process_identifier, + embedder::ScopedPlatformHandle* pending_platform_handle) { + DCHECK(!pending_platform_handle || !pending_platform_handle->is_valid()); + + auto it = process_connections_.find(to_process_identifier); + if (it == process_connections_.end()) + return ConnectionStatus::NONE; + if (!it->second.is_valid()) + return ConnectionStatus::RUNNING; + // Pending: + if (pending_platform_handle) { + pending_platform_handle->reset(it->second); + it->second = embedder::PlatformHandle(); + } + return ConnectionStatus::PENDING; + } + + void AddConnection(ProcessIdentifier to_process_identifier, + ConnectionStatus status, + embedder::ScopedPlatformHandle pending_platform_handle) { + DCHECK(process_connections_.find(to_process_identifier) == + process_connections_.end()); + + if (status == ConnectionStatus::RUNNING) { + DCHECK(!pending_platform_handle.is_valid()); + process_connections_[to_process_identifier] = embedder::PlatformHandle(); + } else if (status == ConnectionStatus::PENDING) { + DCHECK(pending_platform_handle.is_valid()); + process_connections_[to_process_identifier] = + pending_platform_handle.release(); + } else { + NOTREACHED(); + } + } + + private: + base::hash_map + process_connections_; // "Owns" any valid platform handles. + + MOJO_DISALLOW_COPY_AND_ASSIGN(ProcessConnections); }; // MasterConnectionManager ----------------------------------------------------- @@ -265,6 +322,7 @@ MasterConnectionManager::MasterConnectionManager( master_process_delegate_(), private_thread_("MasterConnectionManagerPrivateThread"), next_process_identifier_(kFirstSlaveProcessIdentifier) { + connections_[kMasterProcessIdentifier] = new ProcessConnections(); } MasterConnectionManager::~MasterConnectionManager() { @@ -272,7 +330,7 @@ MasterConnectionManager::~MasterConnectionManager() { DCHECK(!master_process_delegate_); DCHECK(!private_thread_.message_loop()); DCHECK(helpers_.empty()); - DCHECK(pending_connections_.empty()); + DCHECK(pending_connects_.empty()); } void MasterConnectionManager::Init( @@ -303,6 +361,8 @@ ProcessIdentifier MasterConnectionManager::AddSlave( CHECK_NE(next_process_identifier_, kMasterProcessIdentifier); slave_process_identifier = next_process_identifier_; next_process_identifier_++; + DCHECK(connections_.find(slave_process_identifier) == connections_.end()); + connections_[slave_process_identifier] = new ProcessConnections(); } // We have to wait for the task to be executed, in case someone calls @@ -327,13 +387,11 @@ ProcessIdentifier MasterConnectionManager::AddSlaveAndBootstrap( AddSlave(slave_info, platform_handle.Pass()); MutexLocker locker(&mutex_); - DCHECK(pending_connections_.find(connection_id) == - pending_connections_.end()); - PendingConnectionInfo* info = - new PendingConnectionInfo(kMasterProcessIdentifier); - info->state = PendingConnectionInfo::AWAITING_CONNECTS_FROM_BOTH; + DCHECK(pending_connects_.find(connection_id) == pending_connects_.end()); + PendingConnectInfo* info = new PendingConnectInfo(kMasterProcessIdentifier); + info->state = PendingConnectInfo::State::AWAITING_CONNECTS_FROM_BOTH; info->second = slave_process_identifier; - pending_connections_[connection_id] = info; + pending_connects_[connection_id] = info; return slave_process_identifier; } @@ -349,7 +407,7 @@ void MasterConnectionManager::Shutdown() { base::Unretained(this))); private_thread_.Stop(); DCHECK(helpers_.empty()); - DCHECK(pending_connections_.empty()); + DCHECK(pending_connects_.empty()); master_process_delegate_ = nullptr; delegate_thread_task_runner_ = nullptr; } @@ -382,10 +440,10 @@ bool MasterConnectionManager::AllowConnectImpl( MutexLocker locker(&mutex_); - auto it = pending_connections_.find(connection_id); - if (it == pending_connections_.end()) { - pending_connections_[connection_id] = - new PendingConnectionInfo(process_identifier); + auto it = pending_connects_.find(connection_id); + if (it == pending_connects_.end()) { + pending_connects_[connection_id] = + new PendingConnectInfo(process_identifier); // TODO(vtl): Track process identifier -> pending connections also (so these // can be removed efficiently if that process disconnects). DVLOG(1) << "New pending connection ID " << connection_id.ToString() @@ -394,9 +452,9 @@ bool MasterConnectionManager::AllowConnectImpl( return true; } - PendingConnectionInfo* info = it->second; - if (info->state == PendingConnectionInfo::AWAITING_SECOND_ALLOW_CONNECT) { - info->state = PendingConnectionInfo::AWAITING_CONNECTS_FROM_BOTH; + PendingConnectInfo* info = it->second; + if (info->state == PendingConnectInfo::State::AWAITING_SECOND_ALLOW_CONNECT) { + info->state = PendingConnectInfo::State::AWAITING_CONNECTS_FROM_BOTH; info->second = process_identifier; DVLOG(1) << "Pending connection ID " << connection_id.ToString() << ": AllowConnect() from second process identifier " @@ -408,8 +466,8 @@ bool MasterConnectionManager::AllowConnectImpl( // caller). LOG(ERROR) << "AllowConnect() from process " << process_identifier << " for connection ID " << connection_id.ToString() - << " already in state " << info->state; - pending_connections_.erase(it); + << " already in state " << static_cast(info->state); + pending_connects_.erase(it); delete info; return false; } @@ -421,8 +479,8 @@ bool MasterConnectionManager::CancelConnectImpl( MutexLocker locker(&mutex_); - auto it = pending_connections_.find(connection_id); - if (it == pending_connections_.end()) { + auto it = pending_connects_.find(connection_id); + if (it == pending_connects_.end()) { // Not necessarily the caller's fault, and not necessarily an error. DVLOG(1) << "CancelConnect() from process " << process_identifier << " for connection ID " << connection_id.ToString() @@ -430,7 +488,7 @@ bool MasterConnectionManager::CancelConnectImpl( return true; } - PendingConnectionInfo* info = it->second; + PendingConnectInfo* info = it->second; if (process_identifier != info->first && process_identifier != info->second) { LOG(ERROR) << "CancelConnect() from process " << process_identifier << " for connection ID " << connection_id.ToString() @@ -441,7 +499,7 @@ bool MasterConnectionManager::CancelConnectImpl( // Just erase it. The other side may also try to cancel, in which case it'll // "fail" in the first if statement above (we assume that connection IDs never // collide, so there's no need to carefully track both sides). - pending_connections_.erase(it); + pending_connects_.erase(it); delete info; return true; } @@ -460,8 +518,8 @@ ConnectionManager::Result MasterConnectionManager::ConnectImpl( MutexLocker locker(&mutex_); - auto it = pending_connections_.find(connection_id); - if (it == pending_connections_.end()) { + auto it = pending_connects_.find(connection_id); + if (it == pending_connects_.end()) { // Not necessarily the caller's fault. LOG(ERROR) << "Connect() from process " << process_identifier << " for connection ID " << connection_id.ToString() @@ -469,16 +527,15 @@ ConnectionManager::Result MasterConnectionManager::ConnectImpl( return Result::FAILURE; } - PendingConnectionInfo* info = it->second; - if (info->state == PendingConnectionInfo::AWAITING_CONNECTS_FROM_BOTH) { - DCHECK(!info->remaining_handle.is_valid()); - + PendingConnectInfo* info = it->second; + ProcessIdentifier peer; + if (info->state == PendingConnectInfo::State::AWAITING_CONNECTS_FROM_BOTH) { if (process_identifier == info->first) { - info->state = PendingConnectionInfo::AWAITING_CONNECT_FROM_SECOND; - *peer_process_identifier = info->second; + info->state = PendingConnectInfo::State::AWAITING_CONNECT_FROM_SECOND; + peer = info->second; } else if (process_identifier == info->second) { - info->state = PendingConnectionInfo::AWAITING_CONNECT_FROM_FIRST; - *peer_process_identifier = info->first; + info->state = PendingConnectInfo::State::AWAITING_CONNECT_FROM_FIRST; + peer = info->first; } else { LOG(ERROR) << "Connect() from process " << process_identifier << " for connection ID " << connection_id.ToString() @@ -486,35 +543,26 @@ ConnectionManager::Result MasterConnectionManager::ConnectImpl( return Result::FAILURE; } - *is_first = true; - - // TODO(vtl): FIXME -- add stuff for SUCCESS_CONNECT_REUSE_CONNECTION here. - Result result = Result::FAILURE; - if (info->first == info->second) { - platform_handle->reset(); - DCHECK(!info->remaining_handle.is_valid()); - result = Result::SUCCESS_CONNECT_SAME_PROCESS; - } else { - embedder::PlatformChannelPair platform_channel_pair; - *platform_handle = platform_channel_pair.PassServerHandle(); - DCHECK(platform_handle->is_valid()); - info->remaining_handle = platform_channel_pair.PassClientHandle(); - DCHECK(info->remaining_handle.is_valid()); - result = Result::SUCCESS_CONNECT_NEW_CONNECTION; - } DVLOG(1) << "Connection ID " << connection_id.ToString() << ": first Connect() from process identifier " << process_identifier; - return result; + *peer_process_identifier = peer; + *is_first = true; + return ConnectImplHelperNoLock(process_identifier, peer, platform_handle); } + // The remaining cases all result in |it| being removed from + // |pending_connects_| and deleting |info|. + pending_connects_.erase(it); + scoped_ptr info_deleter(info); + + // |remaining_connectee| should be the same as |process_identifier|. ProcessIdentifier remaining_connectee; - ProcessIdentifier peer; - if (info->state == PendingConnectionInfo::AWAITING_CONNECT_FROM_FIRST) { + if (info->state == PendingConnectInfo::State::AWAITING_CONNECT_FROM_FIRST) { remaining_connectee = info->first; peer = info->second; } else if (info->state == - PendingConnectionInfo::AWAITING_CONNECT_FROM_SECOND) { + PendingConnectInfo::State::AWAITING_CONNECT_FROM_SECOND) { remaining_connectee = info->second; peer = info->first; } else { @@ -522,9 +570,7 @@ ConnectionManager::Result MasterConnectionManager::ConnectImpl( // caller). LOG(ERROR) << "Connect() from process " << process_identifier << " for connection ID " << connection_id.ToString() - << " in state " << info->state; - pending_connections_.erase(it); - delete info; + << " in state " << static_cast(info->state); return Result::FAILURE; } @@ -532,43 +578,80 @@ ConnectionManager::Result MasterConnectionManager::ConnectImpl( LOG(ERROR) << "Connect() from process " << process_identifier << " for connection ID " << connection_id.ToString() << " which is not the remaining connectee"; - pending_connections_.erase(it); - delete info; return Result::FAILURE; } - *peer_process_identifier = peer; - *is_first = false; - - // TODO(vtl): FIXME -- add stuff for SUCCESS_CONNECT_REUSE_CONNECTION here. - Result result = Result::FAILURE; - if (info->first == info->second) { - platform_handle->reset(); - DCHECK(!info->remaining_handle.is_valid()); - result = Result::SUCCESS_CONNECT_SAME_PROCESS; - } else { - *platform_handle = info->remaining_handle.Pass(); - DCHECK(platform_handle->is_valid()); - result = Result::SUCCESS_CONNECT_NEW_CONNECTION; - } - pending_connections_.erase(it); - delete info; DVLOG(1) << "Connection ID " << connection_id.ToString() << ": second Connect() from process identifier " << process_identifier; - return result; + *peer_process_identifier = peer; + *is_first = false; + return ConnectImplHelperNoLock(process_identifier, peer, platform_handle); +} + +ConnectionManager::Result MasterConnectionManager::ConnectImplHelperNoLock( + ProcessIdentifier process_identifier, + ProcessIdentifier peer_process_identifier, + embedder::ScopedPlatformHandle* platform_handle) { + if (process_identifier == peer_process_identifier) { + platform_handle->reset(); + DVLOG(1) << "Connect: same process"; + return Result::SUCCESS_CONNECT_SAME_PROCESS; + } + + // We should know about the process identified by |process_identifier|. + DCHECK(connections_.find(process_identifier) != connections_.end()); + ProcessConnections* process_connections = connections_[process_identifier]; + // We should also know about the peer. + DCHECK(connections_.find(peer_process_identifier) != connections_.end()); + switch (process_connections->GetConnectionStatus(peer_process_identifier, + platform_handle)) { + case ProcessConnections::ConnectionStatus::NONE: { + // TODO(vtl): In the "second connect" case, this should never be reached + // (but it's not easy to DCHECK this invariant here). + process_connections->AddConnection( + peer_process_identifier, + ProcessConnections::ConnectionStatus::RUNNING, + embedder::ScopedPlatformHandle()); + embedder::PlatformChannelPair platform_channel_pair; + *platform_handle = platform_channel_pair.PassServerHandle(); + + connections_[peer_process_identifier]->AddConnection( + process_identifier, ProcessConnections::ConnectionStatus::PENDING, + platform_channel_pair.PassClientHandle()); + break; + } + case ProcessConnections::ConnectionStatus::PENDING: + DCHECK(connections_[peer_process_identifier]->GetConnectionStatus( + process_identifier, nullptr) == + ProcessConnections::ConnectionStatus::RUNNING); + break; + case ProcessConnections::ConnectionStatus::RUNNING: + // |process_identifier| already has a connection to + // |peer_process_identifier|, so it should reuse that. + platform_handle->reset(); + DVLOG(1) << "Connect: reuse connection"; + return Result::SUCCESS_CONNECT_REUSE_CONNECTION; + } + DCHECK(platform_handle->is_valid()); + DVLOG(1) << "Connect: new connection"; + return Result::SUCCESS_CONNECT_NEW_CONNECTION; } void MasterConnectionManager::ShutdownOnPrivateThread() { AssertOnPrivateThread(); - if (!pending_connections_.empty()) { + if (!pending_connects_.empty()) { DVLOG(1) << "Shutting down with connections pending"; - for (auto& p : pending_connections_) + for (auto& p : pending_connects_) delete p.second; - pending_connections_.clear(); + pending_connects_.clear(); } + for (auto& p : connections_) + delete p.second; + connections_.clear(); + if (!helpers_.empty()) { DVLOG(1) << "Shutting down with slaves still connected"; for (auto& p : helpers_) { @@ -615,14 +698,13 @@ void MasterConnectionManager::OnError(ProcessIdentifier process_identifier) { MutexLocker locker(&mutex_); // TODO(vtl): This isn't very efficient. - for (auto it = pending_connections_.begin(); - it != pending_connections_.end();) { + for (auto it = pending_connects_.begin(); it != pending_connects_.end();) { if (it->second->first == process_identifier || it->second->second == process_identifier) { auto it_to_erase = it; ++it; delete it_to_erase->second; - pending_connections_.erase(it_to_erase); + pending_connects_.erase(it_to_erase); } else { ++it; } diff --git a/mojo/edk/system/master_connection_manager.h b/mojo/edk/system/master_connection_manager.h index a03719a400e..ee702a88919 100644 --- a/mojo/edk/system/master_connection_manager.h +++ b/mojo/edk/system/master_connection_manager.h @@ -99,6 +99,16 @@ class MOJO_SYSTEM_IMPL_EXPORT MasterConnectionManager final bool* is_first, embedder::ScopedPlatformHandle* platform_handle); + // Helper for |ConnectImpl()|. This is called when the two process identifiers + // are known (and known to be valid), and all that remains is to determine the + // |Result| and provide a platform handle if appropriate. (This will never + // return |Result::FAILURE|.) + Result ConnectImplHelperNoLock( + ProcessIdentifier process_identifier, + ProcessIdentifier peer_process_identifier, + embedder::ScopedPlatformHandle* platform_handle) + MOJO_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + // These should only be called on |private_thread_|: void ShutdownOnPrivateThread() MOJO_NOT_THREAD_SAFE; // Signals |*event| on completion. @@ -141,9 +151,23 @@ class MOJO_SYSTEM_IMPL_EXPORT MasterConnectionManager final ProcessIdentifier next_process_identifier_ MOJO_GUARDED_BY(mutex_); - struct PendingConnectionInfo; - base::hash_map - pending_connections_ MOJO_GUARDED_BY(mutex_); // Owns its values. + // Stores information on pending calls to |AllowConnect()|/|Connect()| (or + // |CancelConnect()|, namely those for which at least one party has called + // |AllowConnect()| but both have not yet called |Connect()| (or + // |CancelConnect()|). + struct PendingConnectInfo; + base::hash_map pending_connects_ + MOJO_GUARDED_BY(mutex_); // Owns its values. + + // A |ProcessConnections| stores information about connections "from" a given + // (fixed, implied) process "to" other processes. A connection may be not + // present, running (meaning that both sides have connected and been given + // platform handles to a connected "pipe"), or pending (meaning that the + // "from" side must still be given a platform handle). + class ProcessConnections; + // This is a map from "from" processes to its |ProcessConnections| (above). + base::hash_map connections_ + MOJO_GUARDED_BY(mutex_); // Owns its values. MOJO_DISALLOW_COPY_AND_ASSIGN(MasterConnectionManager); }; diff --git a/mojo/edk/system/slave_connection_manager.cc b/mojo/edk/system/slave_connection_manager.cc index 5ed9ba6c9b0..1d9d590008c 100644 --- a/mojo/edk/system/slave_connection_manager.cc +++ b/mojo/edk/system/slave_connection_manager.cc @@ -295,9 +295,6 @@ void SlaveConnectionManager::OnReadMessage( DCHECK_EQ(num_platform_handles, 0u); *ack_result_ = Result::SUCCESS_CONNECT_REUSE_CONNECTION; ack_platform_handle_->reset(); - // TODO(vtl): FIXME -- currently, nothing should generate - // SUCCESS_CONNECT_REUSE_CONNECTION. - CHECK(false); break; default: CHECK(false); diff --git a/mojo/gles2/gles2_impl.cc b/mojo/gles2/gles2_impl.cc index 94dbae7c32e..50e1d3f04b1 100644 --- a/mojo/gles2/gles2_impl.cc +++ b/mojo/gles2/gles2_impl.cc @@ -71,12 +71,14 @@ void MojoGLES2SignalSyncPoint(MojoGLES2Context context, return g_gpu_interface.Get().Get()->Function ARGUMENTS; \ } #include "mojo/public/c/gles2/gles2_call_visitor_autogen.h" -#include "mojo/public/c/gles2/gles2_call_visitor_occlusion_query_ext_autogen.h" #include "mojo/public/c/gles2/gles2_call_visitor_chromium_miscellaneous_autogen.h" #include "mojo/public/c/gles2/gles2_call_visitor_chromium_resize_autogen.h" #include "mojo/public/c/gles2/gles2_call_visitor_chromium_sub_image_autogen.h" #include "mojo/public/c/gles2/gles2_call_visitor_chromium_sync_point_autogen.h" #include "mojo/public/c/gles2/gles2_call_visitor_chromium_texture_mailbox_autogen.h" +#include "mojo/public/c/gles2/gles2_call_visitor_ext_debug_marker_autogen.h" +#include "mojo/public/c/gles2/gles2_call_visitor_occlusion_query_ext_autogen.h" +#include "mojo/public/c/gles2/gles2_call_visitor_oes_vertex_array_object_autogen.h" #undef VISIT_GL_CALL } // extern "C" diff --git a/mojo/go/go.py b/mojo/go/go.py index 0b84e685dd6..a0f93f213e2 100755 --- a/mojo/go/go.py +++ b/mojo/go/go.py @@ -78,6 +78,8 @@ def main(): ndk_cc = os.path.join(ndk_path, 'toolchains', NDK_TOOLCHAIN, 'prebuilt', arch, 'bin', 'arm-linux-androideabi-gcc') sysroot = os.path.join(ndk_path, 'platforms', NDK_PLATFORM, 'arch-arm') + env['CGO_CFLAGS'] += ' --sysroot %s' % sysroot + env['CGO_LDFLAGS'] += ' --sysroot %s' % sysroot env['CC'] = '%s --sysroot %s' % (ndk_cc, sysroot) call_result = subprocess.call([go_tool] + go_options, env=env) diff --git a/mojo/go/rules.gni b/mojo/go/rules.gni index 328640575e3..60714f0f088 100644 --- a/mojo/go/rules.gni +++ b/mojo/go/rules.gni @@ -137,11 +137,8 @@ template("go_mojo_application") { "-I" + rebase_path("//"), "-L" + rebase_path(target_out_dir) + " -l" + static_library_name + "", "build", - "-ldflags=-shared", ] - if (is_linux) { - args += [ "-buildmode=c-shared" ] - } + args += [ "-buildmode=c-shared" ] args += rebase_path(invoker.sources, build_dir) } diff --git a/mojo/public/c/gles2/BUILD.gn b/mojo/public/c/gles2/BUILD.gn index 0ccdbbe6799..0036301768c 100644 --- a/mojo/public/c/gles2/BUILD.gn +++ b/mojo/public/c/gles2/BUILD.gn @@ -28,6 +28,7 @@ mojo_sdk_source_set("headers") { "chromium_sub_image.h", "chromium_sync_point.h", "chromium_texture_mailbox.h", + "ext_debug_marker.h", "gles2.h", "gles2_call_visitor_autogen.h", "gles2_call_visitor_chromium_copy_texture_autogen.h", @@ -38,10 +39,13 @@ mojo_sdk_source_set("headers") { "gles2_call_visitor_chromium_sub_image_autogen.h", "gles2_call_visitor_chromium_sync_point_autogen.h", "gles2_call_visitor_chromium_texture_mailbox_autogen.h", + "gles2_call_visitor_ext_debug_marker_autogen.h", "gles2_call_visitor_occlusion_query_ext_autogen.h", + "gles2_call_visitor_oes_vertex_array_object_autogen.h", "gles2_export.h", "gles2_types.h", "occlusion_query_ext.h", + "oes_vertex_array_object.h", ] public_configs = [ ":gles2_config" ] diff --git a/mojo/public/c/gles2/chromium_resize.h b/mojo/public/c/gles2/chromium_resize.h index d8d527a829d..c8b1186684e 100644 --- a/mojo/public/c/gles2/chromium_resize.h +++ b/mojo/public/c/gles2/chromium_resize.h @@ -12,7 +12,6 @@ #include "mojo/public/c/gles2/gles2_export.h" #include "mojo/public/c/gles2/gles2_types.h" -#include "mojo/public/c/system/types.h" #ifdef __cplusplus extern "C" { diff --git a/mojo/public/c/gles2/ext_debug_marker.h b/mojo/public/c/gles2/ext_debug_marker.h new file mode 100644 index 00000000000..c108328e2eb --- /dev/null +++ b/mojo/public/c/gles2/ext_debug_marker.h @@ -0,0 +1,29 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_C_GLES2_EXT_DEBUG_MARKER_H_ +#define MOJO_PUBLIC_C_GLES2_EXT_DEBUG_MARKER_H_ + +// Note: This header should be compilable as C. + +#include +#include + +#include "mojo/public/c/gles2/gles2_export.h" +#include "mojo/public/c/gles2/gles2_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) \ + MOJO_GLES2_EXPORT ReturnType GL_APIENTRY gl##Function PARAMETERS; +#include "mojo/public/c/gles2/gles2_call_visitor_ext_debug_marker_autogen.h" +#undef VISIT_GL_CALL + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_GLES2_EXT_DEBUG_MARKER_H_ diff --git a/mojo/public/c/gles2/gles2_call_visitor_ext_debug_marker_autogen.h b/mojo/public/c/gles2/gles2_call_visitor_ext_debug_marker_autogen.h new file mode 100644 index 00000000000..fe1741a9ca3 --- /dev/null +++ b/mojo/public/c/gles2/gles2_call_visitor_ext_debug_marker_autogen.h @@ -0,0 +1,19 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is auto-generated from +// gpu/command_buffer/build_gles2_cmd_buffer.py +// It's formatted by clang-format using chromium coding style: +// clang-format -i -style=chromium filename +// DO NOT EDIT! + +VISIT_GL_CALL(InsertEventMarkerEXT, + void, + (GLsizei length, const GLchar* marker), + (length, marker)) +VISIT_GL_CALL(PushGroupMarkerEXT, + void, + (GLsizei length, const GLchar* marker), + (length, marker)) +VISIT_GL_CALL(PopGroupMarkerEXT, void, (), ()) diff --git a/mojo/public/c/gles2/gles2_call_visitor_oes_vertex_array_object_autogen.h b/mojo/public/c/gles2/gles2_call_visitor_oes_vertex_array_object_autogen.h new file mode 100644 index 00000000000..bc6f9b569cf --- /dev/null +++ b/mojo/public/c/gles2/gles2_call_visitor_oes_vertex_array_object_autogen.h @@ -0,0 +1,20 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is auto-generated from +// gpu/command_buffer/build_gles2_cmd_buffer.py +// It's formatted by clang-format using chromium coding style: +// clang-format -i -style=chromium filename +// DO NOT EDIT! + +VISIT_GL_CALL(GenVertexArraysOES, + void, + (GLsizei n, GLuint* arrays), + (n, arrays)) +VISIT_GL_CALL(DeleteVertexArraysOES, + void, + (GLsizei n, const GLuint* arrays), + (n, arrays)) +VISIT_GL_CALL(IsVertexArrayOES, GLboolean, (GLuint array), (array)) +VISIT_GL_CALL(BindVertexArrayOES, void, (GLuint array), (array)) diff --git a/mojo/public/c/gles2/occlusion_query_ext.h b/mojo/public/c/gles2/occlusion_query_ext.h index 592207773d3..5be7845e6ae 100644 --- a/mojo/public/c/gles2/occlusion_query_ext.h +++ b/mojo/public/c/gles2/occlusion_query_ext.h @@ -12,7 +12,6 @@ #include "mojo/public/c/gles2/gles2_export.h" #include "mojo/public/c/gles2/gles2_types.h" -#include "mojo/public/c/system/types.h" #ifdef __cplusplus extern "C" { diff --git a/mojo/public/c/gles2/oes_vertex_array_object.h b/mojo/public/c/gles2/oes_vertex_array_object.h new file mode 100644 index 00000000000..76981c9e922 --- /dev/null +++ b/mojo/public/c/gles2/oes_vertex_array_object.h @@ -0,0 +1,29 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_C_GLES2_OES_VERTEX_ARRAY_OBJECT_H_ +#define MOJO_PUBLIC_C_GLES2_OES_VERTEX_ARRAY_OBJECT_H_ + +// Note: This header should be compilable as C. + +#include +#include + +#include "mojo/public/c/gles2/gles2_export.h" +#include "mojo/public/c/gles2/gles2_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) \ + MOJO_GLES2_EXPORT ReturnType GL_APIENTRY gl##Function PARAMETERS; +#include "mojo/public/c/gles2/gles2_call_visitor_oes_vertex_array_object_autogen.h" +#undef VISIT_GL_CALL + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_GLES2_OES_VERTEX_ARRAY_OBJECT_H_ diff --git a/mojo/public/dart/rules.gni b/mojo/public/dart/rules.gni index ee35a26c5cf..6aa2477e914 100644 --- a/mojo/public/dart/rules.gni +++ b/mojo/public/dart/rules.gni @@ -124,11 +124,11 @@ template("dartzip_package") { "--no-hints", ] - deps = [ + public_deps = [ ":${package_target_name}_package", ] if (defined(invoker.deps)) { - deps += invoker.deps + deps = invoker.deps } if (defined(invoker.datadeps)) { diff --git a/mojo/public/mojo_application.gni b/mojo/public/mojo_application.gni index 32bdb7a7d22..3b8aec8dab6 100644 --- a/mojo/public/mojo_application.gni +++ b/mojo/public/mojo_application.gni @@ -430,8 +430,13 @@ if (is_android) { "--output=${rebase_output}", ] + deps = [ + ":${android_standalone_library_name}", + ":${shared_library_name}", + ] + if (defined(invoker.deps)) { - deps = invoker.deps + deps += invoker.deps } if (defined(invoker.public_deps)) { public_deps = invoker.public_deps diff --git a/mojo/public/platform/native/BUILD.gn b/mojo/public/platform/native/BUILD.gn index 7ca35d3d0fa..f02ca8d7d6b 100644 --- a/mojo/public/platform/native/BUILD.gn +++ b/mojo/public/platform/native/BUILD.gn @@ -78,8 +78,12 @@ mojo_sdk_source_set("gles2") { "gles2_impl_chromium_sync_point_thunks.h", "gles2_impl_chromium_texture_mailbox_thunks.cc", "gles2_impl_chromium_texture_mailbox_thunks.h", + "gles2_impl_ext_debug_marker_thunks.cc", + "gles2_impl_ext_debug_marker_thunks.h", "gles2_impl_occlusion_query_ext_thunks.cc", "gles2_impl_occlusion_query_ext_thunks.h", + "gles2_impl_oes_vertex_array_object_thunks.cc", + "gles2_impl_oes_vertex_array_object_thunks.h", "gles2_impl_thunks.cc", "gles2_impl_thunks.h", "gles2_thunks.cc", diff --git a/mojo/public/platform/native/gles2_impl_ext_debug_marker_thunks.cc b/mojo/public/platform/native/gles2_impl_ext_debug_marker_thunks.cc new file mode 100644 index 00000000000..5f1e742b048 --- /dev/null +++ b/mojo/public/platform/native/gles2_impl_ext_debug_marker_thunks.cc @@ -0,0 +1,33 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/platform/native/gles2_impl_ext_debug_marker_thunks.h" + +#include + +#include "mojo/public/platform/native/thunk_export.h" + +extern "C" { + +static MojoGLES2ImplExtDebugMarkerThunks g_impl_ext_debug_marker_thunks = {0}; + +#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) \ + ReturnType gl##Function PARAMETERS { \ + assert(g_impl_ext_debug_marker_thunks.Function); \ + return g_impl_ext_debug_marker_thunks.Function ARGUMENTS; \ + } +#include "mojo/public/c/gles2/gles2_call_visitor_ext_debug_marker_autogen.h" +#undef VISIT_GL_CALL + +extern "C" THUNK_EXPORT size_t +MojoSetGLES2ImplExtDebugMarkerThunks(const MojoGLES2ImplExtDebugMarkerThunks* + gles2_impl_ext_debug_marker_thunks) { + if (gles2_impl_ext_debug_marker_thunks->size >= + sizeof(g_impl_ext_debug_marker_thunks)) { + g_impl_ext_debug_marker_thunks = *gles2_impl_ext_debug_marker_thunks; + } + return sizeof(g_impl_ext_debug_marker_thunks); +} + +} // extern "C" diff --git a/mojo/public/platform/native/gles2_impl_ext_debug_marker_thunks.h b/mojo/public/platform/native/gles2_impl_ext_debug_marker_thunks.h new file mode 100644 index 00000000000..9c52b33290c --- /dev/null +++ b/mojo/public/platform/native/gles2_impl_ext_debug_marker_thunks.h @@ -0,0 +1,44 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_PLATFORM_NATIVE_GLES2_IMPL_EXT_DEBUG_MARKER_THUNKS_H_ +#define MOJO_PUBLIC_PLATFORM_NATIVE_GLES2_IMPL_EXT_DEBUG_MARKER_THUNKS_H_ + +#include + +#include "mojo/public/c/gles2/ext_debug_marker.h" + +// Specifies the frozen API for the Vertex Array Object Extension. +#pragma pack(push, 8) +struct MojoGLES2ImplExtDebugMarkerThunks { + size_t size; // Should be set to sizeof(*this). + +#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) \ + ReturnType(*Function) PARAMETERS; +#include "mojo/public/c/gles2/gles2_call_visitor_ext_debug_marker_autogen.h" +#undef VISIT_GL_CALL +}; +#pragma pack(pop) + +// Intended to be called from the embedder to get the embedder's implementation +// of GLES2. +inline MojoGLES2ImplExtDebugMarkerThunks +MojoMakeGLES2ImplExtDebugMarkerThunks() { + MojoGLES2ImplExtDebugMarkerThunks gles2_impl_ext_debug_marker_thunks = { + sizeof(MojoGLES2ImplExtDebugMarkerThunks), +#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) gl##Function, +#include "mojo/public/c/gles2/gles2_call_visitor_ext_debug_marker_autogen.h" +#undef VISIT_GL_CALL + }; + + return gles2_impl_ext_debug_marker_thunks; +} + +// Use this type for the function found by dynamically discovering it in +// a DSO linked with mojo_system. +// The contents of |gles2_impl_ext_debug_marker_thunks| are copied. +typedef size_t (*MojoSetGLES2ImplExtDebugMarkerThunksFn)( + const MojoGLES2ImplExtDebugMarkerThunks* thunks); + +#endif // MOJO_PUBLIC_PLATFORM_NATIVE_GLES2_IMPL_EXT_DEBUG_MARKER_THUNKS_H_ diff --git a/mojo/public/platform/native/gles2_impl_oes_vertex_array_object_thunks.cc b/mojo/public/platform/native/gles2_impl_oes_vertex_array_object_thunks.cc new file mode 100644 index 00000000000..4bfcc97b210 --- /dev/null +++ b/mojo/public/platform/native/gles2_impl_oes_vertex_array_object_thunks.cc @@ -0,0 +1,35 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/platform/native/gles2_impl_oes_vertex_array_object_thunks.h" + +#include + +#include "mojo/public/platform/native/thunk_export.h" + +extern "C" { + +static MojoGLES2ImplOesVertexArrayObjectThunks + g_impl_oes_vertex_array_object_thunks = {0}; + +#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) \ + ReturnType gl##Function PARAMETERS { \ + assert(g_impl_oes_vertex_array_object_thunks.Function); \ + return g_impl_oes_vertex_array_object_thunks.Function ARGUMENTS; \ + } +#include "mojo/public/c/gles2/gles2_call_visitor_oes_vertex_array_object_autogen.h" +#undef VISIT_GL_CALL + +extern "C" THUNK_EXPORT size_t MojoSetGLES2ImplOesVertexArrayObjectThunks( + const MojoGLES2ImplOesVertexArrayObjectThunks* + gles2_impl_oes_vertex_array_object_thunks) { + if (gles2_impl_oes_vertex_array_object_thunks->size >= + sizeof(g_impl_oes_vertex_array_object_thunks)) { + g_impl_oes_vertex_array_object_thunks = + *gles2_impl_oes_vertex_array_object_thunks; + } + return sizeof(g_impl_oes_vertex_array_object_thunks); +} + +} // extern "C" diff --git a/mojo/public/platform/native/gles2_impl_oes_vertex_array_object_thunks.h b/mojo/public/platform/native/gles2_impl_oes_vertex_array_object_thunks.h new file mode 100644 index 00000000000..6d7f2507135 --- /dev/null +++ b/mojo/public/platform/native/gles2_impl_oes_vertex_array_object_thunks.h @@ -0,0 +1,45 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_PLATFORM_NATIVE_GLES2_IMPL_OES_VERTEX_ARRAY_OBJECT_THUNKS_H_ +#define MOJO_PUBLIC_PLATFORM_NATIVE_GLES2_IMPL_OES_VERTEX_ARRAY_OBJECT_THUNKS_H_ + +#include + +#include "mojo/public/c/gles2/oes_vertex_array_object.h" + +// Specifies the frozen API for the Vertex Array Object Extension. +#pragma pack(push, 8) +struct MojoGLES2ImplOesVertexArrayObjectThunks { + size_t size; // Should be set to sizeof(*this). + +#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) \ + ReturnType(*Function) PARAMETERS; +#include "mojo/public/c/gles2/gles2_call_visitor_oes_vertex_array_object_autogen.h" +#undef VISIT_GL_CALL +}; +#pragma pack(pop) + +// Intended to be called from the embedder to get the embedder's implementation +// of GLES2. +inline MojoGLES2ImplOesVertexArrayObjectThunks +MojoMakeGLES2ImplOesVertexArrayObjectThunks() { + MojoGLES2ImplOesVertexArrayObjectThunks + gles2_impl_oes_vertex_array_object_thunks = { + sizeof(MojoGLES2ImplOesVertexArrayObjectThunks), +#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) gl##Function, +#include "mojo/public/c/gles2/gles2_call_visitor_oes_vertex_array_object_autogen.h" +#undef VISIT_GL_CALL + }; + + return gles2_impl_oes_vertex_array_object_thunks; +} + +// Use this type for the function found by dynamically discovering it in +// a DSO linked with mojo_system. +// The contents of |gles2_impl_oes_vertex_array_object_thunks| are copied. +typedef size_t (*MojoSetGLES2ImplOesVertexArrayObjectThunksFn)( + const MojoGLES2ImplOesVertexArrayObjectThunks* thunks); + +#endif // MOJO_PUBLIC_PLATFORM_NATIVE_GLES2_IMPL_OES_VERTEX_ARRAY_OBJECT_THUNKS_H_ diff --git a/mojo/public/tools/NETWORK_SERVICE_VERSION b/mojo/public/tools/NETWORK_SERVICE_VERSION index 09436b55bf7..91026c67030 100644 --- a/mojo/public/tools/NETWORK_SERVICE_VERSION +++ b/mojo/public/tools/NETWORK_SERVICE_VERSION @@ -1 +1 @@ -f4116cee892c698374ee7ae9ad8e6cea69741122 +1b87e90f670ebfee2b55df21b07dc37dc628fe40 diff --git a/mojo/public/tools/dart_analyze.py b/mojo/public/tools/dart_analyze.py index 513a6c271b3..23119eea505 100755 --- a/mojo/public/tools/dart_analyze.py +++ b/mojo/public/tools/dart_analyze.py @@ -3,7 +3,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -# To integrate dartanalyze with out build system, we take an input file, run +# To integrate dartanalyze with our build system, we take an input file, run # the analyzer on it, and write a stamp file if it passed. # This script can either analyze a dartzip package, specified with the diff --git a/mojo/services/keyboard/public/interfaces/keyboard.mojom b/mojo/services/keyboard/public/interfaces/keyboard.mojom index 55ec182ebec..f47ecc4379f 100644 --- a/mojo/services/keyboard/public/interfaces/keyboard.mojom +++ b/mojo/services/keyboard/public/interfaces/keyboard.mojom @@ -28,8 +28,17 @@ interface KeyboardClient { SetSelection(int32 start, int32 end); }; +// Loosely modeled on Android InputType: +// http://developer.android.com/reference/android/text/InputType.html +enum KeyboardType { + TEXT, + NUMBER, + PHONE, + DATETIME, +}; + interface KeyboardService { - Show(KeyboardClient client); + Show(KeyboardClient client, KeyboardType type); ShowByRequest(); Hide(); }; diff --git a/mojo/services/prediction/public/interfaces/prediction.mojom b/mojo/services/prediction/public/interfaces/prediction.mojom index 58fa8d2510f..e1397244bf7 100644 --- a/mojo/services/prediction/public/interfaces/prediction.mojom +++ b/mojo/services/prediction/public/interfaces/prediction.mojom @@ -5,19 +5,16 @@ [DartPackage="mojo_services"] module prediction; -struct Settings { - bool correction_enabled; - bool block_potentially_offensive; - bool space_aware_gesture_enabled; +struct PrevWordInfo { + string word; + bool is_beginning_of_sentence; }; struct PredictionInfo { - array previous_words; - string current_word; + array previous_words; + string current_word; }; interface PredictionService { - SetSettings(Settings settings); - GetPredictionList(PredictionInfo prediction_info) => (array? prediction_list); }; diff --git a/mojo/tools/data/unittests b/mojo/tools/data/unittests index ecfd2f96318..39af3241aaf 100644 --- a/mojo/tools/data/unittests +++ b/mojo/tools/data/unittests @@ -56,6 +56,9 @@ tests = [ "test": "mojo_shell_tests", "cacheable": False, }, + { + "test": "crash_unittests", + }, ] if config.target_os != config.OS_ANDROID: diff --git a/mojo/tools/get_test_list.py b/mojo/tools/get_test_list.py index 47f292cc927..341b3d803e7 100755 --- a/mojo/tools/get_test_list.py +++ b/mojo/tools/get_test_list.py @@ -159,11 +159,11 @@ def GetTestList(config, verbose_count=0): "--build-dir=" + build_dir, "--dart-exe=third_party/dart-sdk/dart-sdk/bin/dart"]) - AddEntry("Dart HTTP Load test", - ["python", - os.path.join("mojo", "dart", "http_load_test", "runner.py"), - "--build-dir=" + build_dir, - "--dart-exe=third_party/dart-sdk/dart-sdk/bin/dart"]) + AddEntry("Dart HTTP Load test", + ["python", + os.path.join("mojo", "dart", "http_load_test", "runner.py"), + "--build-dir=" + build_dir, + "--dart-exe=third_party/dart-sdk/dart-sdk/bin/dart"]) # mojo tools unit tests: if ShouldRunTest(Config.TEST_TYPE_DEFAULT, Config.TEST_TYPE_UNIT, "tools"): @@ -172,6 +172,14 @@ def GetTestList(config, verbose_count=0): "mojom_fetcher", "mojom_fetcher_tests.py")]) + # Dart mojom package generate.dart script tests: + if target_os == Config.OS_LINUX: + AddEntry("Dart mojom package generate tests", + [os.path.join("third_party", "dart-sdk", "dart-sdk", "bin", "dart"), + "--checked", + "-p", os.path.join("mojo", "dart", "mojom", "packages"), + os.path.join("mojo", "dart", "mojom", "test", "generate_test.dart")]) + # Perf tests ----------------------------------------------------------------- if target_os == Config.OS_LINUX and ShouldRunTest(Config.TEST_TYPE_PERF): diff --git a/mojo/tools/linux64/dump_syms.sha1 b/mojo/tools/linux64/dump_syms.sha1 new file mode 100644 index 00000000000..fd689489d1f --- /dev/null +++ b/mojo/tools/linux64/dump_syms.sha1 @@ -0,0 +1 @@ +4bd8f841d0cebc3f7dac4c5bf2cdf86694605692 diff --git a/mojo/tools/linux64/symupload.sha1 b/mojo/tools/linux64/symupload.sha1 new file mode 100644 index 00000000000..d4563e44cc0 --- /dev/null +++ b/mojo/tools/linux64/symupload.sha1 @@ -0,0 +1 @@ +5d03513c81fba7f0acdc3ea314c46667d6624eb6 \ No newline at end of file diff --git a/mojo/tools/mojo_shell.py b/mojo/tools/mojo_shell.py deleted file mode 100755 index e4da359a6cb..00000000000 --- a/mojo/tools/mojo_shell.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python -# Copyright 2014 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import sys - - -def main(): - print 'Good news, the shell runner has moved! Please use: ' - print '' - print ' mojo/devtools/common/mojo_run' - print '' - print 'as you would use mojo_shell.py before.' - return -1 - -if __name__ == "__main__": - sys.exit(main()) diff --git a/mojo/tools/upload_binaries.py b/mojo/tools/upload_binaries.py index c10c2ec4ba4..d1c25cc9890 100755 --- a/mojo/tools/upload_binaries.py +++ b/mojo/tools/upload_binaries.py @@ -74,6 +74,13 @@ def find_architecture_independent_files(build_dir): return existing_files +def check_call(command_line, dry_run, **kwargs): + if dry_run: + print command_line + else: + subprocess.check_call(command_line, **kwargs) + + def upload(config, source, dest, dry_run, gzip=False): paths = Paths(config) sys.path.insert(0, os.path.join(paths.src_root, "tools")) @@ -89,23 +96,31 @@ def upload(config, source, dest, dry_run, gzip=False): command_line.extend(["-z", extension]) command_line.extend([source, dest]) - if dry_run: - print command_line - else: - subprocess.check_call(command_line) + check_call(command_line, dry_run) -def upload_symbols(config, build_dir, dry_run): +def upload_symbols(config, build_dir, breakpad_upload_urls, dry_run): + dump_syms_exe = os.path.join(Paths().src_root, + "mojo", "tools", "linux64", "dump_syms") + symupload_exe = os.path.join(Paths().src_root, + "mojo", "tools", "linux64", "symupload") dest_dir = "gs://mojo/symbols/" symbols_dir = os.path.join(build_dir, "symbols") - for name in os.listdir(symbols_dir): - path = os.path.join(symbols_dir, name) - with open(path) as f: - signature = signatures.get_signature(f, elffile) - if signature is not None: - dest = dest_dir + signature - upload(config, path, dest, dry_run) - + with open(os.devnull, "w") as devnull: + for name in os.listdir(symbols_dir): + path = os.path.join(symbols_dir, name) + with open(path) as f: + signature = signatures.get_signature(f, elffile) + if signature is not None: + dest = dest_dir + signature + upload(config, path, dest, dry_run) + if breakpad_upload_urls: + with tempfile.NamedTemporaryFile() as temp: + check_call([dump_syms_exe, path], dry_run, + stdout=temp, stderr=devnull) + temp.flush() + for upload_url in breakpad_upload_urls: + check_call([symupload_exe, temp.name, upload_url], dry_run) def upload_shell(config, dry_run, verbose): paths = Paths(config) @@ -179,6 +194,9 @@ def main(): parser.add_argument("--official", action="store_true", help="Upload the official build of the Android shell") + parser.add_argument("--symbols-upload-url", + action="append", default=[], + help="URL of the server to upload breakpad symbols to") args = parser.parse_args() is_official_build = args.official @@ -207,7 +225,8 @@ def main(): for file_to_upload in files_to_upload: upload_file(file_to_upload, config, args.dry_run) - upload_symbols(config, build_directory, args.dry_run) + upload_symbols(config, build_directory, + args.symbols_upload_url, args.dry_run) return 0 diff --git a/services/keyboard/BUILD.gn b/services/keyboard/BUILD.gn index b8d2dc7eb0f..b79633f0071 100644 --- a/services/keyboard/BUILD.gn +++ b/services/keyboard/BUILD.gn @@ -9,6 +9,7 @@ android_library("keyboard") { java_files = [ "src/org/chromium/mojo/keyboard/InputConnectionAdaptor.java", "src/org/chromium/mojo/keyboard/KeyboardServiceImpl.java", + "src/org/chromium/mojo/keyboard/KeyboardServiceState.java", ] deps = [ diff --git a/services/keyboard/src/org/chromium/mojo/keyboard/InputConnectionAdaptor.java b/services/keyboard/src/org/chromium/mojo/keyboard/InputConnectionAdaptor.java index febfe5137d9..42bdd57f180 100644 --- a/services/keyboard/src/org/chromium/mojo/keyboard/InputConnectionAdaptor.java +++ b/services/keyboard/src/org/chromium/mojo/keyboard/InputConnectionAdaptor.java @@ -4,7 +4,6 @@ package org.chromium.mojo.keyboard; -import android.text.InputType; import android.view.View; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.CompletionInfo; @@ -25,7 +24,6 @@ public class InputConnectionAdaptor extends BaseInputConnection { super(view, true); assert client != null; mClient = client; - outAttrs.inputType = InputType.TYPE_CLASS_TEXT; outAttrs.initialSelStart = -1; outAttrs.initialSelEnd = -1; } diff --git a/services/keyboard/src/org/chromium/mojo/keyboard/KeyboardServiceImpl.java b/services/keyboard/src/org/chromium/mojo/keyboard/KeyboardServiceImpl.java index 76891971ddd..42dbc7a79d9 100644 --- a/services/keyboard/src/org/chromium/mojo/keyboard/KeyboardServiceImpl.java +++ b/services/keyboard/src/org/chromium/mojo/keyboard/KeyboardServiceImpl.java @@ -5,70 +5,74 @@ package org.chromium.mojo.keyboard; import android.content.Context; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputConnection; +import android.text.InputType; import android.view.inputmethod.InputMethodManager; import org.chromium.mojo.system.MojoException; import org.chromium.mojom.keyboard.KeyboardClient; import org.chromium.mojom.keyboard.KeyboardService; +import org.chromium.mojom.keyboard.KeyboardType; /** * Android implementation of Keyboard. */ public class KeyboardServiceImpl implements KeyboardService { - private static View sActiveView; - private static KeyboardClient sActiveClient; - + // We have a unique ServiceImpl per connection. However the state + // for the keyboard instance is per-view. However we don't have the + // concept of per-view services, so we currently have a hack by which + // we set the "active view" and its associated per-view keyboard state. + private static KeyboardServiceState sViewState; private Context mContext; public KeyboardServiceImpl(Context context) { mContext = context; } - public static void setActiveView(View view) { - sActiveView = view; + public static void setViewState(KeyboardServiceState state) { + if (sViewState != null) sViewState.close(); + sViewState = state; } - public static InputConnection createInputConnection(EditorInfo outAttrs) { - if (sActiveClient == null) return null; - return new InputConnectionAdaptor(sActiveView, sActiveClient, outAttrs); + private static int inputTypeFromKeyboardType(int keyboardType) { + if (keyboardType == KeyboardType.DATETIME) return InputType.TYPE_CLASS_DATETIME; + if (keyboardType == KeyboardType.NUMBER) return InputType.TYPE_CLASS_NUMBER; + if (keyboardType == KeyboardType.PHONE) return InputType.TYPE_CLASS_PHONE; + return InputType.TYPE_CLASS_TEXT; } @Override public void close() { - if (sActiveClient != null) { - sActiveClient.close(); - sActiveClient = null; - } + if (sViewState == null) return; + sViewState.close(); } @Override public void onConnectionError(MojoException e) {} @Override - public void show(KeyboardClient client) { - sActiveClient = client; + public void show(KeyboardClient client, int keyboardType) { + if (sViewState == null) return; + sViewState.setClient(client, inputTypeFromKeyboardType(keyboardType)); InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); - imm.restartInput(sActiveView); - imm.showSoftInput(sActiveView, InputMethodManager.SHOW_IMPLICIT); + imm.restartInput(sViewState.getView()); + imm.showSoftInput(sViewState.getView(), InputMethodManager.SHOW_IMPLICIT); } @Override public void showByRequest() { + if (sViewState == null) return; InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(sActiveView, 0); + imm.showSoftInput(sViewState.getView(), 0); } @Override public void hide() { + if (sViewState == null) return; InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(sActiveView.getApplicationWindowToken(), 0); - sActiveClient.close(); - sActiveClient = null; + imm.hideSoftInputFromWindow(sViewState.getView().getApplicationWindowToken(), 0); + close(); } } diff --git a/services/keyboard/src/org/chromium/mojo/keyboard/KeyboardServiceState.java b/services/keyboard/src/org/chromium/mojo/keyboard/KeyboardServiceState.java new file mode 100644 index 00000000000..7196b24f89b --- /dev/null +++ b/services/keyboard/src/org/chromium/mojo/keyboard/KeyboardServiceState.java @@ -0,0 +1,49 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.keyboard; + +import android.text.InputType; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; + +import org.chromium.mojom.keyboard.KeyboardClient; + +/** + * Per-View keyboard state. + */ +public class KeyboardServiceState { + private View mView; + private KeyboardClient mActiveClient; + private int mRequestedInputType; + + public KeyboardServiceState(View view) { + mView = view; + mActiveClient = null; + mRequestedInputType = InputType.TYPE_CLASS_TEXT; + } + + public InputConnection createInputConnection(EditorInfo outAttrs) { + if (mActiveClient == null) return null; + outAttrs.inputType = mRequestedInputType; + return new InputConnectionAdaptor(mView, mActiveClient, outAttrs); + } + + public void setClient(KeyboardClient client, int inputType) { + if (mActiveClient != null) mActiveClient.close(); + mActiveClient = client; + mRequestedInputType = inputType; + } + + public View getView() { + return mView; + } + + public void close() { + if (mActiveClient == null) return; + mActiveClient.close(); + mActiveClient = null; + } +} diff --git a/sky/packages/sky/lib/editing/input.dart b/sky/packages/sky/lib/editing/input.dart index b63251ec455..9e1b44a5a09 100644 --- a/sky/packages/sky/lib/editing/input.dart +++ b/sky/packages/sky/lib/editing/input.dart @@ -59,7 +59,7 @@ class Input extends StatefulComponent { bool focused = Focus.at(this); if (focused && !_keyboardHandle.attached) { - _keyboardHandle = keyboard.show(_editableValue.stub); + _keyboardHandle = keyboard.show(_editableValue.stub, KeyboardType_TEXT); } else if (!focused && _keyboardHandle.attached) { _keyboardHandle.release(); } diff --git a/sky/packages/sky/lib/mojo/keyboard.dart b/sky/packages/sky/lib/mojo/keyboard.dart index 8b5f4ccf5fb..ea0f8f4c4ad 100644 --- a/sky/packages/sky/lib/mojo/keyboard.dart +++ b/sky/packages/sky/lib/mojo/keyboard.dart @@ -4,6 +4,7 @@ import 'package:mojo_services/keyboard/keyboard.mojom.dart'; import 'package:sky/mojo/shell.dart' as shell; +export 'package:mojo_services/keyboard/keyboard.mojom.dart'; class _KeyboardConnection { @@ -29,14 +30,14 @@ class Keyboard { KeyboardHandle _currentHandle; - KeyboardHandle show(KeyboardClientStub stub) { + KeyboardHandle show(KeyboardClientStub stub, int keyboardType) { assert(stub != null); if (_currentHandle != null) { if (_currentHandle.stub == stub) return _currentHandle; _currentHandle.release(); } - _currentHandle = new KeyboardHandle._show(this, stub); + _currentHandle = new KeyboardHandle._show(this, stub, keyboardType); return _currentHandle; } @@ -44,8 +45,8 @@ class Keyboard { class KeyboardHandle { - KeyboardHandle._show(Keyboard keyboard, this.stub) : _keyboard = keyboard { - _keyboard.service.show(stub); + KeyboardHandle._show(Keyboard keyboard, this.stub, int keyboardType) : _keyboard = keyboard { + _keyboard.service.show(stub, keyboardType); _attached = true; } diff --git a/sky/packages/sky/pubspec.yaml b/sky/packages/sky/pubspec.yaml index 5aa70777688..9078e5a9ece 100644 --- a/sky/packages/sky/pubspec.yaml +++ b/sky/packages/sky/pubspec.yaml @@ -6,8 +6,8 @@ homepage: https://github.com/domokit/sky_engine/tree/master/sky/packages/sky dependencies: cassowary: ^0.1.7 material_design_icons: ^0.0.2 - mojo_services: 0.0.21 - mojo: 0.0.21 + mojo_services: 0.0.22 + mojo: 0.0.22 newton: ^0.1.2 sky_engine: ^0.0.6 sky_services: ^0.0.6 diff --git a/sky/shell/android/org/domokit/sky/shell/PlatformViewAndroid.java b/sky/shell/android/org/domokit/sky/shell/PlatformViewAndroid.java index a9e4dbb1fdb..a624387b1cd 100644 --- a/sky/shell/android/org/domokit/sky/shell/PlatformViewAndroid.java +++ b/sky/shell/android/org/domokit/sky/shell/PlatformViewAndroid.java @@ -17,6 +17,7 @@ import android.view.inputmethod.InputConnection; import org.chromium.base.JNINamespace; import org.chromium.mojo.bindings.InterfaceRequest; import org.chromium.mojo.keyboard.KeyboardServiceImpl; +import org.chromium.mojo.keyboard.KeyboardServiceState; import org.chromium.mojo.system.Core; import org.chromium.mojo.system.Pair; import org.chromium.mojo.system.impl.CoreImpl; @@ -40,6 +41,7 @@ public class PlatformViewAndroid extends SurfaceView private final SurfaceHolder.Callback mSurfaceCallback; private GestureProvider mGestureProvider; private final EdgeDims mPadding; + private final KeyboardServiceState mKeyboardState; /** * Dimensions in each of the four cardinal directions. @@ -95,7 +97,10 @@ public class PlatformViewAndroid extends SurfaceView getHolder().addCallback(mSurfaceCallback); mGestureProvider = new GestureProvider(context, this); - KeyboardServiceImpl.setActiveView(this); + + // TODO(eseidel): We need per-view services! + mKeyboardState = new KeyboardServiceState(this); + KeyboardServiceImpl.setViewState(mKeyboardState); } SkyEngine getEngine() { @@ -125,7 +130,7 @@ public class PlatformViewAndroid extends SurfaceView @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { - return KeyboardServiceImpl.createInputConnection(outAttrs); + return mKeyboardState.createInputConnection(outAttrs); } private int getTypeForAction(int maskedAction) { diff --git a/third_party/dart-pkg/archive b/third_party/dart-pkg/archive new file mode 160000 index 00000000000..07ffd98c540 --- /dev/null +++ b/third_party/dart-pkg/archive @@ -0,0 +1 @@ +Subproject commit 07ffd98c5403b7f9ae067b57dc9487611be420f5 diff --git a/third_party/dart-pkg/args b/third_party/dart-pkg/args new file mode 160000 index 00000000000..e0e8377412e --- /dev/null +++ b/third_party/dart-pkg/args @@ -0,0 +1 @@ +Subproject commit e0e8377412ee6cd6a5a4a8632848181c1db91f44 diff --git a/third_party/dart-pkg/box2d b/third_party/dart-pkg/box2d new file mode 160000 index 00000000000..c5e65d95462 --- /dev/null +++ b/third_party/dart-pkg/box2d @@ -0,0 +1 @@ +Subproject commit c5e65d9546275e78ad2a1d51b459e7638f6e4323 diff --git a/third_party/dart-pkg/cassowary b/third_party/dart-pkg/cassowary new file mode 160000 index 00000000000..7e5afc5b395 --- /dev/null +++ b/third_party/dart-pkg/cassowary @@ -0,0 +1 @@ +Subproject commit 7e5afc5b3956a18636d5b37b1dcba1705865564b diff --git a/third_party/dart-pkg/collection b/third_party/dart-pkg/collection new file mode 160000 index 00000000000..79ebc6fc2da --- /dev/null +++ b/third_party/dart-pkg/collection @@ -0,0 +1 @@ +Subproject commit 79ebc6fc2dae581cb23ad50a5c600c1b7dd132f8 diff --git a/third_party/dart-pkg/crypto b/third_party/dart-pkg/crypto new file mode 160000 index 00000000000..d4558dea163 --- /dev/null +++ b/third_party/dart-pkg/crypto @@ -0,0 +1 @@ +Subproject commit d4558dea1639e5ad2a41d045265b8ece270c2d90 diff --git a/third_party/dart-pkg/newton b/third_party/dart-pkg/newton new file mode 160000 index 00000000000..11cd659e165 --- /dev/null +++ b/third_party/dart-pkg/newton @@ -0,0 +1 @@ +Subproject commit 11cd659e1650591402b69f91b9c4d2a0841fd5bd diff --git a/third_party/dart-pkg/path b/third_party/dart-pkg/path new file mode 160000 index 00000000000..2f3dcdec320 --- /dev/null +++ b/third_party/dart-pkg/path @@ -0,0 +1 @@ +Subproject commit 2f3dcdec32011f1bc41194ae3640d6d9292a7096 diff --git a/third_party/dart-pkg/quiver b/third_party/dart-pkg/quiver new file mode 160000 index 00000000000..6bab7dec341 --- /dev/null +++ b/third_party/dart-pkg/quiver @@ -0,0 +1 @@ +Subproject commit 6bab7dec34189eee579178eb16d3063c8ae69031 diff --git a/third_party/dart-pkg/source_span b/third_party/dart-pkg/source_span new file mode 160000 index 00000000000..5c6c13f62fc --- /dev/null +++ b/third_party/dart-pkg/source_span @@ -0,0 +1 @@ +Subproject commit 5c6c13f62fc111adaace3aeb4a38853d64481d06 diff --git a/third_party/dart-pkg/string_scanner b/third_party/dart-pkg/string_scanner new file mode 160000 index 00000000000..9f00056b32f --- /dev/null +++ b/third_party/dart-pkg/string_scanner @@ -0,0 +1 @@ +Subproject commit 9f00056b32f41efc376adecfb696a01bc7c593d7 diff --git a/third_party/dart-pkg/vector_math b/third_party/dart-pkg/vector_math new file mode 160000 index 00000000000..65915583f7a --- /dev/null +++ b/third_party/dart-pkg/vector_math @@ -0,0 +1 @@ +Subproject commit 65915583f7aa606cb47ed265f853c18c60102b81 diff --git a/third_party/dart-pkg/yaml b/third_party/dart-pkg/yaml new file mode 160000 index 00000000000..d8c1ce75edf --- /dev/null +++ b/third_party/dart-pkg/yaml @@ -0,0 +1 @@ +Subproject commit d8c1ce75edf051ea1d5583b24474f8656abb4920