Merge pull request #489 from eseidelGoogle/roll_mojo

Roll mojo to 35de44e361
This commit is contained in:
Eric Seidel 2015-08-06 16:22:15 -07:00
commit 287de982d9
85 changed files with 2190 additions and 874 deletions

View File

@ -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)

View File

@ -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;

View File

@ -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();
}

View File

@ -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> 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,

View File

@ -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",
]

17
mojo/common/dart/BUILD.gn Normal file
View File

@ -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",
]
}

View File

@ -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);
}
}
}

View File

@ -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<String, String> 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<String, String> 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<String, String> 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);
}
}
}

View File

@ -0,0 +1,3 @@
# Generated by pub
# See http://pub.dartlang.org/doc/glossary.html#lockfile
packages: {}

View File

@ -0,0 +1,2 @@
author: Chromium Authors <mojo-dev@googlegroups.com>
name: common

View File

@ -6,70 +6,311 @@
#include <stdio.h>
#include <limits>
#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<void(bool)>& 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<void(bool)> callback_;
base::File file_;
scoped_ptr<AsyncWaiter> waiter_;
const void* buffer_;
uint32_t buffer_size_;
scoped_refptr<base::SingleThreadTaskRunner> main_runner_;
DISALLOW_COPY_AND_ASSIGN(CopyToFileHandler);
};
CopyToFileHandler::CopyToFileHandler(ScopedDataPipeConsumerHandle source,
const base::FilePath& destination,
base::TaskRunner* task_runner,
const base::Callback<void(bool)>& 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<void(bool)> 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<char*>(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<const char*>(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<void(bool)>& 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<void(bool)> callback_;
base::File file_;
scoped_ptr<AsyncWaiter> waiter_;
void* buffer_;
uint32_t buffer_size_;
scoped_refptr<base::SingleThreadTaskRunner> 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<void(bool)>& 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<void(bool)> 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<uint32_t>(std::numeric_limits<int>::max()));
int num_bytes = buffer_size_;
int num_bytes_read =
file_.ReadAtCurrentPos(static_cast<char*>(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<void(bool)>& 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<void(bool)>& 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

View File

@ -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))

View File

@ -9,6 +9,7 @@ dart_pkg("mojom") {
sources = [
"CHANGELOG.md",
"README.md",
"lib/src/options.dart",
"lib/src/utils.dart",
"pubspec.yaml",
]

View File

@ -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 </path/to/mojom/dir>
```
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
```

View File

@ -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<String, String> duplicateDetection = new Map<String, String>();
Map<String, String> 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<List<Directory>> 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<Directory> additionalDirs;
final bool download;
final bool generate;
GenerateOptions(
this.packages, this.mojomPackage, this.mojoSdk, this.additionalDirs,
this.download, this.generate);
}
Future<GenerateOptions> parseArguments(List<String> 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<String> arguments) async {
var options = await parseArguments(arguments);
duplicateDetection = new Map<String, String>();
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<String> 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) {

View File

@ -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<Directory> 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<List<Directory>> 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<GenerateOptions> parseArguments(List<String> 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);
}

View File

@ -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<String> getUrl(HttpClient httpClient, String url) async {
fileString.write(contents);
}
return fileString.toString();
} catch(e) {
} catch (e) {
throw new DownloadError("$e");
}
}

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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.

View File

@ -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.

View File

@ -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 '
'<url>=<url-or-local-file-path>')
parser.add_argument('--map-origin', action='append',
help='Define a mapping for a url origin in the format '
'<origin>=<url-or-local-file-path>')
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

View File

@ -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'

View File

@ -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 '
'<url>=<url-or-local-file-path>')
parser.add_argument('--map-origin', action='append',
help='Define a mapping for a url origin in the format '
'<origin>=<url-or-local-file-path>')
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

View File

@ -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

View File

@ -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=<mojo-app>] "
@ -16,7 +16,8 @@ _USAGE = ("mojo_run "
"[--enable-external-applications] "
"[--disable-cache] "
"[--enable-multiprocess] "
"[<mojo-app>] "
"[--wait-for-debugger] "
"[--sky <mojo-app>|<mojo-app>] "
"""
A <mojo-app> 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

View File

@ -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

View File

@ -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();

View File

@ -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<ProcessIdentifier, embedder::PlatformHandle>
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<int>(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<PendingConnectInfo> 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<int>(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;
}

View File

@ -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<ConnectionIdentifier, PendingConnectionInfo*>
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<ConnectionIdentifier, PendingConnectInfo*> 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<ProcessIdentifier, ProcessConnections*> connections_
MOJO_GUARDED_BY(mutex_); // Owns its values.
MOJO_DISALLOW_COPY_AND_ASSIGN(MasterConnectionManager);
};

View File

@ -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);

View File

@ -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"

View File

@ -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)

View File

@ -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)
}

View File

@ -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" ]

View File

@ -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" {

View File

@ -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 <stdint.h>
#include <GLES2/gl2.h>
#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_

View File

@ -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, (), ())

View File

@ -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))

View File

@ -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" {

View File

@ -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 <stdint.h>
#include <GLES2/gl2.h>
#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_

View File

@ -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)) {

View File

@ -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

View File

@ -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",

View File

@ -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 <assert.h>
#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"

View File

@ -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 <stddef.h>
#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_

View File

@ -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 <assert.h>
#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"

View File

@ -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 <stddef.h>
#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_

View File

@ -1 +1 @@
f4116cee892c698374ee7ae9ad8e6cea69741122
1b87e90f670ebfee2b55df21b07dc37dc628fe40

View File

@ -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

View File

@ -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();
};

View File

@ -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<string> previous_words;
string current_word;
array<PrevWordInfo> previous_words;
string current_word;
};
interface PredictionService {
SetSettings(Settings settings);
GetPredictionList(PredictionInfo prediction_info) => (array<string>? prediction_list);
};

View File

@ -56,6 +56,9 @@ tests = [
"test": "mojo_shell_tests",
"cacheable": False,
},
{
"test": "crash_unittests",
},
]
if config.target_os != config.OS_ANDROID:

View File

@ -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):

View File

@ -0,0 +1 @@
4bd8f841d0cebc3f7dac4c5bf2cdf86694605692

View File

@ -0,0 +1 @@
5d03513c81fba7f0acdc3ea314c46667d6624eb6

View File

@ -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())

View File

@ -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

View File

@ -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 = [

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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

View File

@ -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) {

1
third_party/dart-pkg/archive vendored Submodule

@ -0,0 +1 @@
Subproject commit 07ffd98c5403b7f9ae067b57dc9487611be420f5

1
third_party/dart-pkg/args vendored Submodule

@ -0,0 +1 @@
Subproject commit e0e8377412ee6cd6a5a4a8632848181c1db91f44

1
third_party/dart-pkg/box2d vendored Submodule

@ -0,0 +1 @@
Subproject commit c5e65d9546275e78ad2a1d51b459e7638f6e4323

1
third_party/dart-pkg/cassowary vendored Submodule

@ -0,0 +1 @@
Subproject commit 7e5afc5b3956a18636d5b37b1dcba1705865564b

1
third_party/dart-pkg/collection vendored Submodule

@ -0,0 +1 @@
Subproject commit 79ebc6fc2dae581cb23ad50a5c600c1b7dd132f8

1
third_party/dart-pkg/crypto vendored Submodule

@ -0,0 +1 @@
Subproject commit d4558dea1639e5ad2a41d045265b8ece270c2d90

1
third_party/dart-pkg/newton vendored Submodule

@ -0,0 +1 @@
Subproject commit 11cd659e1650591402b69f91b9c4d2a0841fd5bd

1
third_party/dart-pkg/path vendored Submodule

@ -0,0 +1 @@
Subproject commit 2f3dcdec32011f1bc41194ae3640d6d9292a7096

1
third_party/dart-pkg/quiver vendored Submodule

@ -0,0 +1 @@
Subproject commit 6bab7dec34189eee579178eb16d3063c8ae69031

1
third_party/dart-pkg/source_span vendored Submodule

@ -0,0 +1 @@
Subproject commit 5c6c13f62fc111adaace3aeb4a38853d64481d06

@ -0,0 +1 @@
Subproject commit 9f00056b32f41efc376adecfb696a01bc7c593d7

1
third_party/dart-pkg/vector_math vendored Submodule

@ -0,0 +1 @@
Subproject commit 65915583f7aa606cb47ed265f853c18c60102b81

1
third_party/dart-pkg/yaml vendored Submodule

@ -0,0 +1 @@
Subproject commit d8c1ce75edf051ea1d5583b24474f8656abb4920