diff --git a/common/api/atom_bindings.cc b/common/api/atom_bindings.cc index 32afe3bb81..3216232a67 100644 --- a/common/api/atom_bindings.cc +++ b/common/api/atom_bindings.cc @@ -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 DumpStackFrame(v8::Handle stack_frame) { + v8::Local result = v8::Object::New(); + result->Set(ToV8Value("line"), ToV8Value(stack_frame->GetLineNumber())); + result->Set(ToV8Value("column"), ToV8Value(stack_frame->GetColumn())); + + v8::Handle script = stack_frame->GetScriptName(); + if (!script.IsEmpty()) + result->Set(ToV8Value("script"), script); + + v8::Handle function = stack_frame->GetScriptNameOrSourceURL(); + if (!function.IsEmpty()) + result->Set(ToV8Value("function"), function); + return result; +} + } // namespace // Defined in atom_extensions.cc. @@ -37,6 +55,7 @@ void AtomBindings::BindTo(v8::Handle 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)); @@ -111,4 +130,23 @@ v8::Handle AtomBindings::Log(const v8::Arguments& args) { return v8::Undefined(); } +// static +v8::Handle AtomBindings::GetCurrentStackTrace( + const v8::Arguments& args) { + v8::HandleScope scope; + + int stack_limit = kMaxCallStackSize; + FromV8Arguments(args, &stack_limit); + + v8::Local stack_trace = v8::StackTrace::CurrentStackTrace( + stack_limit, v8::StackTrace::kDetailed); + + int frame_count = stack_trace->GetFrameCount(); + v8::Local 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 diff --git a/common/api/atom_bindings.h b/common/api/atom_bindings.h index 31b0152f04..84fc098d1f 100644 --- a/common/api/atom_bindings.h +++ b/common/api/atom_bindings.h @@ -24,6 +24,7 @@ class AtomBindings { static v8::Handle Crash(const v8::Arguments& args); static v8::Handle ActivateUVLoop(const v8::Arguments& args); static v8::Handle Log(const v8::Arguments& args); + static v8::Handle GetCurrentStackTrace(const v8::Arguments& args); DISALLOW_COPY_AND_ASSIGN(AtomBindings); }; diff --git a/common/v8_conversions.h b/common/v8_conversions.h index c59a88a850..bd5f72a6aa 100644 --- a/common/v8_conversions.h +++ b/common/v8_conversions.h @@ -77,6 +77,10 @@ inline v8::Handle ToV8Value(bool b) { return v8::Boolean::New(b); } +inline v8::Handle ToV8Value(const char* s) { + return v8::String::New(s); +} + inline v8::Handle ToV8Value(const std::string& s) { return v8::String::New(s.data(), s.size()); } diff --git a/script/cpplint.py b/script/cpplint.py index 2ee68e08d8..f624a1185f 100755 --- a/script/cpplint.py +++ b/script/cpplint.py @@ -7,12 +7,11 @@ import sys 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', diff --git a/script/lib/github.py b/script/lib/github.py new file mode 100644 index 0000000000..ebb3e3f53a --- /dev/null +++ b/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) diff --git a/script/upload.py b/script/upload.py index 1e1e782615..409fa05c6b 100755 --- a/script/upload.py +++ b/script/upload.py @@ -4,11 +4,13 @@ import argparse 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 = { @@ -18,6 +20,7 @@ TARGET_PLATFORM = { 'win32': 'win32', }[sys.platform] +ATOM_SHELL_REPO = 'atom/atom-shell' ATOM_SHELL_VRESION = get_atom_shell_version() NODE_VERSION = 'v0.10.18' @@ -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() @@ -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(): @@ -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():