Merge branch 'libuv-0.10-headless-display' into mason

Conflicts:
	common/http_request_baton_curl.cpp
	include/mbgl/map/map.hpp
	include/mbgl/util/uv_detail.hpp
	scripts/travis_before_install.sh
	setup-libraries.sh
	src/map/map.cpp
	src/storage/file_request_baton.cpp
	src/storage/http_request.cpp
	src/util/uv-messenger.c
This commit is contained in:
Konstantin Käfer
2014-10-22 15:14:21 +02:00
32 changed files with 895 additions and 143 deletions

View File

@@ -19,11 +19,13 @@ matrix:
env:
global:
- secure: "bG4YYWMfl9API0MSRgmOaJrlGLv06tRg9KJNawBieZvBJbITPpxVGJZT3/l/SEJ+Rl15e2dRex4k+MGQlmT2SqPQxTEYWv1qxNigKPPcla7IWeNmWWqW8uVvFjdglojgBOK2k/xErVQtA4zDfi3mwSXH4DKwquXWsoEKmX2SV7M="
- secure: "Cbvap9ubVKgjPe3hUhI6JGeDZzBXHpOG9RaYKh+SdoIPhKnlJiNOYm1egomi+e4uqJInlFKuVHTw7Ng9Cun6Zm0jIxpkSchv1GpsR7hmB3UGnGed19Dw8121FwuUaktN+4YnbVlsyd+u8EHD3+h58t4eELrLrZolM4rS7DL6caA="
- secure: "HPlPuLk531F7bCyADMcxQ+I1CfGhJlhX9A3q3auNCdLsjqaZbktaCIGuq4R1IPZz0UTtO8RfrTfVaoktyZdq2QyU3iD8oj6BeZ/Jypr4TCBJxI5IeyU3gyPMFHqUYIJuHL9k40Xu7Dhf4HT123zr8KoeVqCHauZP1/TumA0nezg="
- secure: "R/+uOeccl7GqjJbknailAV7VtjAtGwHHM9xToVnhfPVUXp9fzJs+oNk3IWcs3Eg4sw4WsYxXRfbCQG9bA3MnPlTOsxETGRioO9CAuXap6mwcwNAzeWuq7ePFki2u01pJGQD5b6WFqBCdnAQ4o2SgHgGzhs0TJzYd9tONQxSmkCI="
- secure: "RiBIBfVhhaMjU5ksuwJO3shdvG9FpinBjdSv4co9jg9171SR8edNriedHjVKSIeBhSGNmZmX+twS3dJS/By6tl/LKh9sTynA+ZAYYljkE7jn881B/gMrlYvdAA6og5KvkhV1/0iJWlhuZrMTkhpDR200iLgg3EWBhWjltzmDW/I="
- AWS_S3_BUCKET: 'node-mapbox-gl-native'
before_install:
- source ./scripts/travis_helper.sh
- source ./scripts/flags.sh
- (git clone https://github.com/mapbox/mason.git ~/.mason ; sudo ln -s ~/.mason/mason /usr/local/bin/mason)
- ./scripts/travis_before_install.sh

383
common/curl_request.cpp Normal file
View File

@@ -0,0 +1,383 @@
#include <mbgl/platform/platform.hpp>
#include <mbgl/platform/request.hpp>
#include <mbgl/util/uv_detail.hpp>
#include <mbgl/util/std.hpp>
#include <queue>
#include <boost/lockfree/queue.hpp>
#include <curl/curl.h>
// This file contains code from http://curl.haxx.se/libcurl/c/multi-uv.html:
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
***************************************************************************/
/* Example application code using the multi socket interface to download
multiple files at once, but instead of using curl_multi_perform and
curl_multi_wait, which uses select(), we use libuv.
It supports epoll, kqueue, etc. on unixes and fast IO completion ports on
Windows, which means, it should be very fast on all platforms..
Written by Clemens Gruber, based on an outdated example from uvbook and
some tests from libuv.
Requires libuv and (of course) libcurl.
See http://nikhilm.github.com/uvbook/ for more information on libuv.
*/
namespace mbgl {
namespace platform {
namespace request {
struct curl_context {
uv_poll_t poll_handle;
curl_socket_t sockfd;
};
// Handles the request thread + messaging to the thread.
static uv_thread_t thread;
static uv::once init_thread_once;
static uv_loop_t *loop = nullptr;
static uv_async_t async_add;
static uv_async_t async_cancel;
// Stores pointers (!) to shared_ptrs. We use shared_ptrs so that request objects don't get
// auto-destructed while they're in progress. The TileData object retains a weak_ptr to this
// request, so we have to use a shared_ptr here to ensure that this object stays alive.
static boost::lockfree::queue<std::shared_ptr<mbgl::platform::Request> *> add_queue(8);
static boost::lockfree::queue<std::shared_ptr<mbgl::platform::Request> *> cancel_queue(8);
// Used as the CURL timer function to periodically check for socket updates.
static uv_timer_t timeout;
// CURL multi handle that we use to request multiple URLs at the same time, without having to block
// and spawn threads.
static CURLM *curl_multi = nullptr;
// CURL share handles are used for sharing session state (e.g.)
static uv::mutex curl_share_mutex;
static CURLSH *curl_share = nullptr;
// A queue that we use for storing resuable CURL easy handles to avoid creating and destroying them
// all the time.
static std::queue<CURL *> curl_handle_cache;
class CURLRequest : public mbgl::platform::Request {
public:
CURLRequest(const std::string &url,
std::function<void(mbgl::platform::Response *)> callback,
std::shared_ptr<uv::loop> loop)
: Request(url, callback, loop) {}
CURL *curl = nullptr;
};
// Implementation starts here.
// Locks the CURL share handle
void curl_share_lock(CURL *, curl_lock_data, curl_lock_access, void *) { curl_share_mutex.lock(); }
// Unlocks the CURL share handle
void curl_share_unlock(CURL *, curl_lock_data, void *) { curl_share_mutex.unlock(); }
curl_context *create_curl_context(curl_socket_t sockfd) {
curl_context *context = new curl_context;
context->sockfd = sockfd;
uv_poll_init_socket(loop, &context->poll_handle, sockfd);
context->poll_handle.data = context;
return context;
}
void curl_close_cb(uv_handle_t *handle) {
curl_context *context = (curl_context *)handle->data;
free(context);
}
void destroy_curl_context(curl_context *context) {
uv_close((uv_handle_t *)&context->poll_handle, curl_close_cb);
}
void remove_curl_handle(CURL *handle) {
CURLMcode error = curl_multi_remove_handle(curl_multi, handle);
if (error != CURLM_OK) {
throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error));
}
curl_easy_reset(handle);
curl_handle_cache.push(handle);
}
void curl_perform(uv_poll_t *req, int /*status*/, int events) {
int running_handles;
int flags = 0;
curl_context *context;
CURLMsg *message;
int pending;
uv_timer_stop(&timeout);
if (events & UV_READABLE)
flags |= CURL_CSELECT_IN;
if (events & UV_WRITABLE)
flags |= CURL_CSELECT_OUT;
context = (curl_context *)req;
curl_multi_socket_action(curl_multi, context->sockfd, flags, &running_handles);
while ((message = curl_multi_info_read(curl_multi, &pending))) {
switch (message->msg) {
case CURLMSG_DONE: {
std::shared_ptr<Request> *req = nullptr;
curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&req);
// Add human-readable error code
if (message->data.result != CURLE_OK) {
(*req)->res->error_message = curl_easy_strerror(message->data.result);
(*req)->res->code = -1;
} else {
curl_easy_getinfo(message->easy_handle, CURLINFO_RESPONSE_CODE, &(*req)->res->code);
}
// We're currently in the CURL request thread. We're going to schedule a uv_work request
// that executes the background function in a threadpool, and tell it to call the
// after callback back in the main uv loop.
(*req)->complete();
CURL *handle = message->easy_handle;
remove_curl_handle(handle);
// We're setting this to NULL because there might still be shared_ptrs around that could
// be cancelled.
((CURLRequest *)req->get())->curl = nullptr;
// Delete the shared_ptr pointer we created earlier.
delete req;
break;
}
default:
// This should never happen, because there are no other message types.
throw std::runtime_error("CURLMSG returned unknown message type");
}
}
}
int handle_socket(CURL * /*easy*/, curl_socket_t s, int action, void * /*userp*/, void *socketp) {
curl_context *context = nullptr;
if (socketp) {
context = (curl_context *)socketp;
} else if (action != CURL_POLL_REMOVE) {
context = create_curl_context(s);
}
if (context) {
curl_multi_assign(curl_multi, s, (void *)context);
if (action == CURL_POLL_IN || action == CURL_POLL_INOUT) {
uv_poll_start(&context->poll_handle, UV_READABLE, curl_perform);
}
if (action == CURL_POLL_OUT || action == CURL_POLL_INOUT) {
uv_poll_start(&context->poll_handle, UV_WRITABLE, curl_perform);
}
if (action == CURL_POLL_REMOVE && socketp) {
uv_poll_stop(&context->poll_handle);
destroy_curl_context(context);
curl_multi_assign(curl_multi, s, NULL);
}
}
return 0;
}
void on_timeout(uv_timer_t *, int status /*req*/) {
int running_handles;
CURLMcode error =
curl_multi_socket_action(curl_multi, CURL_SOCKET_TIMEOUT, 0, &running_handles);
if (error != CURLM_OK) {
throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error));
}
}
void start_timeout(CURLM * /*multi*/, long timeout_ms, void * /*userp*/) {
if (timeout_ms <= 0) {
on_timeout(&timeout, -1);
} else {
uv_timer_start(&timeout, &on_timeout, timeout_ms, 0);
}
}
// This function is the first function called in the request thread. It sets up the CURL share/multi
// handles and runs the thread loop.
void thread_init(void * /*ptr*/) {
uv_timer_init(loop, &timeout);
CURLSHcode share_error;
curl_share = curl_share_init();
share_error = curl_share_setopt(curl_share, CURLSHOPT_LOCKFUNC, curl_share_lock);
if (share_error != CURLSHE_OK) {
throw std::runtime_error(std::string("CURL share error: ") + curl_share_strerror(share_error));
}
share_error = curl_share_setopt(curl_share, CURLSHOPT_UNLOCKFUNC, curl_share_unlock);
if (share_error != CURLSHE_OK) {
throw std::runtime_error(std::string("CURL share error: ") + curl_share_strerror(share_error));
}
CURLMcode multi_error;
curl_multi = curl_multi_init();
multi_error = curl_multi_setopt(curl_multi, CURLMOPT_SOCKETFUNCTION, handle_socket);
if (multi_error != CURLM_OK) {
throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(multi_error));
}
multi_error = curl_multi_setopt(curl_multi, CURLMOPT_TIMERFUNCTION, start_timeout);
if (multi_error != CURLM_OK) {
throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(multi_error));
}
// Main event loop. This will not return until the request loop is terminated.
uv_run(loop, UV_RUN_DEFAULT);
curl_multi_cleanup(curl_multi);
curl_multi = nullptr;
curl_share_cleanup(curl_share);
curl_share = nullptr;
// Clean up all the CURL easy handles that we kept around for potential future reuse.
while (!curl_handle_cache.empty()) {
curl_easy_cleanup(curl_handle_cache.front());
curl_handle_cache.pop();
}
}
// This function is called when we have new data for a request. We just append it to the string
// containing the previous data.
size_t curl_write_cb(void *contents, size_t size, size_t nmemb, void *userp) {
((std::string *)userp)->append((char *)contents, size * nmemb);
return size * nmemb;
}
// This callback is called in the request event loop (on the request thread).
// It initializes newly queued up download requests and adds them to the CURL
// multi handle.
void async_add_cb(uv_async_t *, int status /*async*/) {
std::shared_ptr<Request> *req = nullptr;
while (add_queue.pop(req)) {
// Make sure that we're not starting requests that have been cancelled
// already by async_cancel_cb.
if ((*req)->cancelled) {
delete req;
continue;
}
// Obtain a curl handle (and try to reuse existing handles before creating new ones).
CURL *handle = nullptr;
if (!curl_handle_cache.empty()) {
handle = curl_handle_cache.front();
curl_handle_cache.pop();
} else {
handle = curl_easy_init();
}
((CURLRequest *)req->get())->curl = handle;
curl_easy_setopt(handle, CURLOPT_PRIVATE, req);
curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt");
curl_easy_setopt(handle, CURLOPT_URL, (*req)->url.c_str());
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curl_write_cb);
curl_easy_setopt(handle, CURLOPT_WRITEDATA, &(*req)->res->body);
curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate");
curl_easy_setopt(handle, CURLOPT_SHARE, curl_share);
curl_multi_add_handle(curl_multi, handle);
}
}
void async_cancel_cb(uv_async_t *, int status /*async*/) {
std::shared_ptr<Request> *req = nullptr;
while (cancel_queue.pop(req)) {
// It is possible that the request has not yet been started, but that it already has been
// added to the queue for scheduling new requests. In this case, the CURL handle is invalid
// and we manually mark the Request as cancelled.
CURL *handle = ((CURLRequest *)req->get())->curl;
if (handle && !(*req)->cancelled) {
remove_curl_handle(handle);
((CURLRequest *)req->get())->curl = nullptr;
}
(*req)->cancelled = true;
delete req;
req = nullptr;
}
}
void thread_init_cb() {
curl_global_init(CURL_GLOBAL_ALL);
loop = uv_loop_new();
uv_async_init(loop, &async_add, &async_add_cb);
uv_async_init(loop, &async_cancel, &async_cancel_cb);
uv_thread_create(&thread, thread_init, nullptr);
}
} // end namespace request
} // end namespace platform
std::shared_ptr<platform::Request>
platform::request_http(const std::string &url,
std::function<void(Response *)> callback,
std::shared_ptr<uv::loop> loop) {
using namespace request;
init_thread_once(thread_init_cb);
std::shared_ptr<CURLRequest> req = std::make_shared<CURLRequest>(url, callback, loop);
// Note that we are creating a new shared_ptr pointer(!) because the lockless queue can't store
// objects with nontrivial destructors. We have to make absolutely sure that we manually delete
// the shared_ptr when we pop it from the queue.
add_queue.push(new std::shared_ptr<Request>(req));
uv_async_send(&async_add);
return req;
}
// Cancels an HTTP request.
void platform::cancel_request_http(const std::shared_ptr<Request> &req) {
if (req) {
using namespace request;
// Note that we are creating a new shared_ptr pointer(!) because the lockless queue can't
// store objects with nontrivial destructors. We have to make absolutely shure that we
// manually delete the shared_ptr when we pop it from the queue.
cancel_queue.push(new std::shared_ptr<Request>(req));
uv_async_send(&async_cancel);
}
}
} // end namespace mbgl

View File

@@ -8,7 +8,10 @@ GLFWView::GLFWView(bool fullscreen) : fullscreen(fullscreen) {
#endif
}
GLFWView::~GLFWView() { glfwTerminate(); }
GLFWView::~GLFWView() {
map->terminate();
glfwTerminate();
}
void GLFWView::initialize(mbgl::Map *map) {
View::initialize(map);
@@ -194,6 +197,10 @@ void GLFWView::make_active() {
glfwMakeContextCurrent(window);
}
void GLFWView::make_inactive() {
glfwMakeContextCurrent(nullptr);
}
void GLFWView::notify() {
glfwPostEmptyEvent();
}

View File

@@ -17,6 +17,7 @@ public:
void initialize(mbgl::Map *map);
void swap();
void make_active();
void make_inactive();
void notify();
void notify_map_change(mbgl::MapChange change, mbgl::timestamp delay = 0);

2
common/glx.h Normal file
View File

@@ -0,0 +1,2 @@
#include <GL/glx.h>
#undef None

View File

@@ -0,0 +1,74 @@
#include "headless_display.hpp"
#include <cstring>
#include <stdexcept>
namespace mbgl {
HeadlessDisplay::HeadlessDisplay() {
#if MBGL_USE_CGL
// TODO: test if OpenGL 4.1 with GL_ARB_ES2_compatibility is supported
// If it is, use kCGLOGLPVersion_3_2_Core and enable that extension.
CGLPixelFormatAttribute attributes[] = {
kCGLPFAOpenGLProfile,
(CGLPixelFormatAttribute) kCGLOGLPVersion_Legacy,
kCGLPFAAccelerated,
(CGLPixelFormatAttribute) 0
};
GLint num;
CGLError error = CGLChoosePixelFormat(attributes, &pixelFormat, &num);
if (error) {
fprintf(stderr, "Error pixel format: %s\n", CGLErrorString(error));
return;
}
#endif
#if MBGL_USE_GLX
if (!XInitThreads()) {
throw std::runtime_error("Failed to XInitThreads");
}
x_display = XOpenDisplay(nullptr);
if (x_display == nullptr) {
throw std::runtime_error("Failed to open X display");
}
const char *extensions = (char *)glXQueryServerString(x_display, DefaultScreen(x_display), GLX_EXTENSIONS);
if (!extensions) {
throw std::runtime_error("Cannot read GLX extensions");
}
if (!strstr(extensions,"GLX_SGIX_fbconfig")) {
throw std::runtime_error("Extension GLX_SGIX_fbconfig was not found");
}
if (!strstr(extensions, "GLX_SGIX_pbuffer")) {
throw std::runtime_error("Cannot find glXCreateContextAttribsARB");
}
// We're creating a dummy pbuffer anyway that we're not using.
static int pixelFormat[] = {
GLX_DRAWABLE_TYPE, GLX_PBUFFER_BIT,
None
};
int configs = 0;
fb_configs = glXChooseFBConfig(x_display, DefaultScreen(x_display), pixelFormat, &configs);
if (configs <= 0) {
throw std::runtime_error("No Framebuffer configurations");
}
#endif
}
HeadlessDisplay::~HeadlessDisplay() {
#if MBGL_USE_CGL
CGLDestroyPixelFormat(pixelFormat);
#endif
#if MBGL_USE_GLX
XFree(fb_configs);
XCloseDisplay(x_display);
#endif
}
}

View File

@@ -0,0 +1,25 @@
#ifndef MBGL_COMMON_HEADLESS_DISPLAY
#define MBGL_COMMON_HEADLESS_DISPLAY
#include "headless_view.hpp"
namespace mbgl {
class HeadlessDisplay {
public:
HeadlessDisplay();
~HeadlessDisplay();
#if MBGL_USE_CGL
CGLPixelFormatObj pixelFormat;
#endif
#if MBGL_USE_GLX
Display *x_display = nullptr;
GLXFBConfig *fb_configs = nullptr;
#endif
};
}
#endif

View File

@@ -1,88 +1,158 @@
#include "headless_view.hpp"
#include <mbgl/util/timer.hpp>
#include "headless_display.hpp"
#include <stdexcept>
#include <sstream>
#include <string>
#if MBGL_USE_GLX
#ifdef GLX_ARB_create_context
static PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = nullptr;
#endif
#endif
namespace mbgl {
HeadlessView::HeadlessView() {
#if MBGL_USE_CGL
// TODO: test if OpenGL 4.1 with GL_ARB_ES2_compatibility is supported
// If it is, use kCGLOGLPVersion_3_2_Core and enable that extension.
CGLPixelFormatAttribute attributes[] = {
kCGLPFAOpenGLProfile,
(CGLPixelFormatAttribute) kCGLOGLPVersion_Legacy,
kCGLPFAAccelerated,
(CGLPixelFormatAttribute) 0
};
HeadlessView::HeadlessView()
: display_(std::make_shared<HeadlessDisplay>()) {
createContext();
}
CGLPixelFormatObj pixelFormat;
GLint num;
CGLError error = CGLChoosePixelFormat(attributes, &pixelFormat, &num);
if (error) {
fprintf(stderr, "Error pixel format\n");
return;
HeadlessView::HeadlessView(std::shared_ptr<HeadlessDisplay> display)
: display_(display) {
createContext();
}
#if MBGL_USE_GLX
#ifdef GLX_ARB_create_context
// These are all of the OpenGL Core profile version that we know about.
struct core_profile_version { int major, minor; };
static const core_profile_version core_profile_versions[] = {
{4, 5},
{4, 4},
{4, 3},
{4, 2},
{4, 1},
{4, 0},
{3, 3},
{3, 2},
{3, 1},
{3, 0},
{0, 0},
};
GLXContext createCoreProfile(Display *dpy, GLXFBConfig fbconfig) {
static bool context_creation_failed = false;
GLXContext ctx = 0;
// Set the Error Handler to avoid crashing the program when the context creation fails.
// It is expected that some context creation attempts fail, e.g. because the OpenGL
// implementation does not support the version we're requesting.
int (*previous_error_handler)(Display *, XErrorEvent *) = XSetErrorHandler([](Display *, XErrorEvent *) {
context_creation_failed = true;
return 0;
});
// Try to create core profiles from the highest known version on down.
for (int i = 0; !ctx && core_profile_versions[i].major; i++) {
context_creation_failed = false;
const int context_flags[] = {
GLX_CONTEXT_MAJOR_VERSION_ARB, core_profile_versions[i].major,
GLX_CONTEXT_MINOR_VERSION_ARB, core_profile_versions[i].minor,
None
};
ctx = glXCreateContextAttribsARB(dpy, fbconfig, 0, True, context_flags);
if (context_creation_failed) {
ctx = 0;
}
}
error = CGLCreateContext(pixelFormat, NULL, &gl_context);
CGLDestroyPixelFormat(pixelFormat);
// Restore the old error handler.
XSetErrorHandler(previous_error_handler);
return ctx;
}
#endif
#endif
void HeadlessView::createContext() {
#if MBGL_USE_CGL
CGLError error = CGLCreateContext(display_->pixelFormat, NULL, &gl_context);
if (error) {
fprintf(stderr, "Error creating GL context object\n");
return;
throw std::runtime_error("Error creating GL context object\n");
}
error = CGLEnable(gl_context, kCGLCEMPEngine);
if (error != kCGLNoError ) {
throw std::runtime_error("Error enabling OpenGL multithreading\n");
}
#endif
#if MBGL_USE_GLX
x_display = XOpenDisplay(0);
if (x_display == nullptr) {
throw std::runtime_error("Failed to open X display");
}
static int pixelFormat[] = {
GLX_RGBA,
GLX_DOUBLEBUFFER,
GLX_RED_SIZE, 8,
GLX_GREEN_SIZE, 8,
GLX_BLUE_SIZE, 8,
GLX_ALPHA_SIZE, 8,
GLX_DEPTH_SIZE, 24,
GLX_STENCIL_SIZE, 8,
None
};
x_info = glXChooseVisual(x_display, DefaultScreen(x_display), pixelFormat);
if (x_info == nullptr) {
throw std::runtime_error("Error pixel format");
}
gl_context = glXCreateContext(x_display, x_info, 0, GL_TRUE);
if (gl_context == nullptr) {
throw std::runtime_error("Error creating GL context object");
#ifdef GLX_ARB_create_context
if (glXCreateContextAttribsARB == nullptr) {
glXCreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddressARB((const GLubyte *)"glXCreateContextAttribsARB");
}
#endif
}
x_display = display_->x_display;
fb_configs = display_->fb_configs;
#ifdef GLX_ARB_create_context
if (glXCreateContextAttribsARB) {
// Try to create a core profile context.
gl_context = createCoreProfile(x_display, fb_configs[0]);
}
#endif
if (!gl_context) {
// Try to create a legacy context
gl_context = glXCreateNewContext(x_display, fb_configs[0], GLX_RGBA_TYPE, 0, True);
if (gl_context) {
if (!glXIsDirect(x_display, gl_context)) {
glXDestroyContext(x_display, gl_context);
gl_context = 0;
}
}
}
if (gl_context == 0) {
throw std::runtime_error("Error creating GL context object");
}
// Create a dummy pbuffer. We will render to framebuffers anyway, but we need a pbuffer to
// activate the context.
int pbuffer_attributes[] = {
GLX_PBUFFER_WIDTH, 8,
GLX_PBUFFER_HEIGHT, 8,
None
};
glx_pbuffer = glXCreatePbuffer(x_display, fb_configs[0], pbuffer_attributes);
#endif
}
void HeadlessView::resize(uint16_t width, uint16_t height, float pixelRatio) {
clear_buffers();
width *= pixelRatio;
height *= pixelRatio;
width_ = width;
height_ = height;
pixelRatio_ = pixelRatio;
const unsigned int w = width_ * pixelRatio_;
const unsigned int h = height_ * pixelRatio_;
#if MBGL_USE_CGL
make_active();
// Create depth/stencil buffer
glGenRenderbuffersEXT(1, &fbo_depth_stencil);
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo_depth_stencil);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH24_STENCIL8_EXT, width, height);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH24_STENCIL8_EXT, w, h);
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
glGenRenderbuffersEXT(1, &fbo_color);
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo_color);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, width, height);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, w, h);
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
glGenFramebuffersEXT(1, &fbo);
@@ -94,29 +164,39 @@ void HeadlessView::resize(uint16_t width, uint16_t height, float pixelRatio) {
GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT) {
fprintf(stderr, "Couldn't create framebuffer: ");
std::stringstream error("Couldn't create framebuffer: ");
switch (status) {
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: fprintf(stderr, "incomplete attachment\n"); break;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: fprintf(stderr, "incomplete missing attachment\n"); break;
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: fprintf(stderr, "incomplete draw buffer\n"); break;
case GL_FRAMEBUFFER_UNSUPPORTED: fprintf(stderr, "unsupported\n"); break;
default: fprintf(stderr, "other\n"); break;
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: (error << "incomplete attachment\n"); break;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: error << "incomplete missing attachment\n"; break;
case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: error << "incomplete dimensions\n"; break;
case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: error << "incomplete formats\n"; break;
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: error << "incomplete draw buffer\n"; break;
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: error << "incomplete read buffer\n"; break;
case GL_FRAMEBUFFER_UNSUPPORTED: error << "unsupported\n"; break;
default: error << "other\n"; break;
}
return;
throw std::runtime_error(error.str());
}
#endif
#if MBGL_USE_GLX
x_pixmap = XCreatePixmap(x_display, DefaultRootWindow(x_display), width, height, 32);
glx_pixmap = glXCreateGLXPixmap(x_display, x_info, x_pixmap);
make_inactive();
}
const std::unique_ptr<uint32_t[]> HeadlessView::readPixels() {
const unsigned int w = width_ * pixelRatio_;
const unsigned int h = height_ * pixelRatio_;
std::unique_ptr<uint32_t[]> pixels(new uint32_t[w * h]);
make_active();
#endif
glReadPixels(0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, pixels.get());
make_inactive();
return pixels;
}
void HeadlessView::clear_buffers() {
#if MBGL_USE_CGL
make_active();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
if (fbo) {
@@ -133,19 +213,8 @@ void HeadlessView::clear_buffers() {
glDeleteRenderbuffersEXT(1, &fbo_depth_stencil);
fbo_depth_stencil = 0;
}
#endif
#if MBGL_USE_GLX
if (glx_pixmap) {
glXDestroyGLXPixmap(x_display, glx_pixmap);
glx_pixmap = 0;
}
if (x_pixmap) {
XFreePixmap(x_display, x_pixmap);
x_pixmap = 0;
}
#endif
make_inactive();
}
HeadlessView::~HeadlessView() {
@@ -156,10 +225,12 @@ HeadlessView::~HeadlessView() {
#endif
#if MBGL_USE_GLX
glXMakeCurrent(x_display, None, NULL);
if (glx_pbuffer) {
glXDestroyPbuffer(x_display, glx_pbuffer);
glx_pbuffer = 0;
}
glXDestroyContext(x_display, gl_context);
XFree(x_info);
XCloseDisplay(x_display);
#endif
}
@@ -175,26 +246,33 @@ void HeadlessView::make_active() {
#if MBGL_USE_CGL
CGLError error = CGLSetCurrentContext(gl_context);
if (error) {
fprintf(stderr, "Switching OpenGL context failed\n");
throw std::runtime_error("Switching OpenGL context failed\n");
}
#endif
#if MBGL_USE_GLX
if (!glXMakeCurrent(x_display, glx_pixmap, gl_context)) {
fprintf(stderr, "Switching OpenGL context failed\n");
if (!glXMakeContextCurrent(x_display, glx_pbuffer, glx_pbuffer, gl_context)) {
throw std::runtime_error("Switching OpenGL context failed\n");
}
#endif
}
void HeadlessView::make_inactive() {
#if MBGL_USE_CGL
CGLError error = CGLSetCurrentContext(nullptr);
if (error) {
throw std::runtime_error("Removing OpenGL context failed\n");
}
#endif
#if MBGL_USE_GLX
if (!glXMakeContextCurrent(x_display, 0, 0, nullptr)) {
throw std::runtime_error("Removing OpenGL context failed\n");
}
#endif
}
void HeadlessView::swap() {}
unsigned int HeadlessView::root_fbo() {
#if MBGL_USE_CGL
return fbo;
#endif
return 0;
}
}

View File

@@ -1,51 +1,63 @@
#ifndef MBGL_COMMON_HEADLESS_CGL
#define MBGL_COMMON_HEADLESS_CGL
#ifndef MBGL_COMMON_HEADLESS_VIEW
#define MBGL_COMMON_HEADLESS_VIEW
#ifdef __APPLE__
#define MBGL_USE_CGL 1
#else
#define GL_GLEXT_PROTOTYPES
#include <GL/glx.h>
#define MBGL_USE_GLX 1
#endif
#include <mbgl/map/view.hpp>
#include <mbgl/platform/gl.hpp>
#include <mbgl/util/time.hpp>
#include <memory>
namespace mbgl {
class HeadlessDisplay;
class HeadlessView : public View {
public:
HeadlessView();
HeadlessView(std::shared_ptr<HeadlessDisplay> display);
~HeadlessView();
void createContext();
void resize(uint16_t width, uint16_t height, float pixelRatio);
const std::unique_ptr<uint32_t[]> readPixels();
void notify();
void notify_map_change(MapChange change, timestamp delay = 0);
void make_active();
void make_inactive();
void swap();
unsigned int root_fbo();
private:
void clear_buffers();
private:
std::shared_ptr<HeadlessDisplay> display_;
uint16_t width_;
uint16_t height_;
float pixelRatio_;
#if MBGL_USE_CGL
CGLContextObj gl_context;
GLuint fbo = 0;
GLuint fbo_depth_stencil = 0;
GLuint fbo_color = 0;
#endif
#if MBGL_USE_GLX
GLXContext gl_context = nullptr;
XVisualInfo *x_info = nullptr;
Display *x_display = nullptr;
Pixmap x_pixmap = 0;
GLXPixmap glx_pixmap = 0;
GLXFBConfig *fb_configs = nullptr;
GLXContext gl_context = 0;
GLXPbuffer glx_pbuffer = 0;
#endif
GLuint fbo = 0;
GLuint fbo_depth_stencil = 0;
GLuint fbo_color = 0;
};
}

View File

@@ -267,7 +267,7 @@ void on_timeout(uv_timer_t *) {
void start_timeout(CURLM *, long timeout_ms, void *) {
if (timeout_ms <= 0) {
on_timeout(&timeout);
on_timeout(&timeout, 0);
} else {
uv_timer_start(&timeout, on_timeout, timeout_ms, 0);
}

View File

@@ -63,6 +63,9 @@ public:
// Triggers a cleanup that releases resources.
void cleanup();
// Releases resources immediately
void terminate();
// Controls buffer swapping.
bool needsSwap();
void swapped();

View File

@@ -35,11 +35,9 @@ public:
// renderer setup since the render thread doesn't switch the contexts.
virtual void make_active() = 0;
// Returns the base framebuffer object, if any, and 0 if using the system
// provided framebuffer.
virtual unsigned int root_fbo() {
return 0;
}
// Called from the render thread. Makes the GL context inactive in the current
// thread. This is called once just before the rendering thread terminates.
virtual void make_inactive() = 0;
virtual void notify() = 0;

View File

@@ -64,6 +64,7 @@ public:
// lazy initialization) in case rendering continues.
void cleanup();
void terminate();
// Renders the backdrop of the OpenGL view. This also paints in areas where we don't have any
// tiles whatsoever.
@@ -124,6 +125,7 @@ public:
private:
void setupShaders();
void deleteShaders();
mat4 translatedMatrix(const mat4& matrix, const std::array<float, 2> &translation, const Tile::ID &id, TranslateAnchorType anchor);
void prepareTile(const Tile& tile);

View File

@@ -14,17 +14,22 @@ enum class ClassID : uint32_t {
};
class ClassDictionary {
private:
ClassDictionary();
public:
static ClassDictionary &Get();
// Returns an ID for a class name. If the class name does not yet have an ID, one is
// auto-generated and stored for future reference.
static ClassID Lookup(const std::string &class_name);
ClassID lookup(const std::string &class_name);
// Returns either Fallback, Default or Named, depending on the type of the class id.
static ClassID Normalize(ClassID id);
ClassID normalize(ClassID id);
private:
static std::unordered_map<std::string, ClassID> store;
static uint32_t offset;
std::unordered_map<std::string, ClassID> store = { { "", ClassID::Default } };
uint32_t offset = 0;
};
}

View File

@@ -2,6 +2,7 @@
if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then
export DISPLAY=:99.0
sh -e /etc/init.d/xvfb start
# use g++ that supports c++11

View File

@@ -11,21 +11,34 @@ if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then
sudo add-apt-repository --yes ppa:ubuntu-toolchain-r/test
fi
sudo add-apt-repository --yes ppa:boost-latest/ppa
mapbox_time "apt_update" \
sudo apt-get update -y
mapbox_time "install_gcc" \
sudo apt-get -y install gcc-4.8 g++-4.8
mapbox_time "install_build_tools" \
sudo apt-get -y install git build-essential zlib1g-dev automake \
libtool xutils-dev make cmake pkg-config python-pip \
libboost1.55-dev libboost-regex1.55-dev libcurl4-openssl-dev \
libpng-dev libsqlite3-dev
apbox_time "install_opengl" \
sudo apt-get -y install libxi-dev libglu1-mesa-dev x11proto-randr-dev \
x11proto-xext-dev libxrandr-dev \
x11proto-xf86vidmode-dev libxxf86vm-dev \
libxcursor-dev libxinerama-dev
mapbox_time "install_awscli" \
sudo pip install awscli
elif [[ ${TRAVIS_OS_NAME} == "osx" ]]; then
#
# install OS X dependencies
#
mapbox_time "install_build_tools" \
brew install autoconf automake libtool makedepend cmake pkg-config node git boost
mapbox_time "install_awscli" \
sudo pip install awscli
fi

41
scripts/travis_helper.sh Executable file
View File

@@ -0,0 +1,41 @@
#!/usr/bin/env bash
function mapbox_time_start {
local name=$1
mapbox_timer_name=$name
travis_fold start $name
mapbox_timer_id=$(printf %08x $(( RANDOM * RANDOM )))
eval "mapbox_start_time_$mapbox_timer_id=$(travis_nanoseconds)"
echo -en "travis_time:start:$mapbox_timer_id\n"
}
function mapbox_time_finish {
local name=${1:-$mapbox_timer_name}
local timer_id=${2:-$mapbox_timer_id}
local timer_start="mapbox_start_time_$timer_id"
eval local start_time=\${$timer_start}
local end_time=$(travis_nanoseconds)
local duration=$(($end_time-$start_time))
echo -en "travis_time:end:$timer_id:start=$start_time,finish=$end_time,duration=$duration\n"
travis_fold end $name
}
function mapbox_time {
local name=$1 ; shift
mapbox_time_start $name
local timer_id=$mapbox_timer_id
echo "\$ $@"
$@
mapbox_time_finish $name $timer_id
}
export ANSI_CLEAR
export -f travis_fold
export -f travis_nanoseconds
export -f mapbox_time
export -f mapbox_time_start
export -f mapbox_time_finish

View File

@@ -4,5 +4,6 @@ if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then
#
# we'll need the test suite on Linux
#
mapbox_time "install_test_suite" \
npm install git+https://github.com/mapbox/mapbox-gl-test-suite.git
fi

View File

@@ -7,26 +7,46 @@ if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then
#
# build & test Linux
#
mapbox_time "compile_program" \
make linux -j4 BUILDTYPE=${BUILDTYPE}
mapbox_time "compile_tests" \
make test -j4 BUILDTYPE=${BUILDTYPE}
mapbox_time "run_tests" \
./scripts/run_tests.sh
mapbox_time_start "compare_results"
(cd ./node_modules/mapbox-gl-test-suite/ && (./bin/compare_images.js || true))
mapbox_time_finish
if [ ! -z "${AWS_ACCESS_KEY_ID}" ] && [ ! -z "${AWS_SECRET_ACCESS_KEY}" ] ; then
mapbox_time_start "deploy_results"
(cd ./node_modules/mapbox-gl-test-suite/ && ./bin/deploy_results.sh)
mapbox_time_finish
fi
elif [[ ${TRAVIS_OS_NAME} == "osx" ]]; then
#
# build OS X
#
mapbox_time "create_osx_project" \
make xproj-cli
mapbox_time "build_osx" \
xcodebuild -project ./build/macosx/mapboxgl-app.xcodeproj -jobs 4
#
# build iOS
#
git submodule init
mapbox_time "load_submodules" \
git submodule update
mapbox_time "create_ios_project" \
make iproj-cli
mapbox_time "build_ios" \
xcodebuild -project ./build/ios/mapbox-gl-cocoa/app/mapboxgl-app.xcodeproj -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO -jobs 4
fi

View File

@@ -163,7 +163,6 @@ void Map::run() {
// If the map rendering wasn't started asynchronously, we perform one render
// *after* all events have been processed.
if (!async) {
prepare();
render();
#ifndef NDEBUG
map_thread = -1;
@@ -206,10 +205,13 @@ void Map::cleanup(uv_async_t *async) {
#endif
Map *map = static_cast<Map *>(async->data);
map->view.make_active();
map->painter.cleanup();
}
void Map::terminate() {
painter.terminate();
}
void Map::setReachability(bool reachable) {
// Note: This function may be called from *any* thread.
if (reachable) {
@@ -271,8 +273,8 @@ void Map::terminate(uv_async_t *async) {
void Map::setup() {
assert(uv_thread_self() == map_thread);
view.make_active();
painter.setup();
view.make_inactive();
}
void Map::setStyleURL(const std::string &url) {
@@ -605,8 +607,6 @@ void Map::updateRenderState() {
}
void Map::prepare() {
view.make_active();
if (!fileSource) {
fileSource = std::make_shared<FileSource>(**loop, platform::defaultCacheDatabase());
glyphStore = std::make_shared<GlyphStore>(fileSource);
@@ -651,6 +651,8 @@ void Map::prepare() {
}
void Map::render() {
view.make_active();
#if defined(DEBUG)
std::vector<std::string> debug;
#endif
@@ -685,6 +687,8 @@ void Map::render() {
}
glFlush();
view.make_inactive();
}
void Map::renderLayers(util::ptr<StyleLayerGroup> group) {

View File

@@ -82,9 +82,29 @@ void Painter::setupShaders() {
if (!gaussianShader) gaussianShader = std::make_unique<GaussianShader>();
}
void Painter::deleteShaders() {
plainShader = nullptr;
outlineShader = nullptr;
lineShader = nullptr;
linejoinShader = nullptr;
linepatternShader = nullptr;
patternShader = nullptr;
iconShader = nullptr;
rasterShader = nullptr;
sdfGlyphShader = nullptr;
sdfIconShader = nullptr;
dotShader = nullptr;
gaussianShader = nullptr;
}
void Painter::cleanup() {
}
void Painter::terminate() {
cleanup();
deleteShaders();
}
void Painter::resize() {
const TransformState &state = map.getState();
if (gl_viewport != state.getFramebufferDimensions()) {

View File

@@ -83,6 +83,7 @@ void FileRequestBaton::file_stated(uv_fs_t *req) {
if (stat->st_size > std::numeric_limits<int>::max()) {
// File is too large for us to open this way because uv_buf's only support unsigned
// ints as maximum size.
const uv_err_t error = {UV_EFBIG, 0};
if (ptr->request) {
ptr->request->response = std::unique_ptr<Response>(new Response);
ptr->request->response->code = UV_EFBIG;
@@ -97,8 +98,7 @@ void FileRequestBaton::file_stated(uv_fs_t *req) {
uv_fs_req_cleanup(req);
uv_fs_close(req->loop, req, ptr->fd, file_closed);
} else {
const unsigned int size =
(unsigned int)(stat->st_size);
const unsigned int size = (unsigned int)(stat->st_size);
ptr->body.resize(size);
ptr->buffer = uv_buf_init(const_cast<char *>(ptr->body.data()), size);
uv_fs_req_cleanup(req);

View File

@@ -1,8 +1,34 @@
#include <mbgl/style/class_dictionary.hpp>
#include <uv.h>
namespace mbgl {
ClassID ClassDictionary::Lookup(const std::string &class_name) {
ClassDictionary::ClassDictionary() {}
ClassDictionary &ClassDictionary::Get() {
// Note: We should eventually switch to uv_key_* functions, but libuv 0.10 doesn't have these
// yet. Instead, we're using the pthread functions directly for now.
static pthread_once_t store_once = PTHREAD_ONCE_INIT;
static pthread_key_t store_key;
// Create the key.
pthread_once(&store_once, []() {
pthread_key_create(&store_key, [](void *ptr) {
delete reinterpret_cast<ClassDictionary *>(ptr);
});
});
ClassDictionary *ptr = reinterpret_cast<ClassDictionary *>(pthread_getspecific(store_key));
if (ptr == nullptr) {
ptr = new ClassDictionary();
pthread_setspecific(store_key, ptr);
}
return *ptr;
}
ClassID ClassDictionary::lookup(const std::string &class_name) {
auto it = store.find(class_name);
if (it == store.end()) {
// Insert the class name into the store.
@@ -14,7 +40,7 @@ ClassID ClassDictionary::Lookup(const std::string &class_name) {
}
}
ClassID ClassDictionary::Normalize(ClassID id) {
ClassID ClassDictionary::normalize(ClassID id) {
if (id >= ClassID::Named) {
return ClassID::Named;
} else {
@@ -22,8 +48,4 @@ ClassID ClassDictionary::Normalize(ClassID id) {
}
}
std::unordered_map<std::string, ClassID> ClassDictionary::store = { { "", ClassID::Default } };
uint32_t ClassDictionary::offset = 0;
}

View File

@@ -23,7 +23,7 @@ void StyleLayer::setClasses(const std::vector<std::string> &class_names, const t
for (auto it = class_names.rbegin(); it != class_names.rend(); it++) {
const std::string &class_name = *it;
// From here on, we're only dealing with IDs to avoid comparing strings all the time.
const ClassID class_id = ClassDictionary::Lookup(class_name);
const ClassID class_id = ClassDictionary::Get().lookup(class_name);
applyClassProperties(class_id, already_applied, now, defaultTransition);
}

View File

@@ -554,7 +554,7 @@ void StyleParser::parseStyles(JSVal value, std::map<ClassID, ClassProperties> &s
if (name == "style") {
parseStyle(replaceConstant(itr->value), styles[ClassID::Default]);
} else if (name.compare(0, 6, "style.") == 0 && name.length() > 6) {
const ClassID class_id = ClassDictionary::Lookup(name.substr(6));
const ClassID class_id = ClassDictionary::Get().lookup(name.substr(6));
parseStyle(replaceConstant(itr->value), styles[class_id]);
}
}

View File

@@ -8,7 +8,6 @@ typedef struct {
void *queue[2];
} uv__messenger_item_t;
#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"

View File

@@ -15,7 +15,7 @@ std::string cwd() {
do {
max += 256;
dir.resize(max);
uv_cwd(const_cast<char *>(dir.data()), &max);
uv_cwd(const_cast<char *>(dir.data()), max);
} while (max == dir.size());
dir.resize(max - 1);
return dir;

View File

@@ -4,6 +4,7 @@
#include <mbgl/util/url.hpp>
#include <mbgl/util/std.hpp>
#include <mbgl/platform/log.hpp>
#include <iostream>
#include <uv.h>

View File

@@ -10,6 +10,7 @@
#include <rapidjson/stringbuffer.h>
#include "../common/headless_view.hpp"
#include "../common/headless_display.hpp"
#include "./fixtures/fixture_log.hpp"
@@ -22,6 +23,8 @@ const std::string base_directory = []{
return fn + "/node_modules/mapbox-gl-test-suite/";
}();
auto display_ = std::make_shared<mbgl::HeadlessDisplay>();
class HeadlessTest : public ::testing::TestWithParam<std::string> {};
TEST_P(HeadlessTest, render) {
@@ -78,7 +81,7 @@ TEST_P(HeadlessTest, render) {
}
}
HeadlessView view;
HeadlessView view(display_);
Map map(view);
map.setStyleJSON(style, base_directory);
@@ -95,10 +98,7 @@ TEST_P(HeadlessTest, render) {
const unsigned int w = width * pixelRatio;
const unsigned int h = height * pixelRatio;
const std::unique_ptr<uint32_t[]> pixels(new uint32_t[w * h]);
glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels.get());
const std::string image = util::compress_png(w, h, pixels.get(), true);
const std::string image = util::compress_png(w, h, view.readPixels().get(), true);
util::write_file(actual_image, image);
}
}

View File

@@ -169,6 +169,8 @@
"./headless.cpp",
"../common/headless_view.hpp",
"../common/headless_view.cpp",
"../common/headless_display.hpp",
"../common/headless_display.cpp",
"../common/platform_default.cpp",
"./fixtures/fixture_request.cpp",
"./fixtures/fixture_log.hpp",

View File

@@ -1,7 +1,7 @@
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "mapbox-gl-native travis resources",
"Description": "node-mapbox-gl-native travis resources",
"Resources": {
"BuildUser": {
"Type": "AWS::IAM::User",
@@ -17,7 +17,7 @@
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::mapbox-gl-testing"
"arn:aws:s3:::node-mapbox-gl-native"
]
}
]
@@ -25,6 +25,42 @@
},
{
"PolicyName": "build",
"PolicyDocument": {
"Statement": [
{
"Action": [
"s3:DeleteObject",
"s3:GetObject",
"s3:GetObjectAcl",
"s3:PutObject",
"s3:PutObjectAcl"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::node-mapbox-gl-native/*"
]
}
]
}
},
{
"PolicyName": "list-testing",
"PolicyDocument": {
"Statement": [
{
"Action": [
"s3:ListBucket"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::mapbox-gl-testing"
]
}
]
}
},
{
"PolicyName": "build-testing",
"PolicyDocument": {
"Statement": [
{