Merge pull request #103 from atom/releases-api

Upload atom-shell's binaries with Releases API
This commit is contained in:
Cheng Zhao 2013-09-26 18:46:35 -07:00
commit c029ff2055
6 changed files with 196 additions and 21 deletions

View file

@ -7,17 +7,35 @@
#include "base/debug/debugger.h" #include "base/debug/debugger.h"
#include "base/logging.h" #include "base/logging.h"
#include "common/atom_version.h" #include "common/atom_version.h"
#include "common/v8_conversions.h"
#include "vendor/node/src/node.h" #include "vendor/node/src/node.h"
namespace atom { namespace atom {
namespace { namespace {
static int kMaxCallStackSize = 200; // Same with WebKit.
static uv_async_t dummy_uv_handle; static uv_async_t dummy_uv_handle;
void UvNoOp(uv_async_t* handle, int status) { 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 } // namespace
// Defined in atom_extensions.cc. // Defined in atom_extensions.cc.
@ -37,6 +55,7 @@ void AtomBindings::BindTo(v8::Handle<v8::Object> process) {
node::SetMethod(process, "crash", Crash); node::SetMethod(process, "crash", Crash);
node::SetMethod(process, "activateUvLoop", ActivateUVLoop); node::SetMethod(process, "activateUvLoop", ActivateUVLoop);
node::SetMethod(process, "log", Log); node::SetMethod(process, "log", Log);
node::SetMethod(process, "getCurrentStackTrace", GetCurrentStackTrace);
process->Get(v8::String::New("versions"))->ToObject()-> process->Get(v8::String::New("versions"))->ToObject()->
Set(v8::String::New("atom-shell"), v8::String::New(ATOM_VERSION_STRING)); Set(v8::String::New("atom-shell"), v8::String::New(ATOM_VERSION_STRING));
@ -111,4 +130,23 @@ v8::Handle<v8::Value> AtomBindings::Log(const v8::Arguments& args) {
return v8::Undefined(); 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 } // namespace atom

View file

@ -24,6 +24,7 @@ class AtomBindings {
static v8::Handle<v8::Value> Crash(const v8::Arguments& args); 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> ActivateUVLoop(const v8::Arguments& args);
static v8::Handle<v8::Value> Log(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); DISALLOW_COPY_AND_ASSIGN(AtomBindings);
}; };

View file

@ -77,6 +77,10 @@ inline v8::Handle<v8::Value> ToV8Value(bool b) {
return v8::Boolean::New(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) { inline v8::Handle<v8::Value> ToV8Value(const std::string& s) {
return v8::String::New(s.data(), s.size()); return v8::String::New(s.data(), s.size());
} }

3
script/cpplint.py vendored
View file

@ -7,12 +7,11 @@ import sys
IGNORE_FILES = [ IGNORE_FILES = [
'app/win/resource.h', 'app/win/resource.h',
'browser/atom_event_processing_window.h',
'browser/atom_application_mac.h', 'browser/atom_application_mac.h',
'browser/atom_application_delegate_mac.h', 'browser/atom_application_delegate_mac.h',
'browser/native_window_mac.h', 'browser/native_window_mac.h',
'browser/ui/atom_event_processing_window.h',
'browser/ui/atom_menu_controller_mac.h', 'browser/ui/atom_menu_controller_mac.h',
'browser/ui/cocoa/custom_frame_view.h',
'browser/ui/nsalert_synchronous_sheet_mac.h', 'browser/ui/nsalert_synchronous_sheet_mac.h',
'common/api/api_messages.cc', 'common/api/api_messages.cc',
'common/api/api_messages.h', 'common/api/api_messages.h',

70
script/lib/github.py Normal file
View file

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

View file

@ -4,11 +4,13 @@ import argparse
import errno import errno
import glob import glob
import os import os
import requests
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
from lib.util import * from lib.util import *
from lib.github import GitHub
TARGET_PLATFORM = { TARGET_PLATFORM = {
@ -18,6 +20,7 @@ TARGET_PLATFORM = {
'win32': 'win32', 'win32': 'win32',
}[sys.platform] }[sys.platform]
ATOM_SHELL_REPO = 'atom/atom-shell'
ATOM_SHELL_VRESION = get_atom_shell_version() ATOM_SHELL_VRESION = get_atom_shell_version()
NODE_VERSION = 'v0.10.18' NODE_VERSION = 'v0.10.18'
@ -32,18 +35,26 @@ def main():
if not dist_newer_than_head(): if not dist_newer_than_head():
create_dist = os.path.join(SOURCE_ROOT, 'script', 'create-dist.py') 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() bucket, access_key, secret_key = s3_config()
upload(bucket, access_key, secret_key) upload_node(bucket, access_key, secret_key, NODE_VERSION)
if not args.no_update_version:
update_version(bucket, access_key, secret_key)
def parse_args(): def parse_args():
parser = argparse.ArgumentParser(description='upload distribution file') parser = argparse.ArgumentParser(description='upload distribution file')
parser.add_argument('-n', '--no-update-version', parser.add_argument('-v', '--version', help='Specify the version',
help='Do not update the latest version file', default=ATOM_SHELL_VRESION)
parser.add_argument('-n', '--no-publish-release',
help='Do not publish the release',
action='store_true') action='store_true')
return parser.parse_args() return parser.parse_args()
@ -62,36 +73,88 @@ def dist_newer_than_head():
return dist_time > int(head_time) 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) os.chdir(DIST_DIR)
s3put(bucket, access_key, secret_key, DIST_DIR, s3put(bucket, access_key, secret_key, DIST_DIR,
'atom-shell/{0}'.format(version), [DIST_NAME]) 'atom-shell/dist/{0}'.format(version), glob.glob('node-*.tar.gz'))
s3put(bucket, access_key, secret_key, DIST_DIR,
'atom-shell/dist/{0}'.format(NODE_VERSION), glob.glob('node-*.tar.gz'))
if TARGET_PLATFORM == 'win32': if TARGET_PLATFORM == 'win32':
# Generate the node.lib. # Generate the node.lib.
build = os.path.join(SOURCE_ROOT, 'script', 'build.py') build = os.path.join(SOURCE_ROOT, 'script', 'build.py')
subprocess.check_call([sys.executable, build, '-c', 'Release', subprocess.check_output([sys.executable, build, '-c', 'Release',
'-t', 'generate_node_lib']) '-t', 'generate_node_lib'])
# Upload the 32bit node.lib. # Upload the 32bit node.lib.
node_lib = os.path.join(OUT_DIR, 'node.lib') node_lib = os.path.join(OUT_DIR, 'node.lib')
s3put(bucket, access_key, secret_key, OUT_DIR, 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. # Upload the fake 64bit node.lib.
touch_x64_node_lib() touch_x64_node_lib()
node_lib = os.path.join(OUT_DIR, 'x64', 'node.lib') node_lib = os.path.join(OUT_DIR, 'x64', 'node.lib')
s3put(bucket, access_key, secret_key, OUT_DIR, 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): def auth_token():
prefix = os.path.join(SOURCE_ROOT, 'dist') token = os.environ.get('ATOM_SHELL_GITHUB_TOKEN')
version = os.path.join(prefix, 'version') message = ('Error: Please set the $ATOM_SHELL_GITHUB_TOKEN '
s3put(bucket, access_key, secret_key, prefix, 'atom-shell', [version]) 'environment variable, which is your personal token')
assert token, message
return token
def s3_config(): def s3_config():
@ -116,7 +179,7 @@ def s3put(bucket, access_key, secret_key, prefix, key_prefix, files):
'--grant', 'public-read' '--grant', 'public-read'
] + files ] + files
subprocess.check_call(args) subprocess.check_output(args)
def touch_x64_node_lib(): def touch_x64_node_lib():