mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Merge pull request #489 from eseidelGoogle/roll_mojo
Roll mojo to 35de44e361
This commit is contained in:
commit
287de982d9
@ -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)
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
17
mojo/common/dart/BUILD.gn
Normal 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",
|
||||
]
|
||||
}
|
||||
46
mojo/common/dart/lib/trace_controller_impl.dart
Normal file
46
mojo/common/dart/lib/trace_controller_impl.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
109
mojo/common/dart/lib/tracing_helper.dart
Normal file
109
mojo/common/dart/lib/tracing_helper.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
mojo/common/dart/pubspec.lock
Normal file
3
mojo/common/dart/pubspec.lock
Normal file
@ -0,0 +1,3 @@
|
||||
# Generated by pub
|
||||
# See http://pub.dartlang.org/doc/glossary.html#lockfile
|
||||
packages: {}
|
||||
2
mojo/common/dart/pubspec.yaml
Normal file
2
mojo/common/dart/pubspec.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
author: Chromium Authors <mojo-dev@googlegroups.com>
|
||||
name: common
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -9,6 +9,7 @@ dart_pkg("mojom") {
|
||||
sources = [
|
||||
"CHANGELOG.md",
|
||||
"README.md",
|
||||
"lib/src/options.dart",
|
||||
"lib/src/utils.dart",
|
||||
"pubspec.yaml",
|
||||
]
|
||||
|
||||
@ -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
|
||||
```
|
||||
|
||||
@ -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) {
|
||||
|
||||
123
mojo/dart/mojom/lib/src/options.dart
Normal file
123
mojo/dart/mojom/lib/src/options.dart
Normal 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);
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
140
mojo/devtools/common/devtoolslib/http_server_unittest.py
Normal file
140
mojo/devtools/common/devtoolslib/http_server_unittest.py
Normal 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()
|
||||
@ -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)
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
|
||||
196
mojo/devtools/common/devtoolslib/shell_config.py
Normal file
196
mojo/devtools/common/devtoolslib/shell_config.py
Normal 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
|
||||
16
mojo/devtools/common/devtoolslib/utils.py
Normal file
16
mojo/devtools/common/devtoolslib/utils.py
Normal 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
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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" ]
|
||||
|
||||
@ -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" {
|
||||
|
||||
29
mojo/public/c/gles2/ext_debug_marker.h
Normal file
29
mojo/public/c/gles2/ext_debug_marker.h
Normal 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_
|
||||
@ -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, (), ())
|
||||
@ -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))
|
||||
@ -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" {
|
||||
|
||||
29
mojo/public/c/gles2/oes_vertex_array_object.h
Normal file
29
mojo/public/c/gles2/oes_vertex_array_object.h
Normal 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_
|
||||
@ -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)) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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"
|
||||
@ -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_
|
||||
@ -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"
|
||||
@ -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_
|
||||
@ -1 +1 @@
|
||||
f4116cee892c698374ee7ae9ad8e6cea69741122
|
||||
1b87e90f670ebfee2b55df21b07dc37dc628fe40
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
};
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
@ -56,6 +56,9 @@ tests = [
|
||||
"test": "mojo_shell_tests",
|
||||
"cacheable": False,
|
||||
},
|
||||
{
|
||||
"test": "crash_unittests",
|
||||
},
|
||||
]
|
||||
|
||||
if config.target_os != config.OS_ANDROID:
|
||||
|
||||
@ -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):
|
||||
|
||||
1
mojo/tools/linux64/dump_syms.sha1
Normal file
1
mojo/tools/linux64/dump_syms.sha1
Normal file
@ -0,0 +1 @@
|
||||
4bd8f841d0cebc3f7dac4c5bf2cdf86694605692
|
||||
1
mojo/tools/linux64/symupload.sha1
Normal file
1
mojo/tools/linux64/symupload.sha1
Normal file
@ -0,0 +1 @@
|
||||
5d03513c81fba7f0acdc3ea314c46667d6624eb6
|
||||
@ -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())
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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 = [
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
1
third_party/dart-pkg/archive
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 07ffd98c5403b7f9ae067b57dc9487611be420f5
|
||||
1
third_party/dart-pkg/args
vendored
Submodule
1
third_party/dart-pkg/args
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit e0e8377412ee6cd6a5a4a8632848181c1db91f44
|
||||
1
third_party/dart-pkg/box2d
vendored
Submodule
1
third_party/dart-pkg/box2d
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit c5e65d9546275e78ad2a1d51b459e7638f6e4323
|
||||
1
third_party/dart-pkg/cassowary
vendored
Submodule
1
third_party/dart-pkg/cassowary
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 7e5afc5b3956a18636d5b37b1dcba1705865564b
|
||||
1
third_party/dart-pkg/collection
vendored
Submodule
1
third_party/dart-pkg/collection
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 79ebc6fc2dae581cb23ad50a5c600c1b7dd132f8
|
||||
1
third_party/dart-pkg/crypto
vendored
Submodule
1
third_party/dart-pkg/crypto
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit d4558dea1639e5ad2a41d045265b8ece270c2d90
|
||||
1
third_party/dart-pkg/newton
vendored
Submodule
1
third_party/dart-pkg/newton
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 11cd659e1650591402b69f91b9c4d2a0841fd5bd
|
||||
1
third_party/dart-pkg/path
vendored
Submodule
1
third_party/dart-pkg/path
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 2f3dcdec32011f1bc41194ae3640d6d9292a7096
|
||||
1
third_party/dart-pkg/quiver
vendored
Submodule
1
third_party/dart-pkg/quiver
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 6bab7dec34189eee579178eb16d3063c8ae69031
|
||||
1
third_party/dart-pkg/source_span
vendored
Submodule
1
third_party/dart-pkg/source_span
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 5c6c13f62fc111adaace3aeb4a38853d64481d06
|
||||
1
third_party/dart-pkg/string_scanner
vendored
Submodule
1
third_party/dart-pkg/string_scanner
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 9f00056b32f41efc376adecfb696a01bc7c593d7
|
||||
1
third_party/dart-pkg/vector_math
vendored
Submodule
1
third_party/dart-pkg/vector_math
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 65915583f7aa606cb47ed265f853c18c60102b81
|
||||
1
third_party/dart-pkg/yaml
vendored
Submodule
1
third_party/dart-pkg/yaml
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit d8c1ce75edf051ea1d5583b24474f8656abb4920
|
||||
Loading…
x
Reference in New Issue
Block a user