Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upload atom-shell's binaries with Releases API #103

Merged
merged 13 commits into from Sep 27, 2013
Merged
38 changes: 38 additions & 0 deletions common/api/atom_bindings.cc
Expand Up @@ -7,17 +7,35 @@
#include "base/debug/debugger.h"
#include "base/logging.h"
#include "common/atom_version.h"
#include "common/v8_conversions.h"
#include "vendor/node/src/node.h"

namespace atom {

namespace {

static int kMaxCallStackSize = 200; // Same with WebKit.

static uv_async_t dummy_uv_handle;

void UvNoOp(uv_async_t* handle, int status) {
}

v8::Handle<v8::Object> DumpStackFrame(v8::Handle<v8::StackFrame> stack_frame) {
v8::Local<v8::Object> result = v8::Object::New();
result->Set(ToV8Value("line"), ToV8Value(stack_frame->GetLineNumber()));
result->Set(ToV8Value("column"), ToV8Value(stack_frame->GetColumn()));

v8::Handle<v8::String> script = stack_frame->GetScriptName();
if (!script.IsEmpty())
result->Set(ToV8Value("script"), script);

v8::Handle<v8::String> function = stack_frame->GetScriptNameOrSourceURL();
if (!function.IsEmpty())
result->Set(ToV8Value("function"), function);
return result;
}

} // namespace

// Defined in atom_extensions.cc.
Expand All @@ -37,6 +55,7 @@ void AtomBindings::BindTo(v8::Handle<v8::Object> process) {
node::SetMethod(process, "crash", Crash);
node::SetMethod(process, "activateUvLoop", ActivateUVLoop);
node::SetMethod(process, "log", Log);
node::SetMethod(process, "getCurrentStackTrace", GetCurrentStackTrace);

process->Get(v8::String::New("versions"))->ToObject()->
Set(v8::String::New("atom-shell"), v8::String::New(ATOM_VERSION_STRING));
Expand Down Expand Up @@ -111,4 +130,23 @@ v8::Handle<v8::Value> AtomBindings::Log(const v8::Arguments& args) {
return v8::Undefined();
}

// static
v8::Handle<v8::Value> AtomBindings::GetCurrentStackTrace(
const v8::Arguments& args) {
v8::HandleScope scope;

int stack_limit = kMaxCallStackSize;
FromV8Arguments(args, &stack_limit);

v8::Local<v8::StackTrace> stack_trace = v8::StackTrace::CurrentStackTrace(
stack_limit, v8::StackTrace::kDetailed);

int frame_count = stack_trace->GetFrameCount();
v8::Local<v8::Array> result = v8::Array::New(frame_count);
for (int i = 0; i < frame_count; ++i)
result->Set(i, DumpStackFrame(stack_trace->GetFrame(i)));

return scope.Close(result);
}

} // namespace atom
1 change: 1 addition & 0 deletions common/api/atom_bindings.h
Expand Up @@ -24,6 +24,7 @@ class AtomBindings {
static v8::Handle<v8::Value> Crash(const v8::Arguments& args);
static v8::Handle<v8::Value> ActivateUVLoop(const v8::Arguments& args);
static v8::Handle<v8::Value> Log(const v8::Arguments& args);
static v8::Handle<v8::Value> GetCurrentStackTrace(const v8::Arguments& args);

DISALLOW_COPY_AND_ASSIGN(AtomBindings);
};
Expand Down
4 changes: 4 additions & 0 deletions common/v8_conversions.h
Expand Up @@ -77,6 +77,10 @@ inline v8::Handle<v8::Value> ToV8Value(bool b) {
return v8::Boolean::New(b);
}

inline v8::Handle<v8::Value> ToV8Value(const char* s) {
return v8::String::New(s);
}

inline v8::Handle<v8::Value> ToV8Value(const std::string& s) {
return v8::String::New(s.data(), s.size());
}
Expand Down
3 changes: 1 addition & 2 deletions script/cpplint.py
Expand Up @@ -7,12 +7,11 @@

IGNORE_FILES = [
'app/win/resource.h',
'browser/atom_event_processing_window.h',
'browser/atom_application_mac.h',
'browser/atom_application_delegate_mac.h',
'browser/native_window_mac.h',
'browser/ui/atom_event_processing_window.h',
'browser/ui/atom_menu_controller_mac.h',
'browser/ui/cocoa/custom_frame_view.h',
'browser/ui/nsalert_synchronous_sheet_mac.h',
'common/api/api_messages.cc',
'common/api/api_messages.h',
Expand Down
70 changes: 70 additions & 0 deletions script/lib/github.py
@@ -0,0 +1,70 @@
#!/usr/bin/env python

import json
import re
import requests

GITHUB_URL = 'https://api.github.com'
GITHUB_UPLOAD_ASSET_URL = 'https://uploads.github.com'

class GitHub:
def __init__(self, access_token):
self._authorization = 'token %s' % access_token

pattern = '^/repos/{0}/{0}/releases/{1}/assets$'.format('[^/]+', '[0-9]+')
self._releases_upload_api_pattern = re.compile(pattern)

def __getattr__(self, attr):
return _Callable(self, '/%s' % attr)

def _http(self, method, path, **kw):
if not 'headers' in kw:
kw['headers'] = dict()
headers = kw['headers']
headers['Authorization'] = self._authorization
headers['Accept'] = 'application/vnd.github.manifold-preview'

# Data are sent in JSON format.
if 'data' in kw:
kw['data'] = json.dumps(kw['data'])

# Switch to a different domain for the releases uploading API.
if self._releases_upload_api_pattern.match(path):
url = '%s%s' % (GITHUB_UPLOAD_ASSET_URL, path)
else:
url = '%s%s' % (GITHUB_URL, path)

r = getattr(requests, method)(url, **kw).json()
if 'message' in r:
raise Exception(json.dumps(r, indent=2, separators=(',', ': ')))
return r


class _Executable:
def __init__(self, gh, method, path):
self._gh = gh
self._method = method
self._path = path

def __call__(self, **kw):
return self._gh._http(self._method, self._path, **kw)


class _Callable(object):
def __init__(self, gh, name):
self._gh = gh
self._name = name

def __call__(self, *args):
if len(args) == 0:
return self

name = '%s/%s' % (self._name, '/'.join([str(arg) for arg in args]))
return _Callable(self._gh, name)

def __getattr__(self, attr):
if attr in ['get', 'put', 'post', 'patch', 'delete']:
return _Executable(self._gh, attr, self._name)

name = '%s/%s' % (self._name, attr)
return _Callable(self._gh, name)
101 changes: 82 additions & 19 deletions script/upload.py
Expand Up @@ -4,11 +4,13 @@
import errno
import glob
import os
import requests
import subprocess
import sys
import tempfile

from lib.util import *
from lib.github import GitHub


TARGET_PLATFORM = {
Expand All @@ -18,6 +20,7 @@
'win32': 'win32',
}[sys.platform]

ATOM_SHELL_REPO = 'atom/atom-shell'
ATOM_SHELL_VRESION = get_atom_shell_version()
NODE_VERSION = 'v0.10.18'

Expand All @@ -32,18 +35,26 @@ def main():

if not dist_newer_than_head():
create_dist = os.path.join(SOURCE_ROOT, 'script', 'create-dist.py')
subprocess.check_call([sys.executable, create_dist])
subprocess.check_output([sys.executable, create_dist])

# Upload atom-shell with GitHub Releases API.
github = GitHub(auth_token())
release_id = create_or_get_release_draft(github, args.version)
upload_atom_shell(github, release_id, os.path.join(DIST_DIR, DIST_NAME))
if not args.no_publish_release:
publish_release(github, release_id)

# Upload node's headers to S3.
bucket, access_key, secret_key = s3_config()
upload(bucket, access_key, secret_key)
if not args.no_update_version:
update_version(bucket, access_key, secret_key)
upload_node(bucket, access_key, secret_key, NODE_VERSION)


def parse_args():
parser = argparse.ArgumentParser(description='upload distribution file')
parser.add_argument('-n', '--no-update-version',
help='Do not update the latest version file',
parser.add_argument('-v', '--version', help='Specify the version',
default=ATOM_SHELL_VRESION)
parser.add_argument('-n', '--no-publish-release',
help='Do not publish the release',
action='store_true')
return parser.parse_args()

Expand All @@ -62,36 +73,88 @@ def dist_newer_than_head():
return dist_time > int(head_time)


def upload(bucket, access_key, secret_key, version=ATOM_SHELL_VRESION):
def get_text_with_editor():
editor = os.environ.get('EDITOR','nano')
initial_message = '\n# Please enter the body of your release note.'

t = tempfile.NamedTemporaryFile(suffix='.tmp', delete=False)
t.write(initial_message)
t.close()
subprocess.call([editor, t.name])

text = ''
for line in open(t.name, 'r'):
if len(line) == 0 or line[0] != '#':
text += line

os.unlink(t.name)
return text

def create_or_get_release_draft(github, tag):
name = 'atom-shell %s' % tag
releases = github.repos(ATOM_SHELL_REPO).releases.get()
for release in releases:
# The untagged commit doesn't have a matching tag_name, so also check name.
if release['tag_name'] == tag or release['name'] == name:
return release['id']

return create_release_draft(github, tag)


def create_release_draft(github, tag):
name = 'atom-shell %s' % tag
body = get_text_with_editor()
print body

data = dict(tag_name=tag, target_commitish=tag, name=name, body=body,
draft=True)
r = github.repos(ATOM_SHELL_REPO).releases.post(data=data)
return r['id']


def upload_atom_shell(github, release_id, file_path):
params = {'name': os.path.basename(file_path)}
headers = {'Content-Type': 'application/zip'}
files = {'file': open(file_path, 'rb')}
github.repos(ATOM_SHELL_REPO).releases(release_id).assets.post(
params=params, headers=headers, files=files, verify=False)


def publish_release(github, release_id):
data = dict(draft=False)
github.repos(ATOM_SHELL_REPO).releases(release_id).patch(data=data)


def upload_node(bucket, access_key, secret_key, version):
os.chdir(DIST_DIR)

s3put(bucket, access_key, secret_key, DIST_DIR,
'atom-shell/{0}'.format(version), [DIST_NAME])
s3put(bucket, access_key, secret_key, DIST_DIR,
'atom-shell/dist/{0}'.format(NODE_VERSION), glob.glob('node-*.tar.gz'))
'atom-shell/dist/{0}'.format(version), glob.glob('node-*.tar.gz'))

if TARGET_PLATFORM == 'win32':
# Generate the node.lib.
build = os.path.join(SOURCE_ROOT, 'script', 'build.py')
subprocess.check_call([sys.executable, build, '-c', 'Release',
'-t', 'generate_node_lib'])
subprocess.check_output([sys.executable, build, '-c', 'Release',
'-t', 'generate_node_lib'])

# Upload the 32bit node.lib.
node_lib = os.path.join(OUT_DIR, 'node.lib')
s3put(bucket, access_key, secret_key, OUT_DIR,
'atom-shell/dist/{0}'.format(NODE_VERSION), [node_lib])
'atom-shell/dist/{0}'.format(version), [node_lib])

# Upload the fake 64bit node.lib.
touch_x64_node_lib()
node_lib = os.path.join(OUT_DIR, 'x64', 'node.lib')
s3put(bucket, access_key, secret_key, OUT_DIR,
'atom-shell/dist/{0}'.format(NODE_VERSION), [node_lib])
'atom-shell/dist/{0}'.format(version), [node_lib])


def update_version(bucket, access_key, secret_key):
prefix = os.path.join(SOURCE_ROOT, 'dist')
version = os.path.join(prefix, 'version')
s3put(bucket, access_key, secret_key, prefix, 'atom-shell', [version])
def auth_token():
token = os.environ.get('ATOM_SHELL_GITHUB_TOKEN')
message = ('Error: Please set the $ATOM_SHELL_GITHUB_TOKEN '
'environment variable, which is your personal token')
assert token, message
return token


def s3_config():
Expand All @@ -116,7 +179,7 @@ def s3put(bucket, access_key, secret_key, prefix, key_prefix, files):
'--grant', 'public-read'
] + files

subprocess.check_call(args)
subprocess.check_output(args)


def touch_x64_node_lib():
Expand Down