mirror of
https://github.com/roytam1/UXP.git
synced 2026-05-26 14:54:25 +00:00
Move Add-on SDK source to toolkit/jetpack
This commit is contained in:
@@ -1,29 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
TESTADDONS = source/test/addons
|
||||
ADDONSRC = $(srcdir)/$(TESTADDONS)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
# This can switch to just zipping the files when native jetpacks land
|
||||
%.xpi: FORCE
|
||||
$(PYTHON) $(srcdir)/source/bin/cfx xpi --no-strip-xpi --pkgdir=$(ADDONSRC)/$* --output-file=$@
|
||||
|
||||
TEST_FILES = \
|
||||
$(srcdir)/source/app-extension \
|
||||
$(srcdir)/source/bin \
|
||||
$(srcdir)/source/python-lib \
|
||||
$(srcdir)/source/test \
|
||||
$(srcdir)/source/package.json \
|
||||
$(srcdir)/source/mapping.json \
|
||||
$(NULL)
|
||||
|
||||
# Remove this once the test harness uses the APIs built into Firefox
|
||||
TEST_FILES += $(srcdir)/source/lib
|
||||
|
||||
PKG_STAGE = $(DIST)/test-stage
|
||||
|
||||
stage-tests-package:: $(TEST_FILES)
|
||||
$(INSTALL) $^ $(PKG_STAGE)/jetpack
|
||||
@@ -1,107 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# Integrates the xpcshell test runner with mach.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
|
||||
import mozpack.path as mozpath
|
||||
|
||||
from mozbuild.base import (
|
||||
MachCommandBase,
|
||||
)
|
||||
|
||||
from mach.decorators import (
|
||||
CommandArgument,
|
||||
CommandProvider,
|
||||
Command,
|
||||
)
|
||||
|
||||
@CommandProvider
|
||||
class MachCommands(MachCommandBase):
|
||||
@Command('generate-addon-sdk-moz-build', category='misc',
|
||||
description='Generates the moz.build file for the addon-sdk/ directory.')
|
||||
def run_addon_sdk_moz_build(self, **params):
|
||||
addon_sdk_dir = mozpath.join(self.topsrcdir, 'addon-sdk')
|
||||
js_src_dir = mozpath.join(addon_sdk_dir, 'source/lib')
|
||||
dirs_to_files = {}
|
||||
|
||||
for path, dirs, files in os.walk(js_src_dir):
|
||||
js_files = [f for f in files if f.endswith(('.js', '.jsm', '.html'))]
|
||||
if not js_files:
|
||||
continue
|
||||
|
||||
relative = mozpath.relpath(path, js_src_dir)
|
||||
dirs_to_files[relative] = js_files
|
||||
|
||||
moz_build = """# AUTOMATICALLY GENERATED FROM mozbuild.template AND mach. DO NOT EDIT.
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
%(moz-build-template)s
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk":
|
||||
%(non-b2g-modules)s
|
||||
%(always-on-modules)s"""
|
||||
|
||||
non_b2g_paths = [
|
||||
'method/test',
|
||||
'sdk/ui',
|
||||
'sdk/ui/button',
|
||||
'sdk/ui/sidebar',
|
||||
'sdk/places',
|
||||
'sdk/places/host',
|
||||
'sdk/tabs',
|
||||
'sdk/panel',
|
||||
'sdk/frame',
|
||||
'sdk/test',
|
||||
'sdk/window',
|
||||
'sdk/windows',
|
||||
'sdk/deprecated',
|
||||
]
|
||||
|
||||
non_b2g_modules = []
|
||||
always_on_modules = []
|
||||
|
||||
for d, files in sorted(dirs_to_files.items()):
|
||||
if d in non_b2g_paths:
|
||||
non_b2g_modules.append((d, files))
|
||||
else:
|
||||
always_on_modules.append((d, files))
|
||||
|
||||
def list_to_js_modules(l, indent=''):
|
||||
js_modules = []
|
||||
for d, files in l:
|
||||
if d == '':
|
||||
module_path = ''
|
||||
dir_path = ''
|
||||
else:
|
||||
# Ensure that we don't have things like:
|
||||
# EXTRA_JS_MODULES.commonjs.sdk.private-browsing
|
||||
# which would be a Python syntax error.
|
||||
path = d.split('/')
|
||||
module_path = ''.join('.' + p if p.find('-') == -1 else "['%s']" % p for p in path)
|
||||
dir_path = d + '/'
|
||||
filelist = ["'source/lib/%s%s'" % (dir_path, f)
|
||||
for f in sorted(files, key=lambda x: x.lower())]
|
||||
js_modules.append("EXTRA_JS_MODULES.commonjs%s += [\n %s,\n]\n"
|
||||
% (module_path, ',\n '.join(filelist)))
|
||||
stringified = '\n'.join(js_modules)
|
||||
# This isn't the same thing as |js_modules|, since |js_modules| had
|
||||
# embedded newlines.
|
||||
lines = stringified.split('\n')
|
||||
# Indent lines while avoiding trailing whitespace.
|
||||
lines = [indent + line if line else line for line in lines]
|
||||
return '\n'.join(lines)
|
||||
|
||||
moz_build_output = mozpath.join(addon_sdk_dir, 'moz.build')
|
||||
moz_build_template = mozpath.join(addon_sdk_dir, 'mozbuild.template')
|
||||
with open(moz_build_output, 'w') as f, open(moz_build_template, 'r') as t:
|
||||
substs = { 'moz-build-template': t.read(),
|
||||
'non-b2g-modules': list_to_js_modules(non_b2g_modules,
|
||||
indent=' '),
|
||||
'always-on-modules': list_to_js_modules(always_on_modules) }
|
||||
f.write(moz_build % substs)
|
||||
@@ -1,542 +0,0 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# Makefile.in uses a misc target through test_addons_TARGET.
|
||||
HAS_MISC_RULE = True
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
JETPACK_PACKAGE_MANIFESTS += ['source/test/jetpack-package.ini',
|
||||
'source/test/leak/jetpack-package.ini']
|
||||
JETPACK_ADDON_MANIFESTS += ['source/test/addons/jetpack-addon.ini']
|
||||
|
||||
DIRS += ['source/test/fixtures']
|
||||
|
||||
addons = [
|
||||
'addon-manager',
|
||||
'author-email',
|
||||
'child_process',
|
||||
'chrome',
|
||||
'content-permissions',
|
||||
'content-script-messages-latency',
|
||||
'contributors',
|
||||
'curly-id',
|
||||
'developers',
|
||||
'e10s-content',
|
||||
'e10s-l10n',
|
||||
'e10s-remote',
|
||||
'e10s-tabs',
|
||||
'e10s',
|
||||
'embedded-webextension',
|
||||
'l10n-properties',
|
||||
'l10n',
|
||||
'layout-change',
|
||||
'main',
|
||||
'name-in-numbers-plus',
|
||||
'name-in-numbers',
|
||||
'packaging',
|
||||
'packed',
|
||||
'page-mod-debugger-post',
|
||||
'page-mod-debugger-pre',
|
||||
'page-worker',
|
||||
'places',
|
||||
'predefined-id-with-at',
|
||||
'preferences-branch',
|
||||
'private-browsing-supported',
|
||||
'remote',
|
||||
'require',
|
||||
'self',
|
||||
'simple-prefs-l10n',
|
||||
'simple-prefs-regression',
|
||||
'simple-prefs',
|
||||
'standard-id',
|
||||
'tab-close-on-startup',
|
||||
'toolkit-require-reload',
|
||||
'translators',
|
||||
'unsafe-content-script',
|
||||
]
|
||||
|
||||
addons = ['%s.xpi' % f for f in addons]
|
||||
GENERATED_FILES += addons
|
||||
|
||||
TEST_HARNESS_FILES.testing.mochitest['jetpack-addon']['addon-sdk'].source.test.addons += [
|
||||
'!%s' % f for f in addons
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.sdk += [
|
||||
'source/app-extension/bootstrap.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.sdk.system += [
|
||||
'source/modules/system/Startup.js',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk":
|
||||
EXTRA_JS_MODULES.commonjs.method.test += [
|
||||
'source/lib/method/test/browser.js',
|
||||
'source/lib/method/test/common.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.deprecated += [
|
||||
'source/lib/sdk/deprecated/api-utils.js',
|
||||
'source/lib/sdk/deprecated/sync-worker.js',
|
||||
'source/lib/sdk/deprecated/unit-test-finder.js',
|
||||
'source/lib/sdk/deprecated/unit-test.js',
|
||||
'source/lib/sdk/deprecated/window-utils.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.frame += [
|
||||
'source/lib/sdk/frame/hidden-frame.js',
|
||||
'source/lib/sdk/frame/utils.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.panel += [
|
||||
'source/lib/sdk/panel/events.js',
|
||||
'source/lib/sdk/panel/utils.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.places += [
|
||||
'source/lib/sdk/places/bookmarks.js',
|
||||
'source/lib/sdk/places/contract.js',
|
||||
'source/lib/sdk/places/events.js',
|
||||
'source/lib/sdk/places/favicon.js',
|
||||
'source/lib/sdk/places/history.js',
|
||||
'source/lib/sdk/places/utils.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.places.host += [
|
||||
'source/lib/sdk/places/host/host-bookmarks.js',
|
||||
'source/lib/sdk/places/host/host-query.js',
|
||||
'source/lib/sdk/places/host/host-tags.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.tabs += [
|
||||
'source/lib/sdk/tabs/common.js',
|
||||
'source/lib/sdk/tabs/events.js',
|
||||
'source/lib/sdk/tabs/helpers.js',
|
||||
'source/lib/sdk/tabs/namespace.js',
|
||||
'source/lib/sdk/tabs/observer.js',
|
||||
'source/lib/sdk/tabs/tab-fennec.js',
|
||||
'source/lib/sdk/tabs/tab-firefox.js',
|
||||
'source/lib/sdk/tabs/tab.js',
|
||||
'source/lib/sdk/tabs/tabs-firefox.js',
|
||||
'source/lib/sdk/tabs/utils.js',
|
||||
'source/lib/sdk/tabs/worker.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.test += [
|
||||
'source/lib/sdk/test/assert.js',
|
||||
'source/lib/sdk/test/harness.js',
|
||||
'source/lib/sdk/test/httpd.js',
|
||||
'source/lib/sdk/test/loader.js',
|
||||
'source/lib/sdk/test/memory.js',
|
||||
'source/lib/sdk/test/options.js',
|
||||
'source/lib/sdk/test/runner.js',
|
||||
'source/lib/sdk/test/utils.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.ui += [
|
||||
'source/lib/sdk/ui/component.js',
|
||||
'source/lib/sdk/ui/frame.js',
|
||||
'source/lib/sdk/ui/id.js',
|
||||
'source/lib/sdk/ui/sidebar.js',
|
||||
'source/lib/sdk/ui/state.js',
|
||||
'source/lib/sdk/ui/toolbar.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.ui.button += [
|
||||
'source/lib/sdk/ui/button/action.js',
|
||||
'source/lib/sdk/ui/button/contract.js',
|
||||
'source/lib/sdk/ui/button/toggle.js',
|
||||
'source/lib/sdk/ui/button/view.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.ui.sidebar += [
|
||||
'source/lib/sdk/ui/sidebar/actions.js',
|
||||
'source/lib/sdk/ui/sidebar/contract.js',
|
||||
'source/lib/sdk/ui/sidebar/namespace.js',
|
||||
'source/lib/sdk/ui/sidebar/utils.js',
|
||||
'source/lib/sdk/ui/sidebar/view.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.window += [
|
||||
'source/lib/sdk/window/browser.js',
|
||||
'source/lib/sdk/window/events.js',
|
||||
'source/lib/sdk/window/helpers.js',
|
||||
'source/lib/sdk/window/namespace.js',
|
||||
'source/lib/sdk/window/utils.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.windows += [
|
||||
'source/lib/sdk/windows/fennec.js',
|
||||
'source/lib/sdk/windows/firefox.js',
|
||||
'source/lib/sdk/windows/observer.js',
|
||||
'source/lib/sdk/windows/tabs-fennec.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs += [
|
||||
'source/lib/index.js',
|
||||
'source/lib/test.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk += [
|
||||
'source/lib/sdk/webextension.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.dev += [
|
||||
'source/lib/dev/debuggee.js',
|
||||
'source/lib/dev/frame-script.js',
|
||||
'source/lib/dev/panel.js',
|
||||
'source/lib/dev/ports.js',
|
||||
'source/lib/dev/theme.js',
|
||||
'source/lib/dev/toolbox.js',
|
||||
'source/lib/dev/utils.js',
|
||||
'source/lib/dev/volcan.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.dev.panel += [
|
||||
'source/lib/dev/panel/view.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.dev.theme += [
|
||||
'source/lib/dev/theme/hooks.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.diffpatcher += [
|
||||
'source/lib/diffpatcher/diff.js',
|
||||
'source/lib/diffpatcher/index.js',
|
||||
'source/lib/diffpatcher/patch.js',
|
||||
'source/lib/diffpatcher/rebase.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.diffpatcher.test += [
|
||||
'source/lib/diffpatcher/test/common.js',
|
||||
'source/lib/diffpatcher/test/diff.js',
|
||||
'source/lib/diffpatcher/test/index.js',
|
||||
'source/lib/diffpatcher/test/patch.js',
|
||||
'source/lib/diffpatcher/test/tap.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.framescript += [
|
||||
'source/lib/framescript/content.jsm',
|
||||
'source/lib/framescript/context-menu.js',
|
||||
'source/lib/framescript/FrameScriptManager.jsm',
|
||||
'source/lib/framescript/manager.js',
|
||||
'source/lib/framescript/util.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs['jetpack-id'] += [
|
||||
'source/lib/jetpack-id/index.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.method += [
|
||||
'source/lib/method/core.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs['mozilla-toolkit-versioning'] += [
|
||||
'source/lib/mozilla-toolkit-versioning/index.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs['mozilla-toolkit-versioning'].lib += [
|
||||
'source/lib/mozilla-toolkit-versioning/lib/utils.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.node += [
|
||||
'source/lib/node/os.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk += [
|
||||
'source/lib/sdk/base64.js',
|
||||
'source/lib/sdk/clipboard.js',
|
||||
'source/lib/sdk/context-menu.js',
|
||||
'source/lib/sdk/context-menu@2.js',
|
||||
'source/lib/sdk/hotkeys.js',
|
||||
'source/lib/sdk/indexed-db.js',
|
||||
'source/lib/sdk/l10n.js',
|
||||
'source/lib/sdk/messaging.js',
|
||||
'source/lib/sdk/notifications.js',
|
||||
'source/lib/sdk/page-mod.js',
|
||||
'source/lib/sdk/page-worker.js',
|
||||
'source/lib/sdk/panel.js',
|
||||
'source/lib/sdk/passwords.js',
|
||||
'source/lib/sdk/private-browsing.js',
|
||||
'source/lib/sdk/querystring.js',
|
||||
'source/lib/sdk/request.js',
|
||||
'source/lib/sdk/selection.js',
|
||||
'source/lib/sdk/self.js',
|
||||
'source/lib/sdk/simple-prefs.js',
|
||||
'source/lib/sdk/simple-storage.js',
|
||||
'source/lib/sdk/system.js',
|
||||
'source/lib/sdk/tabs.js',
|
||||
'source/lib/sdk/test.js',
|
||||
'source/lib/sdk/timers.js',
|
||||
'source/lib/sdk/ui.js',
|
||||
'source/lib/sdk/url.js',
|
||||
'source/lib/sdk/windows.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.addon += [
|
||||
'source/lib/sdk/addon/bootstrap.js',
|
||||
'source/lib/sdk/addon/events.js',
|
||||
'source/lib/sdk/addon/host.js',
|
||||
'source/lib/sdk/addon/installer.js',
|
||||
'source/lib/sdk/addon/manager.js',
|
||||
'source/lib/sdk/addon/runner.js',
|
||||
'source/lib/sdk/addon/window.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.browser += [
|
||||
'source/lib/sdk/browser/events.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.console += [
|
||||
'source/lib/sdk/console/plain-text.js',
|
||||
'source/lib/sdk/console/traceback.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.content += [
|
||||
'source/lib/sdk/content/content-worker.js',
|
||||
'source/lib/sdk/content/content.js',
|
||||
'source/lib/sdk/content/context-menu.js',
|
||||
'source/lib/sdk/content/events.js',
|
||||
'source/lib/sdk/content/l10n-html.js',
|
||||
'source/lib/sdk/content/loader.js',
|
||||
'source/lib/sdk/content/mod.js',
|
||||
'source/lib/sdk/content/page-mod.js',
|
||||
'source/lib/sdk/content/page-worker.js',
|
||||
'source/lib/sdk/content/sandbox.js',
|
||||
'source/lib/sdk/content/tab-events.js',
|
||||
'source/lib/sdk/content/thumbnail.js',
|
||||
'source/lib/sdk/content/utils.js',
|
||||
'source/lib/sdk/content/worker-child.js',
|
||||
'source/lib/sdk/content/worker.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.content.sandbox += [
|
||||
'source/lib/sdk/content/sandbox/events.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk['context-menu'] += [
|
||||
'source/lib/sdk/context-menu/context.js',
|
||||
'source/lib/sdk/context-menu/core.js',
|
||||
'source/lib/sdk/context-menu/readers.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.core += [
|
||||
'source/lib/sdk/core/disposable.js',
|
||||
'source/lib/sdk/core/heritage.js',
|
||||
'source/lib/sdk/core/namespace.js',
|
||||
'source/lib/sdk/core/observer.js',
|
||||
'source/lib/sdk/core/promise.js',
|
||||
'source/lib/sdk/core/reference.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.deprecated.events += [
|
||||
'source/lib/sdk/deprecated/events/assembler.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.dom += [
|
||||
'source/lib/sdk/dom/events-shimmed.js',
|
||||
'source/lib/sdk/dom/events.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.dom.events += [
|
||||
'source/lib/sdk/dom/events/keys.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.event += [
|
||||
'source/lib/sdk/event/chrome.js',
|
||||
'source/lib/sdk/event/core.js',
|
||||
'source/lib/sdk/event/dom.js',
|
||||
'source/lib/sdk/event/target.js',
|
||||
'source/lib/sdk/event/utils.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.fs += [
|
||||
'source/lib/sdk/fs/path.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.input += [
|
||||
'source/lib/sdk/input/browser.js',
|
||||
'source/lib/sdk/input/customizable-ui.js',
|
||||
'source/lib/sdk/input/frame.js',
|
||||
'source/lib/sdk/input/system.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.io += [
|
||||
'source/lib/sdk/io/buffer.js',
|
||||
'source/lib/sdk/io/byte-streams.js',
|
||||
'source/lib/sdk/io/file.js',
|
||||
'source/lib/sdk/io/fs.js',
|
||||
'source/lib/sdk/io/stream.js',
|
||||
'source/lib/sdk/io/text-streams.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.keyboard += [
|
||||
'source/lib/sdk/keyboard/hotkeys.js',
|
||||
'source/lib/sdk/keyboard/observer.js',
|
||||
'source/lib/sdk/keyboard/utils.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.l10n += [
|
||||
'source/lib/sdk/l10n/core.js',
|
||||
'source/lib/sdk/l10n/html.js',
|
||||
'source/lib/sdk/l10n/loader.js',
|
||||
'source/lib/sdk/l10n/locale.js',
|
||||
'source/lib/sdk/l10n/plural-rules.js',
|
||||
'source/lib/sdk/l10n/prefs.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.l10n.json += [
|
||||
'source/lib/sdk/l10n/json/core.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.l10n.properties += [
|
||||
'source/lib/sdk/l10n/properties/core.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.lang += [
|
||||
'source/lib/sdk/lang/functional.js',
|
||||
'source/lib/sdk/lang/type.js',
|
||||
'source/lib/sdk/lang/weak-set.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.lang.functional += [
|
||||
'source/lib/sdk/lang/functional/concurrent.js',
|
||||
'source/lib/sdk/lang/functional/core.js',
|
||||
'source/lib/sdk/lang/functional/helpers.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.loader += [
|
||||
'source/lib/sdk/loader/cuddlefish.js',
|
||||
'source/lib/sdk/loader/sandbox.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.model += [
|
||||
'source/lib/sdk/model/core.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.net += [
|
||||
'source/lib/sdk/net/url.js',
|
||||
'source/lib/sdk/net/xhr.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.output += [
|
||||
'source/lib/sdk/output/system.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk['page-mod'] += [
|
||||
'source/lib/sdk/page-mod/match-pattern.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.passwords += [
|
||||
'source/lib/sdk/passwords/utils.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.platform += [
|
||||
'source/lib/sdk/platform/xpcom.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.preferences += [
|
||||
'source/lib/sdk/preferences/event-target.js',
|
||||
'source/lib/sdk/preferences/native-options.js',
|
||||
'source/lib/sdk/preferences/service.js',
|
||||
'source/lib/sdk/preferences/utils.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk['private-browsing'] += [
|
||||
'source/lib/sdk/private-browsing/utils.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.remote += [
|
||||
'source/lib/sdk/remote/child.js',
|
||||
'source/lib/sdk/remote/core.js',
|
||||
'source/lib/sdk/remote/parent.js',
|
||||
'source/lib/sdk/remote/utils.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.stylesheet += [
|
||||
'source/lib/sdk/stylesheet/style.js',
|
||||
'source/lib/sdk/stylesheet/utils.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.system += [
|
||||
'source/lib/sdk/system/child_process.js',
|
||||
'source/lib/sdk/system/environment.js',
|
||||
'source/lib/sdk/system/events-shimmed.js',
|
||||
'source/lib/sdk/system/events.js',
|
||||
'source/lib/sdk/system/globals.js',
|
||||
'source/lib/sdk/system/process.js',
|
||||
'source/lib/sdk/system/runtime.js',
|
||||
'source/lib/sdk/system/unload.js',
|
||||
'source/lib/sdk/system/xul-app.js',
|
||||
'source/lib/sdk/system/xul-app.jsm',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.system.child_process += [
|
||||
'source/lib/sdk/system/child_process/subprocess.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.tab += [
|
||||
'source/lib/sdk/tab/events.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.ui.button.view += [
|
||||
'source/lib/sdk/ui/button/view/events.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.ui.frame += [
|
||||
'source/lib/sdk/ui/frame/model.js',
|
||||
'source/lib/sdk/ui/frame/view.html',
|
||||
'source/lib/sdk/ui/frame/view.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.ui.state += [
|
||||
'source/lib/sdk/ui/state/events.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.ui.toolbar += [
|
||||
'source/lib/sdk/ui/toolbar/model.js',
|
||||
'source/lib/sdk/ui/toolbar/view.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.uri += [
|
||||
'source/lib/sdk/uri/resource.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.url += [
|
||||
'source/lib/sdk/url/utils.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.util += [
|
||||
'source/lib/sdk/util/array.js',
|
||||
'source/lib/sdk/util/collection.js',
|
||||
'source/lib/sdk/util/contract.js',
|
||||
'source/lib/sdk/util/deprecate.js',
|
||||
'source/lib/sdk/util/dispatcher.js',
|
||||
'source/lib/sdk/util/list.js',
|
||||
'source/lib/sdk/util/match-pattern.js',
|
||||
'source/lib/sdk/util/object.js',
|
||||
'source/lib/sdk/util/rules.js',
|
||||
'source/lib/sdk/util/sequence.js',
|
||||
'source/lib/sdk/util/uuid.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.view += [
|
||||
'source/lib/sdk/view/core.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.worker += [
|
||||
'source/lib/sdk/worker/utils.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.sdk.zip += [
|
||||
'source/lib/sdk/zip/utils.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.toolkit += [
|
||||
'source/lib/toolkit/loader.js',
|
||||
'source/lib/toolkit/require.js',
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# Makefile.in uses a misc target through test_addons_TARGET.
|
||||
HAS_MISC_RULE = True
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
JETPACK_PACKAGE_MANIFESTS += ['source/test/jetpack-package.ini']
|
||||
JETPACK_ADDON_MANIFESTS += ['source/test/addons/jetpack-addon.ini']
|
||||
|
||||
EXTRA_JS_MODULES.sdk += [
|
||||
'source/app-extension/bootstrap.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.sdk.system += [
|
||||
'source/modules/system/Startup.js',
|
||||
]
|
||||
@@ -1,5 +0,0 @@
|
||||
.gitignore export-ignore
|
||||
.hgignore export-ignore
|
||||
.hgtags export-ignore
|
||||
.gitattributes export-ignore
|
||||
python-lib/cuddlefish/_version.py export-subst
|
||||
@@ -1,36 +0,0 @@
|
||||
local.json
|
||||
python-lib/cuddlefish/app-extension/components/jetpack.xpt
|
||||
testdocs.tgz
|
||||
jetpack-sdk-docs.tgz
|
||||
.test_tmp/
|
||||
doc/dev-guide/
|
||||
doc/index.html
|
||||
doc/modules/
|
||||
doc/status.md5
|
||||
packages/*
|
||||
node_modules
|
||||
cache
|
||||
|
||||
# Python
|
||||
*.pyc
|
||||
|
||||
# OSX
|
||||
*.DS_Store
|
||||
|
||||
# Windows
|
||||
*Thumbs.db
|
||||
|
||||
# Ignore subtrees
|
||||
|
||||
# git@github.com:jsantell/jetpack-id.git
|
||||
lib/jetpack-id/*
|
||||
!lib/jetpack-id/index.js
|
||||
!lib/jetpack-id/package.json
|
||||
|
||||
# git@github.com:jsantell/mozilla-toolkit-versioning.git
|
||||
lib/mozilla-toolkit-versioning/*
|
||||
!lib/mozilla-toolkit-versioning/index.js
|
||||
!lib/mozilla-toolkit-versioning/lib
|
||||
lib/mozilla-toolkit-versioning/lib/*
|
||||
!lib/mozilla-toolkit-versioning/lib/*.js
|
||||
!lib/mozilla-toolkit-versioning/package.json
|
||||
@@ -1,18 +0,0 @@
|
||||
local.json
|
||||
mapping.json
|
||||
CONTRIBUTING.md
|
||||
@addon-sdk.xpi
|
||||
.*
|
||||
app-extension/
|
||||
bin/
|
||||
modules/
|
||||
node_modules/
|
||||
examples/
|
||||
cache/
|
||||
|
||||
# Python
|
||||
python-lib/
|
||||
*.pyc
|
||||
|
||||
# Windows
|
||||
*Thumbs.db
|
||||
@@ -1,26 +0,0 @@
|
||||
sudo: false
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.12"
|
||||
|
||||
env:
|
||||
- JPM_FX_DEBUG=0
|
||||
- JPM_FX_DEBUG=1
|
||||
|
||||
notifications:
|
||||
irc: "irc.mozilla.org#jetpack"
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- cache
|
||||
|
||||
before_install:
|
||||
- "export DISPLAY=:99.0"
|
||||
- "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 -extension RANDR"
|
||||
|
||||
before_script:
|
||||
- npm install fx-download -g
|
||||
- npm install gulp -g
|
||||
- bash bin/fx-download.sh
|
||||
- export JPM_FIREFOX_BINARY=$TRAVIS_BUILD_DIR/../firefox/firefox
|
||||
- cd $TRAVIS_BUILD_DIR
|
||||
@@ -1,54 +0,0 @@
|
||||
## Overview
|
||||
|
||||
- Changes should follow the [design guidelines], as well as the [coding style guide]
|
||||
- All changes need tests
|
||||
- In order to land, changes need a review by one of the Jetpack reviewers
|
||||
- Changes may need an API review
|
||||
- Changes may need a review from a Mozilla platform domain-expert
|
||||
|
||||
If you have questions, ask in [#jetpack on IRC][jetpack irc channel] or on the [Jetpack mailing list].
|
||||
|
||||
## How to Make Code Contributions
|
||||
|
||||
Follow the [standard mozilla contribution guidelines](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Introduction). All contributions and patches should be through Bugzilla.
|
||||
|
||||
Pull requests on github are not accepted and new pull requests on github will be rejected.
|
||||
|
||||
## Good First Bugs
|
||||
|
||||
There is a list of [good first bugs here][good first bugs].
|
||||
|
||||
## Reviewers
|
||||
|
||||
Changes should be reviewed by someone on the [add-on sdk review team](https://bugzilla.mozilla.org/page.cgi?id=review_suggestions.html#Add-on%20SDK) within Bugzilla.
|
||||
|
||||
Others who might be able to help include:
|
||||
|
||||
- [@mossop]
|
||||
- [@gozala]
|
||||
- [@ZER0]
|
||||
- [@jsantell]
|
||||
- [@zombie]
|
||||
|
||||
For review of Mozilla platform usage and best practices, ask [@autonome],
|
||||
[@0c0w3], or [@mossop] to find the domain expert.
|
||||
|
||||
For API and developer ergonomics review, ask [@gozala].
|
||||
|
||||
[design guidelines]:https://wiki.mozilla.org/Labs/Jetpack/Design_Guidelines
|
||||
[jetpack irc channel]:irc://irc.mozilla.org/#jetpack
|
||||
[Jetpack mailing list]:http://groups.google.com/group/mozilla-labs-jetpack
|
||||
[open bugs]:https://bugzilla.mozilla.org/buglist.cgi?quicksearch=product%3ASDK
|
||||
[make bug]:https://bugzilla.mozilla.org/enter_bug.cgi?product=Add-on%20SDK&component=general
|
||||
[test intro]:https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Unit_testing
|
||||
[test API]:https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/test_assert
|
||||
[coding style guide]:https://github.com/mozilla/addon-sdk/wiki/Coding-style-guide
|
||||
[Add-on SDK repo]:https://github.com/mozilla/addon-sdk
|
||||
[GitHub]:https://github.com/
|
||||
[good first bugs]:https://bugzilla.mozilla.org/buglist.cgi?list_id=7345714&columnlist=bug_severity%2Cpriority%2Cassigned_to%2Cbug_status%2Ctarget_milestone%2Cresolution%2Cshort_desc%2Cchangeddate&query_based_on=jetpack-good-1st-bugs&status_whiteboard_type=allwordssubstr&query_format=advanced&status_whiteboard=[good%20first%20bug]&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&bug_status=VERIFIED&product=Add-on%20SDK&known_name=jetpack-good-1st-bugs
|
||||
|
||||
[@mossop]:https://github.com/mossop/
|
||||
[@gozala]:https://github.com/Gozala/
|
||||
[@ZER0]:https://github.com/ZER0/
|
||||
[@jsantell]:https://github.com/jsantell
|
||||
[@zombie]:https://github.com/zombie
|
||||
@@ -1,30 +0,0 @@
|
||||
The files which make up the SDK are developed by Mozilla and licensed
|
||||
under the MPL 2.0 (http://mozilla.org/MPL/2.0/), with the exception of the
|
||||
components listed below, which are made available by their authors under
|
||||
the licenses listed alongside.
|
||||
|
||||
syntaxhighlighter
|
||||
------------------
|
||||
doc/static-files/syntaxhighlighter
|
||||
Made available under the MIT license.
|
||||
|
||||
jQuery
|
||||
------
|
||||
examples/reddit-panel/data/jquery-1.4.4.min.js
|
||||
examples/annotator/data/jquery-1.4.2.min.js
|
||||
Made available under the MIT license.
|
||||
|
||||
simplejson
|
||||
----------
|
||||
python-lib/simplejson
|
||||
Made available under the MIT license.
|
||||
|
||||
Python Markdown
|
||||
---------------
|
||||
python-lib/markdown
|
||||
Made available under the BSD license.
|
||||
|
||||
LibraryDetector
|
||||
---------------
|
||||
examples/library-detector/data/library-detector.js
|
||||
Made available under the MIT license.
|
||||
@@ -1,34 +0,0 @@
|
||||
# Mozilla Add-on SDK [](https://travis-ci.org/mozilla/addon-sdk)
|
||||
|
||||
We suggest that developers of new add-ons [should look at using WebExtensions](https://developer.mozilla.org/en-US/Add-ons/WebExtensions).
|
||||
|
||||
Using the Add-on SDK you can create Firefox add-ons using standard Web technologies: JavaScript, HTML, and CSS. The SDK includes JavaScript APIs which you can use to create add-ons, and tools for creating, running, testing, and packaging add-ons.
|
||||
|
||||
If you find a problem, please [report the bug here](https://bugzilla.mozilla.org/enter_bug.cgi?product=Add-on%20SDK).
|
||||
|
||||
## Developing Add-ons
|
||||
|
||||
These resources offer some help:
|
||||
|
||||
* [Add-on SDK Documentation](https://developer.mozilla.org/en-US/Add-ons/SDK)
|
||||
* [Community Developed Modules](https://github.com/mozilla/addon-sdk/wiki/Community-developed-modules)
|
||||
* [Jetpack FAQ](https://wiki.mozilla.org/Jetpack/FAQ)
|
||||
* [StackOverflow Questions](http://stackoverflow.com/questions/tagged/firefox-addon-sdk)
|
||||
* [Mailing List](https://wiki.mozilla.org/Jetpack#Mailing_list)
|
||||
* #jetpack on irc.mozilla.org
|
||||
|
||||
## Contributing Code
|
||||
|
||||
Please read these two guides if you wish to make some patches to the addon-sdk:
|
||||
|
||||
* [Contribute Guide](https://github.com/mozilla/addon-sdk/blob/master/CONTRIBUTING.md)
|
||||
* [Style Guide](https://github.com/mozilla/addon-sdk/wiki/Coding-style-guide)
|
||||
|
||||
## Issues
|
||||
|
||||
We use [bugzilla](https://bugzilla.mozilla.org/) as our issue tracker, here are some useful links:
|
||||
|
||||
* [File a bug](https://bugzilla.mozilla.org/enter_bug.cgi?product=Add-on%20SDK)
|
||||
* [Open bugs](https://bugzilla.mozilla.org/buglist.cgi?bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&columnlist=bug_severity%2Cpriority%2Cassigned_to%2Cbug_status%2Ctarget_milestone%2Cresolution%2Cshort_desc%2Cchangeddate&product=Add-on%20SDK&query_format=advanced&order=priority)
|
||||
* [Good first bugs](https://bugzilla.mozilla.org/buglist.cgi?status_whiteboard=[good+first+bug]&&resolution=---&product=Add-on+SDK)
|
||||
* [Good next bugs](https://bugzilla.mozilla.org/buglist.cgi?status_whiteboard=[good+next+bug]&&resolution=---&product=Add-on+SDK)
|
||||
@@ -1,84 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# This file must be used with "source bin/activate" *from bash*
|
||||
# you cannot run it directly
|
||||
|
||||
deactivate () {
|
||||
if [ -n "$_OLD_VIRTUAL_PATH" ] ; then
|
||||
PATH="$_OLD_VIRTUAL_PATH"
|
||||
export PATH
|
||||
unset _OLD_VIRTUAL_PATH
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then
|
||||
hash -r
|
||||
fi
|
||||
|
||||
if [ -n "$_OLD_VIRTUAL_PS1" ] ; then
|
||||
PS1="$_OLD_VIRTUAL_PS1"
|
||||
export PS1
|
||||
unset _OLD_VIRTUAL_PS1
|
||||
fi
|
||||
|
||||
PYTHONPATH="$_OLD_PYTHONPATH"
|
||||
export PYTHONPATH
|
||||
unset _OLD_PYTHONPATH
|
||||
|
||||
unset CUDDLEFISH_ROOT
|
||||
|
||||
unset VIRTUAL_ENV
|
||||
if [ ! "$1" = "nondestructive" ] ; then
|
||||
# Self destruct!
|
||||
unset deactivate
|
||||
fi
|
||||
}
|
||||
|
||||
# unset irrelavent variables
|
||||
deactivate nondestructive
|
||||
|
||||
_OLD_PYTHONPATH="$PYTHONPATH"
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
|
||||
VIRTUAL_ENV="`pwd`"
|
||||
|
||||
if [ "x$OSTYPE" = "xmsys" ] ; then
|
||||
CUDDLEFISH_ROOT="`pwd -W | sed s,/,\\\\\\\\,g`"
|
||||
PATH="`pwd`/bin:$PATH"
|
||||
# msys will convert any env vars with PATH in it to use msys
|
||||
# form and will unconvert before launching
|
||||
PYTHONPATH="`pwd -W`/python-lib;$PYTHONPATH"
|
||||
else
|
||||
CUDDLEFISH_ROOT="$VIRTUAL_ENV"
|
||||
PYTHONPATH="$VIRTUAL_ENV/python-lib:$PYTHONPATH"
|
||||
PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
fi
|
||||
|
||||
VIRTUAL_ENV="`pwd`"
|
||||
|
||||
export CUDDLEFISH_ROOT
|
||||
export PYTHONPATH
|
||||
export PATH
|
||||
|
||||
_OLD_VIRTUAL_PS1="$PS1"
|
||||
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
|
||||
# special case for Aspen magic directories
|
||||
# see http://www.zetadev.com/software/aspen/
|
||||
PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
|
||||
else
|
||||
PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
|
||||
fi
|
||||
export PS1
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then
|
||||
hash -r
|
||||
fi
|
||||
|
||||
python -c "from jetpack_sdk_env import welcome; welcome()"
|
||||
@@ -1,134 +0,0 @@
|
||||
@echo off
|
||||
rem This Source Code Form is subject to the terms of the Mozilla Public
|
||||
rem License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
rem file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
set VIRTUAL_ENV=%~dp0
|
||||
set VIRTUAL_ENV=%VIRTUAL_ENV:~0,-5%
|
||||
set CUDDLEFISH_ROOT=%VIRTUAL_ENV%
|
||||
|
||||
SET PYTHONKEY=SOFTWARE\Python\PythonCore
|
||||
|
||||
rem look for 32-bit windows and python, or 64-bit windows and python
|
||||
|
||||
SET PYTHONVERSION=2.7
|
||||
call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath
|
||||
if "%PYTHONINSTALL%" NEQ "" goto FoundPython
|
||||
|
||||
SET PYTHONVERSION=2.6
|
||||
call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath
|
||||
if "%PYTHONINSTALL%" NEQ "" goto FoundPython
|
||||
|
||||
SET PYTHONVERSION=2.5
|
||||
call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath
|
||||
if "%PYTHONINSTALL%" NEQ "" goto FoundPython
|
||||
|
||||
if not defined ProgramFiles(x86) goto win32
|
||||
|
||||
rem look for 32-bit python on 64-bit windows
|
||||
|
||||
SET PYTHONKEY=SOFTWARE\Wow6432Node\Python\PythonCore
|
||||
|
||||
SET PYTHONVERSION=2.7
|
||||
call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath
|
||||
if "%PYTHONINSTALL%" NEQ "" goto FoundPython
|
||||
|
||||
SET PYTHONVERSION=2.6
|
||||
call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath
|
||||
if "%PYTHONINSTALL%" NEQ "" goto FoundPython
|
||||
|
||||
SET PYTHONVERSION=2.5
|
||||
call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath
|
||||
if "%PYTHONINSTALL%" NEQ "" goto FoundPython
|
||||
|
||||
:win32
|
||||
|
||||
SET PYTHONVERSION=
|
||||
set PYTHONKEY=
|
||||
echo Warning: Failed to find Python installation directory
|
||||
goto :EOF
|
||||
|
||||
:FoundPython
|
||||
|
||||
if defined _OLD_PYTHONPATH (
|
||||
set PYTHONPATH=%_OLD_PYTHONPATH%
|
||||
)
|
||||
if not defined PYTHONPATH (
|
||||
set PYTHONPATH=;
|
||||
)
|
||||
set _OLD_PYTHONPATH=%PYTHONPATH%
|
||||
set PYTHONPATH=%VIRTUAL_ENV%\python-lib;%PYTHONPATH%
|
||||
|
||||
if not defined PROMPT (
|
||||
set PROMPT=$P$G
|
||||
)
|
||||
|
||||
if defined _OLD_VIRTUAL_PROMPT (
|
||||
set PROMPT=%_OLD_VIRTUAL_PROMPT%
|
||||
)
|
||||
|
||||
set _OLD_VIRTUAL_PROMPT=%PROMPT%
|
||||
set PROMPT=(%VIRTUAL_ENV%) %PROMPT%
|
||||
|
||||
if defined _OLD_VIRTUAL_PATH goto OLDPATH
|
||||
goto SKIPPATH
|
||||
:OLDPATH
|
||||
PATH %_OLD_VIRTUAL_PATH%
|
||||
|
||||
:SKIPPATH
|
||||
set _OLD_VIRTUAL_PATH=%PATH%
|
||||
PATH %VIRTUAL_ENV%\bin;%PYTHONINSTALL%;%PATH%
|
||||
set PYTHONKEY=
|
||||
set PYTHONINSTALL=
|
||||
set PYTHONVERSION=
|
||||
set key=
|
||||
set reg=
|
||||
set _tokens=
|
||||
python -c "from jetpack_sdk_env import welcome; welcome()"
|
||||
GOTO :EOF
|
||||
|
||||
:CheckPython
|
||||
::CheckPython(retVal, key)
|
||||
::Reads the registry at %2% and checks if a Python exists there.
|
||||
::Checks both HKLM and HKCU, then checks the executable actually exists.
|
||||
SET key=%2%
|
||||
SET "%~1="
|
||||
SET reg=reg
|
||||
if defined ProgramFiles(x86) (
|
||||
rem 32-bit cmd on 64-bit windows
|
||||
if exist %WINDIR%\sysnative\reg.exe SET reg=%WINDIR%\sysnative\reg.exe
|
||||
)
|
||||
rem On Vista+, the last line of output is:
|
||||
rem (default) REG_SZ the_value
|
||||
rem (but note the word "default" will be localized.
|
||||
rem On XP, the last line of output is:
|
||||
rem <NO NAME>\tREG_SZ\tthe_value
|
||||
rem (not sure if "NO NAME" is localized or not!)
|
||||
rem SO: we use ")>" as the tokens to split on, then nuke
|
||||
rem the REG_SZ and any tabs or spaces.
|
||||
FOR /F "usebackq tokens=2 delims=)>" %%A IN (`%reg% QUERY HKLM\%key% /ve 2^>NUL`) DO SET "%~1=%%A"
|
||||
rem Remove the REG_SZ
|
||||
set PYTHONINSTALL=%PYTHONINSTALL:REG_SZ=%
|
||||
rem Remove tabs (note the literal \t in the next line
|
||||
set PYTHONINSTALL=%PYTHONINSTALL: =%
|
||||
rem Remove spaces.
|
||||
set PYTHONINSTALL=%PYTHONINSTALL: =%
|
||||
if exist %PYTHONINSTALL%\python.exe goto :EOF
|
||||
rem It may be a 32bit Python directory built from source, in which case the
|
||||
rem executable is in the PCBuild directory.
|
||||
if exist %PYTHONINSTALL%\PCBuild\python.exe (set "PYTHONINSTALL=%PYTHONINSTALL%\PCBuild" & goto :EOF)
|
||||
rem Or maybe a 64bit build directory.
|
||||
if exist %PYTHONINSTALL%\PCBuild\amd64\python.exe (set "PYTHONINSTALL=%PYTHONINSTALL%\PCBuild\amd64" & goto :EOF)
|
||||
|
||||
rem And try HKCU
|
||||
FOR /F "usebackq tokens=2 delims=)>" %%A IN (`%reg% QUERY HKCU\%key% /ve 2^>NUL`) DO SET "%~1=%%A"
|
||||
set PYTHONINSTALL=%PYTHONINSTALL:REG_SZ=%
|
||||
set PYTHONINSTALL=%PYTHONINSTALL: =%
|
||||
set PYTHONINSTALL=%PYTHONINSTALL: =%
|
||||
if exist %PYTHONINSTALL%\python.exe goto :EOF
|
||||
if exist %PYTHONINSTALL%\PCBuild\python.exe (set "PYTHONINSTALL=%PYTHONINSTALL%\PCBuild" & goto :EOF)
|
||||
if exist %PYTHONINSTALL%\PCBuild\amd64\python.exe (set "PYTHONINSTALL=%PYTHONINSTALL%\PCBuild\amd64" & goto :EOF)
|
||||
rem can't find it here, so arrange to try the next key
|
||||
set PYTHONINSTALL=
|
||||
|
||||
GOTO :EOF
|
||||
@@ -1,66 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# This file must be used with "source bin/activate.fish" *from fish*
|
||||
# you cannot run it directly
|
||||
|
||||
# Much of this code is based off of the activate.fish file for the
|
||||
# virtualenv project. http://ur1.ca/ehmd6
|
||||
|
||||
function deactivate -d "Exit addon-sdk and return to normal shell environment"
|
||||
if test -n "$_OLD_VIRTUAL_PATH"
|
||||
set -gx PATH $_OLD_VIRTUAL_PATH
|
||||
set -e _OLD_VIRTUAL_PATH
|
||||
end
|
||||
|
||||
if test -n "$_OLD_PYTHONPATH"
|
||||
set -gx PYTHONPATH $_OLD_PYTHONPATH
|
||||
set -e _OLD_PYTHONPATH
|
||||
end
|
||||
|
||||
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
||||
functions -e fish_prompt
|
||||
set -e _OLD_FISH_PROMPT_OVERRIDE
|
||||
. ( begin
|
||||
printf "function fish_prompt\n\t#"
|
||||
functions _old_fish_prompt
|
||||
end | psub )
|
||||
|
||||
functions -e _old_fish_prompt
|
||||
end
|
||||
|
||||
set -e CUDDLEFISH_ROOT
|
||||
set -e VIRTUAL_ENV
|
||||
|
||||
if test "$argv[1]" != "nondestructive"
|
||||
functions -e deactivate
|
||||
end
|
||||
end
|
||||
|
||||
# unset irrelavent variables
|
||||
deactivate nondestructive
|
||||
|
||||
set -gx _OLD_PYTHONPATH $PYTHONPATH
|
||||
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||
set -gx _OLD_FISH_PROMPT_OVERRIDE "true"
|
||||
|
||||
set VIRTUAL_ENV (pwd)
|
||||
|
||||
set -gx CUDDLEFISH_ROOT $VIRTUAL_ENV
|
||||
set -gx PYTHONPATH "$VIRTUAL_ENV/python-lib" $PYTHONPATH
|
||||
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
|
||||
|
||||
# save the current fish_prompt function as the function _old_fish_prompt
|
||||
. ( begin
|
||||
printf "function _old_fish_prompt\n\t#"
|
||||
functions fish_prompt
|
||||
end | psub )
|
||||
|
||||
# with the original prompt function renamed, we can override with our own.
|
||||
function fish_prompt
|
||||
printf "(%s)%s%s" (basename "$VIRTUAL_ENV") (set_color normal) (_old_fish_prompt)
|
||||
return
|
||||
end
|
||||
|
||||
python -c "from jetpack_sdk_env import welcome; welcome()"
|
||||
@@ -1,99 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
$Env:VIRTUAL_ENV = (gl);
|
||||
$Env:CUDDLEFISH_ROOT = $Env:VIRTUAL_ENV;
|
||||
|
||||
# http://stackoverflow.com/questions/5648931/powershell-test-if-registry-value-exists/5652674#5652674
|
||||
Function Test-RegistryValue {
|
||||
param(
|
||||
[Alias("PSPath")]
|
||||
[Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
|
||||
[String]$Path
|
||||
,
|
||||
[Parameter(Position = 1, Mandatory = $true)]
|
||||
[String]$Name
|
||||
,
|
||||
[Switch]$PassThru
|
||||
)
|
||||
|
||||
process {
|
||||
if (Test-Path $Path) {
|
||||
$Key = Get-Item -LiteralPath $Path
|
||||
if ($Key.GetValue($Name, $null) -ne $null) {
|
||||
if ($PassThru) {
|
||||
Get-ItemProperty $Path $Name
|
||||
} else {
|
||||
$true
|
||||
}
|
||||
} else {
|
||||
$false
|
||||
}
|
||||
} else {
|
||||
$false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$WINCURVERKEY = 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion';
|
||||
$WIN64 = (Test-RegistryValue $WINCURVERKEY 'ProgramFilesDir (x86)');
|
||||
|
||||
if($WIN64) {
|
||||
$PYTHONKEY='HKLM:SOFTWARE\Wow6432Node\Python\PythonCore';
|
||||
}
|
||||
else {
|
||||
$PYTHONKEY='HKLM:SOFTWARE\Python\PythonCore';
|
||||
}
|
||||
|
||||
$Env:PYTHONVERSION = '';
|
||||
$Env:PYTHONINSTALL = '';
|
||||
|
||||
foreach ($version in @('2.6', '2.5', '2.4')) {
|
||||
if (Test-RegistryValue "$PYTHONKEY\$version\InstallPath" '(default)') {
|
||||
$Env:PYTHONVERSION = $version;
|
||||
}
|
||||
}
|
||||
|
||||
if ($Env:PYTHONVERSION) {
|
||||
$Env:PYTHONINSTALL = (Get-Item "$PYTHONKEY\$version\InstallPath)").'(default)';
|
||||
}
|
||||
|
||||
if ($Env:PYTHONINSTALL) {
|
||||
$Env:Path += ";$Env:PYTHONINSTALL";
|
||||
}
|
||||
|
||||
if (Test-Path Env:_OLD_PYTHONPATH) {
|
||||
$Env:PYTHONPATH = $Env:_OLD_PYTHONPATH;
|
||||
}
|
||||
else {
|
||||
$Env:PYTHONPATH = '';
|
||||
}
|
||||
|
||||
$Env:_OLD_PYTHONPATH=$Env:PYTHONPATH;
|
||||
$Env:PYTHONPATH= "$Env:VIRTUAL_ENV\python-lib;$Env:PYTHONPATH";
|
||||
|
||||
if (Test-Path Function:_OLD_VIRTUAL_PROMPT) {
|
||||
Set-Content Function:Prompt (Get-Content Function:_OLD_VIRTUAL_PROMPT);
|
||||
}
|
||||
else {
|
||||
function global:_OLD_VIRTUAL_PROMPT {}
|
||||
}
|
||||
|
||||
Set-Content Function:_OLD_VIRTUAL_PROMPT (Get-Content Function:Prompt);
|
||||
|
||||
function global:prompt { "($Env:VIRTUAL_ENV) $(_OLD_VIRTUAL_PROMPT)"; };
|
||||
|
||||
if (Test-Path Env:_OLD_VIRTUAL_PATH) {
|
||||
$Env:PATH = $Env:_OLD_VIRTUAL_PATH;
|
||||
}
|
||||
else {
|
||||
$Env:_OLD_VIRTUAL_PATH = $Env:PATH;
|
||||
}
|
||||
|
||||
$Env:Path="$Env:VIRTUAL_ENV\bin;$Env:Path"
|
||||
|
||||
[System.Console]::WriteLine("Note: this PowerShell SDK activation script is experimental.")
|
||||
|
||||
python -c "from jetpack_sdk_env import welcome; welcome()"
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# set the cuddlefish "root directory" for this process if it's not already
|
||||
# set in the environment
|
||||
cuddlefish_root = os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0])))
|
||||
|
||||
if 'CUDDLEFISH_ROOT' not in os.environ:
|
||||
os.environ['CUDDLEFISH_ROOT'] = cuddlefish_root
|
||||
|
||||
# add our own python-lib path to the python module search path.
|
||||
python_lib_dir = os.path.join(cuddlefish_root, "python-lib")
|
||||
if python_lib_dir not in sys.path:
|
||||
sys.path.insert(0, python_lib_dir)
|
||||
|
||||
# now export to env so sub-processes get it too
|
||||
if 'PYTHONPATH' not in os.environ:
|
||||
os.environ['PYTHONPATH'] = python_lib_dir
|
||||
elif python_lib_dir not in os.environ['PYTHONPATH'].split(os.pathsep):
|
||||
paths = os.environ['PYTHONPATH'].split(os.pathsep)
|
||||
paths.insert(0, python_lib_dir)
|
||||
os.environ['PYTHONPATH'] = os.pathsep.join(paths)
|
||||
|
||||
import cuddlefish
|
||||
|
||||
if __name__ == '__main__':
|
||||
cuddlefish.run()
|
||||
@@ -1,6 +0,0 @@
|
||||
@echo off
|
||||
rem This Source Code Form is subject to the terms of the Mozilla Public
|
||||
rem License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
rem file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
python "%VIRTUAL_ENV%\bin\cfx" %*
|
||||
@@ -1,23 +0,0 @@
|
||||
@echo off
|
||||
rem This Source Code Form is subject to the terms of the Mozilla Public
|
||||
rem License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
rem file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
if defined _OLD_VIRTUAL_PROMPT (
|
||||
set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
|
||||
)
|
||||
set _OLD_VIRTUAL_PROMPT=
|
||||
|
||||
if defined _OLD_VIRTUAL_PATH (
|
||||
set "PATH=%_OLD_VIRTUAL_PATH%"
|
||||
)
|
||||
set _OLD_VIRTUAL_PATH=
|
||||
|
||||
if defined _OLD_PYTHONPATH (
|
||||
set "PYTHONPATH=%_OLD_PYTHONPATH%"
|
||||
)
|
||||
set _OLD_PYTHONPATH=
|
||||
|
||||
set CUDDLEFISH_ROOT=
|
||||
|
||||
:END
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ "$JPM_FX_DEBUG" = "1" ]; then
|
||||
fx-download --branch nightly -c prerelease --host ftp.mozilla.org ../firefox --debug
|
||||
else
|
||||
fx-download --branch nightly -c prerelease --host ftp.mozilla.org ../firefox
|
||||
fi
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/bin/bash
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
|
||||
source ./bin/activate
|
||||
if [ type -P xvfb-run ]
|
||||
then
|
||||
xvfb-run cfx $*
|
||||
else
|
||||
cfx $*
|
||||
fi
|
||||
deactivate
|
||||
@@ -1,364 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
import signal
|
||||
import threading
|
||||
import urllib2, urllib
|
||||
import zipfile
|
||||
import tarfile
|
||||
import subprocess
|
||||
import optparse
|
||||
import sys, re
|
||||
#import win32api
|
||||
|
||||
|
||||
class SDK:
|
||||
def __init__(self):
|
||||
try:
|
||||
# Take the current working directory
|
||||
self.default_path = os.getcwd()
|
||||
if sys.platform == "win32":
|
||||
self.mswindows = True
|
||||
else:
|
||||
self.mswindows = False
|
||||
# Take the default home path of the user.
|
||||
home = os.path.expanduser('~')
|
||||
|
||||
# The following are the parameters that can be used to pass a dynamic URL, a specific path or a binry. The binary is not used yet. It will be used in version 2.0
|
||||
# If a dynamic path is to be mentioned, it should start with a '/'. For eg. "/Desktop"
|
||||
parser = optparse.OptionParser()
|
||||
parser.add_option('-u', '--url', dest = 'url', default = 'https://ftp.mozilla.org/pub/mozilla.org/labs/jetpack/addon-sdk-latest.zip')
|
||||
parser.add_option('-p', '--path', dest = 'path', default = self.default_path)
|
||||
parser.add_option('-b', '--binary', dest = 'binary')#, default='/Applications/Firefox.app')
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
# Get the URL from the parameter
|
||||
self.link = options.url
|
||||
# Set the base path for the user. If the user supplies the path, use the home variable as well. Else, take the default path of this script as the installation directory.
|
||||
if options.path!=self.default_path:
|
||||
if self.mswindows:
|
||||
self.base_path = home + str(options.path).strip() + '\\'
|
||||
else:
|
||||
self.base_path = home + str(options.path).strip() + '/'
|
||||
else:
|
||||
if self.mswindows:
|
||||
self.base_path = str(options.path).strip() + '\\'
|
||||
else:
|
||||
self.base_path = str(options.path).strip() + '/'
|
||||
assert ' ' not in self.base_path, "You cannot have a space in your home path. Please remove the space before you continue."
|
||||
print('Your Base path is =' + self.base_path)
|
||||
|
||||
# This assignment is not used in this program. It will be used in version 2 of this script.
|
||||
self.bin = options.binary
|
||||
# if app or bin is empty, dont pass anything
|
||||
|
||||
# Search for the .zip file or tarball file in the URL.
|
||||
i = self.link.rfind('/')
|
||||
|
||||
self.fname = self.link[i+1:]
|
||||
z = re.search('zip',self.fname,re.I)
|
||||
g = re.search('gz',self.fname,re.I)
|
||||
if z:
|
||||
print 'zip file present in the URL.'
|
||||
self.zip = True
|
||||
self.gz = False
|
||||
elif g:
|
||||
print 'gz file present in the URL'
|
||||
self.gz = True
|
||||
self.zip = False
|
||||
else:
|
||||
print 'zip/gz file not present. Check the URL.'
|
||||
return
|
||||
print("File name is =" + self.fname)
|
||||
|
||||
# Join the base path and the zip/tar file name to crate a complete Local file path.
|
||||
self.fpath = self.base_path + self.fname
|
||||
print('Your local file path will be=' + self.fpath)
|
||||
except AssertionError, e:
|
||||
print e.args[0]
|
||||
sys.exit(1)
|
||||
|
||||
# Download function - to download the SDK from the URL to the local machine.
|
||||
def download(self,url,fpath,fname):
|
||||
try:
|
||||
# Start the download
|
||||
print("Downloading...Please be patient!")
|
||||
urllib.urlretrieve(url,filename = fname)
|
||||
print('Download was successful.')
|
||||
except ValueError: # Handles broken URL errors.
|
||||
print 'The URL is ether broken or the file does not exist. Please enter the correct URL.'
|
||||
raise
|
||||
except urllib2.URLError: # Handles URL errors
|
||||
print '\nURL not correct. Check again!'
|
||||
raise
|
||||
|
||||
# Function to extract the downloaded zipfile.
|
||||
def extract(self, zipfilepath, extfile):
|
||||
try:
|
||||
# Timeout is set to 30 seconds.
|
||||
timeout = 30
|
||||
# Change the directory to the location of the zip file.
|
||||
try:
|
||||
os.chdir(zipfilepath)
|
||||
except OSError:
|
||||
# Will reach here if zip file doesnt exist
|
||||
print 'O/S Error:' + zipfilepath + 'does not exist'
|
||||
raise
|
||||
|
||||
# Get the folder name of Jetpack to get the exact version number.
|
||||
if self.zip:
|
||||
try:
|
||||
f = zipfile.ZipFile(extfile, "r")
|
||||
except IOError as (errno, strerror): # Handles file errors
|
||||
print "I/O error - Cannot perform extract operation: {1}".format(errno, strerror)
|
||||
raise
|
||||
list = f.namelist()[0]
|
||||
temp_name = list.split('/')
|
||||
print('Folder Name= ' +temp_name[0])
|
||||
self.folder_name = temp_name[0]
|
||||
elif self.gz:
|
||||
try:
|
||||
f = tarfile.open(extfile,'r')
|
||||
except IOError as (errno, strerror): # Handles file errors
|
||||
print "I/O error - Cannot perform extract operation: {1}".format(errno, strerror)
|
||||
raise
|
||||
list = f.getnames()[0]
|
||||
temp_name = list.split('/')
|
||||
print('Folder Name= ' +temp_name[0])
|
||||
self.folder_name = temp_name[0]
|
||||
|
||||
print ('Starting to Extract...')
|
||||
|
||||
# Timeout code. The subprocess.popen exeutes the command and the thread waits for a timeout. If the process does not finish within the mentioned-
|
||||
# timeout, the process is killed.
|
||||
kill_check = threading.Event()
|
||||
|
||||
if self.zip:
|
||||
# Call the command to unzip the file.
|
||||
if self.mswindows:
|
||||
zipfile.ZipFile.extractall(f)
|
||||
else:
|
||||
p = subprocess.Popen('unzip '+extfile, stdout=subprocess.PIPE, shell=True)
|
||||
pid = p.pid
|
||||
elif self.gz:
|
||||
# Call the command to untar the file.
|
||||
if self.mswindows:
|
||||
tarfile.TarFile.extractall(f)
|
||||
else:
|
||||
p = subprocess.Popen('tar -xf '+extfile, stdout=subprocess.PIPE, shell=True)
|
||||
pid = p.pid
|
||||
|
||||
#No need to handle for windows because windows automatically replaces old files with new files. It does not ask the user(as it does in Mac/Unix)
|
||||
if self.mswindows==False:
|
||||
watch = threading.Timer(timeout, kill_process, args=(pid, kill_check, self.mswindows ))
|
||||
watch.start()
|
||||
(stdout, stderr) = p.communicate()
|
||||
watch.cancel() # if it's still waiting to run
|
||||
success = not kill_check.isSet()
|
||||
|
||||
# Abort process if process fails.
|
||||
if not success:
|
||||
raise RuntimeError
|
||||
kill_check.clear()
|
||||
print('Extraction Successful.')
|
||||
except RuntimeError:
|
||||
print "Ending the program"
|
||||
sys.exit(1)
|
||||
except:
|
||||
print "Error during file extraction: ", sys.exc_info()[0]
|
||||
raise
|
||||
|
||||
# Function to run the cfx testall comands and to make sure the SDK is not broken.
|
||||
def run_testall(self, home_path, folder_name):
|
||||
try:
|
||||
timeout = 500
|
||||
|
||||
self.new_dir = home_path + folder_name
|
||||
try:
|
||||
os.chdir(self.new_dir)
|
||||
except OSError:
|
||||
# Will reach here if the jetpack 0.X directory doesnt exist
|
||||
print 'O/S Error: Jetpack directory does not exist at ' + self.new_dir
|
||||
raise
|
||||
print '\nStarting tests...'
|
||||
# Timeout code. The subprocess.popen exeutes the command and the thread waits for a timeout. If the process does not finish within the mentioned-
|
||||
# timeout, the process is killed.
|
||||
kill_check = threading.Event()
|
||||
|
||||
# Set the path for the logs. They will be in the parent directory of the Jetpack SDK.
|
||||
log_path = home_path + 'tests.log'
|
||||
|
||||
# Subprocess call to set up the jetpack environment and to start the tests. Also sends the output to a log file.
|
||||
if self.bin != None:
|
||||
if self.mswindows:
|
||||
p = subprocess.Popen("bin\\activate && cfx testall -a firefox -b \"" + self.bin + "\"" , stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
proc_handle = p._handle
|
||||
(stdout,stderr) = p.communicate()
|
||||
else:
|
||||
p = subprocess.Popen('. bin/activate; cfx testall -a firefox -b ' + self.bin , stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
pid = p.pid
|
||||
(stdout,stderr) = p.communicate()
|
||||
elif self.bin == None:
|
||||
if self.mswindows:
|
||||
p=subprocess.Popen('bin\\activate && cfx testall -a firefox > '+log_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
proc_handle = p._handle
|
||||
(stdout,stderr) = p.communicate()
|
||||
else:
|
||||
p = subprocess.Popen('. bin/activate; cfx testall -a firefox > '+log_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
pid = p.pid
|
||||
(stdout,stderr) = p.communicate()
|
||||
|
||||
#Write the output to log file
|
||||
f=open(log_path,"w")
|
||||
f.write(stdout+stderr)
|
||||
f.close()
|
||||
|
||||
#Watchdog for timeout process
|
||||
if self.mswindows:
|
||||
watch = threading.Timer(timeout, kill_process, args=(proc_handle, kill_check, self.mswindows))
|
||||
else:
|
||||
watch = threading.Timer(timeout, kill_process, args=(pid, kill_check, self.mswindows))
|
||||
watch.start()
|
||||
watch.cancel() # if it's still waiting to run
|
||||
success = not kill_check.isSet()
|
||||
if not success:
|
||||
raise RuntimeError
|
||||
kill_check.clear()
|
||||
|
||||
if p.returncode!=0:
|
||||
print('\nAll tests were not successful. Check the test-logs in the jetpack directory.')
|
||||
result_sdk(home_path)
|
||||
#sys.exit(1)
|
||||
raise RuntimeError
|
||||
else:
|
||||
ret_code=result_sdk(home_path)
|
||||
if ret_code==0:
|
||||
print('\nAll tests were successful. Yay \o/ . Running a sample package test now...')
|
||||
else:
|
||||
print ('\nThere were errors during the tests.Take a look at logs')
|
||||
raise RuntimeError
|
||||
except RuntimeError:
|
||||
print "Ending the program"
|
||||
sys.exit(1)
|
||||
except:
|
||||
print "Error during the testall command execution:", sys.exc_info()[0]
|
||||
raise
|
||||
|
||||
def package(self, example_dir):
|
||||
try:
|
||||
timeout = 30
|
||||
|
||||
print '\nNow Running packaging tests...'
|
||||
|
||||
kill_check = threading.Event()
|
||||
|
||||
# Set the path for the example logs. They will be in the parent directory of the Jetpack SDK.
|
||||
exlog_path = example_dir + 'test-example.log'
|
||||
# Subprocess call to test the sample example for packaging.
|
||||
if self.bin!=None:
|
||||
if self.mswindows:
|
||||
p = subprocess.Popen('bin\\activate && cfx run --pkgdir examples\\reading-data --static-args="{\\"quitWhenDone\\":true}" -b \"" + self.bin + "\"' , stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
proc_handle = p._handle
|
||||
(stdout, stderr) = p.communicate()
|
||||
else:
|
||||
p = subprocess.Popen('. bin/activate; cfx run --pkgdir examples/reading-data --static-args=\'{\"quitWhenDone\":true}\' -b ' + self.bin , stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
pid = p.pid
|
||||
(stdout, stderr) = p.communicate()
|
||||
elif self.bin==None:
|
||||
if self.mswindows:
|
||||
p = subprocess.Popen('bin\\activate && cfx run --pkgdir examples\\reading-data --static-args="{\\"quitWhenDone\\":true}"', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
proc_handle = p._handle
|
||||
(stdout, stderr) = p.communicate()
|
||||
else:
|
||||
p = subprocess.Popen('. bin/activate; cfx run --pkgdir examples/reading-data --static-args=\'{\"quitWhenDone\":true}\' ', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
pid = p.pid
|
||||
(stdout, stderr) = p.communicate()
|
||||
|
||||
#Write the output to log file
|
||||
f=open(exlog_path,"w")
|
||||
f.write(stdout+stderr)
|
||||
f.close()
|
||||
|
||||
#Watch dog for timeout process
|
||||
if self.mswindows:
|
||||
watch = threading.Timer(timeout, kill_process, args=(proc_handle, kill_check, self.mswindows))
|
||||
else:
|
||||
watch = threading.Timer(timeout, kill_process, args=(pid, kill_check, self.mswindows))
|
||||
watch.start()
|
||||
watch.cancel() # if it's still waiting to run
|
||||
success = not kill_check.isSet()
|
||||
if not success:
|
||||
raise RuntimeError
|
||||
kill_check.clear()
|
||||
|
||||
if p.returncode != 0:
|
||||
print('\nSample tests were not executed correctly. Check the test-example log in jetpack diretory.')
|
||||
result_example(example_dir)
|
||||
raise RuntimeError
|
||||
else:
|
||||
ret_code=result_example(example_dir)
|
||||
if ret_code==0:
|
||||
print('\nAll tests pass. The SDK is working! Yay \o/')
|
||||
else:
|
||||
print ('\nTests passed with warning.Take a look at logs')
|
||||
sys.exit(1)
|
||||
|
||||
except RuntimeError:
|
||||
print "Ending program"
|
||||
sys.exit(1)
|
||||
except:
|
||||
print "Error during running sample tests:", sys.exc_info()[0]
|
||||
raise
|
||||
|
||||
def result_sdk(sdk_dir):
|
||||
log_path = sdk_dir + 'tests.log'
|
||||
print 'Results are logged at:' + log_path
|
||||
try:
|
||||
f = open(log_path,'r')
|
||||
# Handles file errors
|
||||
except IOError :
|
||||
print 'I/O error - Cannot open test log at ' + log_path
|
||||
raise
|
||||
|
||||
for line in reversed(open(log_path).readlines()):
|
||||
if line.strip()=='FAIL':
|
||||
print ('\nOverall result - FAIL. Look at the test log at '+log_path)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def result_example(sdk_dir):
|
||||
exlog_path = sdk_dir + 'test-example.log'
|
||||
print 'Sample test results are logged at:' + exlog_path
|
||||
try:
|
||||
f = open(exlog_path,'r')
|
||||
# Handles file errors
|
||||
except IOError :
|
||||
print 'I/O error - Cannot open sample test log at ' + exlog_path
|
||||
raise
|
||||
|
||||
#Read the file in reverse and check for the keyword 'FAIL'.
|
||||
for line in reversed(open(exlog_path).readlines()):
|
||||
if line.strip()=='FAIL':
|
||||
print ('\nOverall result for Sample tests - FAIL. Look at the test log at '+exlog_path)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def kill_process(process, kill_check, mswindows):
|
||||
print '\nProcess Timedout. Killing the process. Please Rerun this script.'
|
||||
if mswindows:
|
||||
win32api.TerminateProcess(process, -1)
|
||||
else:
|
||||
os.kill(process, signal.SIGKILL)
|
||||
kill_check.set()# tell the main routine to kill. Used SIGKILL to hard kill the process.
|
||||
return
|
||||
|
||||
if __name__ == "__main__":
|
||||
obj = SDK()
|
||||
obj.download(obj.link,obj.fpath,obj.fname)
|
||||
obj.extract(obj.base_path,obj.fname)
|
||||
obj.run_testall(obj.base_path,obj.folder_name)
|
||||
obj.package(obj.base_path)
|
||||
@@ -1,34 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var Promise = require("promise");
|
||||
var Mocha = require("mocha");
|
||||
var mocha = new Mocha({
|
||||
ui: "bdd",
|
||||
reporter: "spec",
|
||||
timeout: 900000
|
||||
});
|
||||
|
||||
var isDebug = require("./node-scripts/utils").isDebug;
|
||||
|
||||
exports.run = function(type) {
|
||||
return new Promise(function(resolve) {
|
||||
type = type || "";
|
||||
[
|
||||
(!isDebug && /^(firefox-bin)?$/.test(type)) && require.resolve("../bin/node-scripts/test.firefox-bin"),
|
||||
(!isDebug && /^(docs)?$/.test(type)) && require.resolve("../bin/node-scripts/test.docs"),
|
||||
(!isDebug && /^(ini)?$/.test(type)) && require.resolve("../bin/node-scripts/test.ini"),
|
||||
(/^(examples)?$/.test(type)) && require.resolve("../bin/node-scripts/test.examples"),
|
||||
(!isDebug && /^(addons)?$/.test(type)) && require.resolve("../bin/node-scripts/test.addons"),
|
||||
(!isDebug && /^(modules)?$/.test(type)) && require.resolve("../bin/node-scripts/test.modules"),
|
||||
].forEach(function(filepath) {
|
||||
filepath && mocha.addFile(filepath);
|
||||
})
|
||||
|
||||
mocha.run(function(failures) {
|
||||
resolve(failures);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var path = require("path");
|
||||
var cp = require("child_process");
|
||||
var fs = require("fs");
|
||||
var Promise = require("promise");
|
||||
var patcher = require("patch-editor");
|
||||
var readParam = require("./utils").readParam;
|
||||
|
||||
var isKeeper = /\/addon-sdk\/source/;
|
||||
|
||||
function apply(options) {
|
||||
return clean(options).then(function() {
|
||||
return new Promise(function(resolve) {
|
||||
var patch = path.resolve(readParam("patch"));
|
||||
var proc = cp.spawn("git", ["apply", patch]);
|
||||
proc.stdout.pipe(process.stdout);
|
||||
proc.stderr.pipe(process.stderr);
|
||||
proc.on("close", resolve);
|
||||
});
|
||||
});
|
||||
}
|
||||
exports.apply = apply;
|
||||
|
||||
function clean(options) {
|
||||
return new Promise(function(resolve) {
|
||||
var patch = path.resolve(readParam("patch"));
|
||||
if (!patch) {
|
||||
throw new Error("no --patch was provided.");
|
||||
}
|
||||
console.log("Cleaning patch " + patch);
|
||||
|
||||
patcher.getChunks({ patch: patch }).then(function(chunks) {
|
||||
var keepers = [];
|
||||
|
||||
for (var i = chunks.length - 1; i >= 0; i--) {
|
||||
var chunk = chunks[i];
|
||||
var files = chunk.getFilesChanged();
|
||||
|
||||
// check if the file changed is related to the addon-sdk/source directory
|
||||
var keepIt = files.map(function(file) {
|
||||
return (isKeeper.test(file));
|
||||
}).reduce(function(prev, curr) {
|
||||
return prev || curr;
|
||||
}, false);
|
||||
|
||||
if (keepIt) {
|
||||
keepers.push(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
var contents = "\n" + keepers.join("\n") + "\n";
|
||||
contents = contents.replace(/\/addon-sdk\/source/g, "");
|
||||
|
||||
fs.writeFileSync(patch, contents, { encoding: "utf8" });
|
||||
|
||||
console.log("Done cleaning patch.");
|
||||
}).then(resolve).catch(console.error);
|
||||
});
|
||||
}
|
||||
exports.clean = clean;
|
||||
@@ -1,57 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var utils = require("./utils");
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
var jpm = utils.run;
|
||||
var readParam = utils.readParam;
|
||||
var isDebug = utils.isDebug;
|
||||
|
||||
var addonsPath = path.join(__dirname, "..", "..", "test", "addons");
|
||||
|
||||
var binary = process.env.JPM_FIREFOX_BINARY || "nightly";
|
||||
var filterPattern = readParam("filter");
|
||||
|
||||
describe("jpm test sdk addons", function () {
|
||||
fs.readdirSync(addonsPath)
|
||||
.filter(fileFilter.bind(null, addonsPath))
|
||||
.forEach(function (file) {
|
||||
it(file, function (done) {
|
||||
var addonPath = path.join(addonsPath, file);
|
||||
process.chdir(addonPath);
|
||||
|
||||
var options = { cwd: addonPath, env: { JPM_FIREFOX_BINARY: binary }};
|
||||
if (process.env.DISPLAY) {
|
||||
options.env.DISPLAY = process.env.DISPLAY;
|
||||
}
|
||||
if (/^e10s/.test(file)) {
|
||||
options.e10s = true;
|
||||
}
|
||||
|
||||
jpm("run", options).then(done).catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function fileFilter(root, file) {
|
||||
var matcher = filterPattern && new RegExp(filterPattern);
|
||||
if (/^(l10n-properties|simple-prefs|page-mod-debugger)/.test(file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// filter additional add-ons when using debug builds
|
||||
if (isDebug) {
|
||||
if (/^(chrome|e10s)/.test(file)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (matcher && !matcher.test(file)) {
|
||||
return false;
|
||||
}
|
||||
var stat = fs.statSync(path.join(root, file))
|
||||
return (stat && stat.isDirectory());
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var createHash = require('crypto').createHash;
|
||||
var fs = require("fs");
|
||||
var fsExtra = require("fs-extra")
|
||||
var path = require("path");
|
||||
var Promise = require("promise");
|
||||
var chai = require("chai");
|
||||
var expect = chai.expect;
|
||||
var teacher = require("teacher");
|
||||
|
||||
var rootURI = path.join(__dirname, "..", "..");
|
||||
|
||||
// get a list of words that fail spell check but are still acceptable
|
||||
var NEW_WORDS = fs.readFileSync(path.join(__dirname, "words.txt")).toString().trim().split("\n");
|
||||
|
||||
var CACHE_PATH = path.join(__dirname, "..", "..", "cache", "spellchecks.json");
|
||||
|
||||
var CACHE = {};
|
||||
|
||||
try {
|
||||
CACHE = JSON.parse(fs.readFileSync(CACHE_PATH).toString());
|
||||
}
|
||||
catch (e) {}
|
||||
|
||||
function md5(str) {
|
||||
return createHash("md5").update(str).digest("utf8");
|
||||
}
|
||||
|
||||
function addCacheHash(hash) {
|
||||
CACHE[hash] = true;
|
||||
fsExtra.ensureFileSync(CACHE_PATH);
|
||||
fsExtra.writeJSONSync(CACHE_PATH, CACHE);
|
||||
}
|
||||
|
||||
describe("Spell Checking", function () {
|
||||
it("Spellcheck CONTRIBUTING.md", function (done) {
|
||||
var readme = path.join(rootURI, "CONTRIBUTING.md");
|
||||
|
||||
fs.readFile(readme, function (err, data) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var text = data.toString();
|
||||
var hash = md5(text);
|
||||
|
||||
// skip this test if we know we have done the
|
||||
// exact same test with positive results before
|
||||
if (CACHE[hash]) {
|
||||
expect(CACHE[hash]).to.be.equal(true);
|
||||
return done();
|
||||
}
|
||||
|
||||
teacher.check(text, function(err, data) {
|
||||
expect(err).to.be.equal(null);
|
||||
|
||||
var results = data || [];
|
||||
results = results.filter(function(result) {
|
||||
if (NEW_WORDS.indexOf(result.string.toLowerCase()) != -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ignore anything that starts with a dash
|
||||
if (result.string[0] == "-") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(new RegExp(result.string)).test(text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
|
||||
if (results.length > 0) {
|
||||
console.log(results);
|
||||
}
|
||||
else {
|
||||
addCacheHash(hash);
|
||||
}
|
||||
|
||||
expect(results.length).to.be.equal(0);
|
||||
|
||||
setTimeout(done, 500);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("Spellcheck README.md", function (done) {
|
||||
var readme = path.join(rootURI, "README.md");
|
||||
|
||||
fs.readFile(readme, function (err, data) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var text = data.toString();
|
||||
var hash = md5(text);
|
||||
|
||||
// skip this test if we know we have done the
|
||||
// exact same test with positive results before
|
||||
if (CACHE[hash]) {
|
||||
expect(CACHE[hash]).to.be.equal(true);
|
||||
return done();
|
||||
}
|
||||
|
||||
teacher.check(text, function(err, data) {
|
||||
expect(err).to.be.equal(null);
|
||||
|
||||
var results = data || [];
|
||||
results = results.filter(function(result) {
|
||||
if (NEW_WORDS.indexOf(result.string.toLowerCase()) != -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ignore anything that starts with a dash
|
||||
if (result.string[0] == "-") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ignore anything that we don't find in the original text,
|
||||
// for some reason "bootstrap.js" becomes "bootstrapjs".
|
||||
if (!(new RegExp(result.string)).test(text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
|
||||
if (results.length > 0) {
|
||||
console.log(results);
|
||||
}
|
||||
else {
|
||||
addCacheHash(hash);
|
||||
}
|
||||
|
||||
expect(results.length).to.be.equal(0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,45 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var utils = require("./utils");
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
var jpm = utils.run;
|
||||
var readParam = utils.readParam;
|
||||
|
||||
var examplesPath = path.join(__dirname, "..", "..", "examples");
|
||||
|
||||
var binary = process.env.JPM_FIREFOX_BINARY || "nightly";
|
||||
var filterPattern = readParam("filter");
|
||||
|
||||
describe("jpm test sdk examples", function () {
|
||||
fs.readdirSync(examplesPath)
|
||||
.filter(fileFilter.bind(null, examplesPath))
|
||||
.forEach(function (file) {
|
||||
it(file, function (done) {
|
||||
var addonPath = path.join(examplesPath, file);
|
||||
process.chdir(addonPath);
|
||||
|
||||
var options = { cwd: addonPath, env: { JPM_FIREFOX_BINARY: binary }};
|
||||
if (process.env.DISPLAY) {
|
||||
options.env.DISPLAY = process.env.DISPLAY;
|
||||
}
|
||||
|
||||
jpm("test", options).then(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function fileFilter(root, file) {
|
||||
var matcher = filterPattern && new RegExp(filterPattern);
|
||||
if (/^(reading-data)/.test(file)) {
|
||||
return false;
|
||||
}
|
||||
if (matcher && !matcher.test(file)) {
|
||||
return false;
|
||||
}
|
||||
var stat = fs.statSync(path.join(root, file))
|
||||
return (stat && stat.isDirectory());
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var fs = require("fs");
|
||||
var Promise = require("promise");
|
||||
var chai = require("chai");
|
||||
var expect = chai.expect;
|
||||
var normalizeBinary = require("fx-runner/lib/utils").normalizeBinary;
|
||||
|
||||
//var firefox_binary = process.env["JPM_FIREFOX_BINARY"] || normalizeBinary("nightly");
|
||||
|
||||
describe("Checking Firefox binary", function () {
|
||||
|
||||
it("using matching fx-runner version with jpm", function () {
|
||||
var sdkPackageJSON = require("../../package.json");
|
||||
var jpmPackageINI = require("jpm/package.json");
|
||||
expect(sdkPackageJSON.devDependencies["fx-runner"]).to.be.equal(jpmPackageINI.dependencies["fx-runner"]);
|
||||
});
|
||||
|
||||
it("exists", function (done) {
|
||||
var useEnvVar = new Promise(function(resolve) {
|
||||
resolve(process.env["JPM_FIREFOX_BINARY"]);
|
||||
});
|
||||
|
||||
var firefox_binary = process.env["JPM_FIREFOX_BINARY"] ? useEnvVar : normalizeBinary("nightly");
|
||||
firefox_binary.then(function(path) {
|
||||
expect(path).to.be.ok;
|
||||
fs.exists(path, function (exists) {
|
||||
expect(exists).to.be.ok;
|
||||
done();
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,68 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
var Promise = require("promise");
|
||||
var chai = require("chai");
|
||||
var expect = chai.expect;
|
||||
var ini = require("./update-ini");
|
||||
|
||||
var addonINI = path.resolve("./test/addons/jetpack-addon.ini");
|
||||
var packageINI = path.resolve("./test/jetpack-package.ini");
|
||||
|
||||
describe("Checking ini files", function () {
|
||||
|
||||
it("Check test/addons/jetpack-addon.ini", function (done) {
|
||||
|
||||
fs.readFile(addonINI, function (err, data) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
// filter comments
|
||||
var text = data.toString().split("\n").filter(function(line) {
|
||||
return !/^\s*#/.test(line);
|
||||
}).join("\n");
|
||||
var expected = "";
|
||||
|
||||
ini.makeAddonIniContent()
|
||||
.then(function(contents) {
|
||||
expected = contents;
|
||||
|
||||
setTimeout(function end() {
|
||||
expect(text.trim()).to.be.equal(expected.trim());
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it("Check test/jetpack-package.ini", function (done) {
|
||||
|
||||
fs.readFile(packageINI, function (err, data) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
// filter comments
|
||||
var text = data.toString().split("\n").filter(function(line) {
|
||||
return !/^\s*#/.test(line);
|
||||
}).join("\n");
|
||||
var expected = "";
|
||||
|
||||
ini.makePackageIniContent()
|
||||
.then(function(contents) {
|
||||
expected = contents;
|
||||
|
||||
setTimeout(function end() {
|
||||
expect(text.trim()).to.be.equal(expected.trim());
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var utils = require("./utils");
|
||||
var readParam = utils.readParam;
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
var jpm = utils.run;
|
||||
var sdk = path.join(__dirname, "..", "..");
|
||||
var binary = process.env.JPM_FIREFOX_BINARY || "nightly";
|
||||
|
||||
var filterPattern = readParam("filter");
|
||||
|
||||
describe("jpm test sdk modules", function () {
|
||||
it("SDK Modules", function (done) {
|
||||
process.chdir(sdk);
|
||||
|
||||
var options = { cwd: sdk, env: { JPM_FIREFOX_BINARY: binary } };
|
||||
if (process.env.DISPLAY) {
|
||||
options.env.DISPLAY = process.env.DISPLAY;
|
||||
}
|
||||
options.filter = filterPattern;
|
||||
|
||||
jpm("test", options, process).then(done);
|
||||
});
|
||||
});
|
||||
@@ -1,141 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var path = require("path");
|
||||
var cp = require("child_process");
|
||||
var fs = require("fs");
|
||||
var Promise = require("promise");
|
||||
var parser = require("ini-parser");
|
||||
|
||||
var addonINI = path.resolve("./test/addons/jetpack-addon.ini");
|
||||
var addonsDir = path.resolve("./test/addons/");
|
||||
var packageINI = path.resolve("./test/jetpack-package.ini");
|
||||
var packageDir = path.resolve("./test/");
|
||||
var packageIgnorables = [ "addons", "preferences" ];
|
||||
var packageSupportFiles = [
|
||||
"fixtures.js",
|
||||
"test-context-menu.html",
|
||||
"util.js"
|
||||
]
|
||||
|
||||
function updateAddonINI() {
|
||||
return new Promise(function(resolve) {
|
||||
console.log("Start updating " + addonINI);
|
||||
|
||||
makeAddonIniContent().
|
||||
then(function(contents) {
|
||||
fs.writeFileSync(addonINI, contents, { encoding: "utf8" });
|
||||
console.log("Done updating " + addonINI);
|
||||
resolve();
|
||||
});
|
||||
})
|
||||
}
|
||||
exports.updateAddonINI = updateAddonINI;
|
||||
|
||||
function makeAddonIniContent() {
|
||||
return new Promise(function(resolve) {
|
||||
var data = parser.parse(fs.readFileSync(addonINI, { encoding: "utf8" }).toString());
|
||||
var result = {};
|
||||
|
||||
fs.readdir(addonsDir, function(err, files) {
|
||||
// get a list of folders
|
||||
var folders = files.filter(function(file) {
|
||||
return fs.statSync(path.resolve(addonsDir, file)).isDirectory();
|
||||
}).sort();
|
||||
|
||||
// copy any related data from the existing ini
|
||||
folders.forEach(function(folder) {
|
||||
var oldData = data[folder + ".xpi"];
|
||||
result[folder] = oldData ? oldData : {};
|
||||
});
|
||||
|
||||
// build a new ini file
|
||||
var contents = [];
|
||||
Object.keys(result).sort().forEach(function(key) {
|
||||
contents.push("[" + key + ".xpi]");
|
||||
Object.keys(result[key]).forEach(function(dataKey) {
|
||||
contents.push(dataKey + " = " + result[key][dataKey]);
|
||||
});
|
||||
});
|
||||
contents = contents.join("\n") + "\n";
|
||||
|
||||
return resolve(contents);
|
||||
});
|
||||
});
|
||||
}
|
||||
exports.makeAddonIniContent = makeAddonIniContent;
|
||||
|
||||
function makePackageIniContent() {
|
||||
return new Promise(function(resolve) {
|
||||
var data = parser.parse(fs.readFileSync(packageINI, { encoding: "utf8" }).toString());
|
||||
var result = {};
|
||||
|
||||
fs.readdir(packageDir, function(err, files) {
|
||||
// get a list of folders
|
||||
var folders = files.filter(function(file) {
|
||||
var ignore = (packageIgnorables.indexOf(file) >= 0);
|
||||
var isDir = fs.statSync(path.resolve(packageDir, file)).isDirectory();
|
||||
return (isDir && !ignore);
|
||||
}).sort();
|
||||
|
||||
// get a list of "test-"" files
|
||||
var files = files.filter(function(file) {
|
||||
var ignore = !/^test\-.*\.js$/i.test(file);
|
||||
var isDir = fs.statSync(path.resolve(packageDir, file)).isDirectory();
|
||||
return (!isDir && !ignore);
|
||||
}).sort();
|
||||
|
||||
// get a list of the support files
|
||||
var support_files = packageSupportFiles.map(function(file) {
|
||||
return " " + file;
|
||||
});
|
||||
folders.forEach(function(folder) {
|
||||
support_files.push(" " + folder + "/**");
|
||||
});
|
||||
support_files = support_files.sort();
|
||||
|
||||
// copy any related data from the existing ini
|
||||
files.forEach(function(file) {
|
||||
var oldData = data[file];
|
||||
result[file] = oldData ? oldData : {};
|
||||
});
|
||||
|
||||
// build a new ini file
|
||||
var contents = [
|
||||
"[DEFAULT]",
|
||||
"support-files ="
|
||||
];
|
||||
support_files.forEach(function(support_file) {
|
||||
contents.push(support_file);
|
||||
});
|
||||
contents.push("");
|
||||
|
||||
Object.keys(result).sort().forEach(function(key) {
|
||||
contents.push("[" + key + "]");
|
||||
Object.keys(result[key]).forEach(function(dataKey) {
|
||||
contents.push(dataKey + " = " + result[key][dataKey]);
|
||||
});
|
||||
});
|
||||
contents = contents.join("\n") + "\n";
|
||||
|
||||
return resolve(contents);
|
||||
});
|
||||
});
|
||||
}
|
||||
exports.makePackageIniContent = makePackageIniContent;
|
||||
|
||||
function updatePackageINI() {
|
||||
return new Promise(function(resolve) {
|
||||
console.log("Start updating " + packageINI);
|
||||
|
||||
makeAddonIniContent().
|
||||
then(function(contents) {
|
||||
fs.writeFileSync(packageINI, contents, { encoding: "utf8" });
|
||||
console.log("Done updating " + packageINI);
|
||||
resolve();
|
||||
});
|
||||
})
|
||||
}
|
||||
exports.updatePackageINI = updatePackageINI;
|
||||
@@ -1,104 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var _ = require("lodash");
|
||||
var path = require("path");
|
||||
var child_process = require("child_process");
|
||||
var jpm = require.resolve("../../node_modules/jpm/bin/jpm");
|
||||
var Promise = require("promise");
|
||||
var chai = require("chai");
|
||||
var expect = chai.expect;
|
||||
var assert = chai.assert;
|
||||
var DEFAULT_PROCESS = process;
|
||||
|
||||
var sdk = path.join(__dirname, "..", "..");
|
||||
var prefsPath = path.join(sdk, "test", "preferences", "test-preferences.js");
|
||||
var e10sPrefsPath = path.join(sdk, "test", "preferences", "test-e10s-preferences.js");
|
||||
|
||||
var OUTPUT_FILTERS = [
|
||||
/[^\n\r]+WARNING\: NS_ENSURE_SUCCESS\(rv, rv\) failed[^\n]+\n\r?/
|
||||
];
|
||||
|
||||
var isDebug = (process.env["JPM_FX_DEBUG"] == "1");
|
||||
exports.isDebug = isDebug;
|
||||
|
||||
function spawn (cmd, options) {
|
||||
options = options || {};
|
||||
var env = _.extend({}, options.env, process.env);
|
||||
|
||||
if (isDebug) {
|
||||
env["MOZ_QUIET"] = 1;
|
||||
}
|
||||
|
||||
var e10s = options.e10s || false;
|
||||
|
||||
return child_process.spawn("node", [
|
||||
jpm, cmd, "-v", "--tbpl",
|
||||
"--prefs", e10s ? e10sPrefsPath : prefsPath,
|
||||
"-o", sdk,
|
||||
"-f", options.filter || ""
|
||||
], {
|
||||
cwd: options.cwd || tmpOutputDir,
|
||||
env: env
|
||||
});
|
||||
}
|
||||
exports.spawn = spawn;
|
||||
|
||||
function run (cmd, options, p) {
|
||||
return new Promise(function(resolve) {
|
||||
var output = [];
|
||||
|
||||
var proc = spawn(cmd, options);
|
||||
proc.stderr.pipe(process.stderr);
|
||||
proc.stdout.on("data", function (data) {
|
||||
for (var i = OUTPUT_FILTERS.length - 1; i >= 0; i--) {
|
||||
if (OUTPUT_FILTERS[i].test(data)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
output.push(data);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (p) {
|
||||
proc.stdout.pipe(p.stdout);
|
||||
}
|
||||
else if (!isDebug) {
|
||||
proc.stdout.pipe(DEFAULT_PROCESS.stdout);
|
||||
}
|
||||
else {
|
||||
proc.stdout.on("data", function (data) {
|
||||
data = (data || "") + "";
|
||||
if (/TEST-/.test(data)) {
|
||||
DEFAULT_PROCESS.stdout.write(data.replace(/[\s\n]+$/, "") + "\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
proc.on("close", function(code) {
|
||||
var out = output.join("");
|
||||
var buildDisplayed = /Build \d+/.test(out);
|
||||
var noTests = /No tests were run/.test(out);
|
||||
var hasSuccess = /All tests passed!/.test(out);
|
||||
var hasFailure = /There were test failures\.\.\./.test(out);
|
||||
if (noTests || hasFailure || !hasSuccess || code != 0) {
|
||||
DEFAULT_PROCESS.stdout.write(out);
|
||||
}
|
||||
expect(code).to.equal(hasFailure ? 1 : 0);
|
||||
expect(buildDisplayed).to.equal(true);
|
||||
expect(hasFailure).to.equal(false);
|
||||
expect(hasSuccess).to.equal(true);
|
||||
expect(noTests).to.equal(false);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
exports.run = run;
|
||||
|
||||
function readParam(name) {
|
||||
var index = process.argv.indexOf("--" + name)
|
||||
return index >= 0 && process.argv[index + 1]
|
||||
}
|
||||
exports.readParam = readParam;
|
||||
@@ -1,11 +0,0 @@
|
||||
addon-sdk
|
||||
github
|
||||
stackoverflow
|
||||
bugzilla
|
||||
irc
|
||||
jsantell
|
||||
mossop
|
||||
gozala
|
||||
zer0
|
||||
autonome
|
||||
0c0w3
|
||||
@@ -1,3 +0,0 @@
|
||||
# Actor REPL
|
||||
|
||||
Simple REPL for a Firefox debugging protocol.
|
||||
File diff suppressed because one or more lines are too long
@@ -1,264 +0,0 @@
|
||||
/* BASICS */
|
||||
|
||||
.CodeMirror {
|
||||
/* Set height, width, borders, and global font properties here */
|
||||
font-family: monospace;
|
||||
height: 300px;
|
||||
}
|
||||
.CodeMirror-scroll {
|
||||
/* Set scrolling behaviour here */
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* PADDING */
|
||||
|
||||
.CodeMirror-lines {
|
||||
padding: 4px 0; /* Vertical padding around content */
|
||||
}
|
||||
.CodeMirror pre {
|
||||
padding: 0 4px; /* Horizontal padding of content */
|
||||
}
|
||||
|
||||
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
background-color: white; /* The little square between H and V scrollbars */
|
||||
}
|
||||
|
||||
/* GUTTER */
|
||||
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid #ddd;
|
||||
background-color: #f7f7f7;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.CodeMirror-linenumbers {}
|
||||
.CodeMirror-linenumber {
|
||||
padding: 0 3px 0 5px;
|
||||
min-width: 20px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/* CURSOR */
|
||||
|
||||
.CodeMirror div.CodeMirror-cursor {
|
||||
border-left: 1px solid black;
|
||||
z-index: 3;
|
||||
}
|
||||
/* Shown when moving in bi-directional text */
|
||||
.CodeMirror div.CodeMirror-secondarycursor {
|
||||
border-left: 1px solid silver;
|
||||
}
|
||||
.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
|
||||
width: auto;
|
||||
border: 0;
|
||||
background: #7e7;
|
||||
z-index: 1;
|
||||
}
|
||||
/* Can style cursor different in overwrite (non-insert) mode */
|
||||
.CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
|
||||
|
||||
.cm-tab { display: inline-block; }
|
||||
|
||||
.CodeMirror-ruler {
|
||||
border-left: 1px solid #ccc;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* DEFAULT THEME */
|
||||
|
||||
.cm-s-default .cm-keyword {color: #708;}
|
||||
.cm-s-default .cm-atom {color: #219;}
|
||||
.cm-s-default .cm-number {color: #164;}
|
||||
.cm-s-default .cm-def {color: #00f;}
|
||||
.cm-s-default .cm-variable {color: black;}
|
||||
.cm-s-default .cm-variable-2 {color: #05a;}
|
||||
.cm-s-default .cm-variable-3 {color: #085;}
|
||||
.cm-s-default .cm-property {color: black;}
|
||||
.cm-s-default .cm-operator {color: black;}
|
||||
.cm-s-default .cm-comment {color: #a50;}
|
||||
.cm-s-default .cm-string {color: #a11;}
|
||||
.cm-s-default .cm-string-2 {color: #f50;}
|
||||
.cm-s-default .cm-meta {color: #555;}
|
||||
.cm-s-default .cm-qualifier {color: #555;}
|
||||
.cm-s-default .cm-builtin {color: #30a;}
|
||||
.cm-s-default .cm-bracket {color: #997;}
|
||||
.cm-s-default .cm-tag {color: #170;}
|
||||
.cm-s-default .cm-attribute {color: #00c;}
|
||||
.cm-s-default .cm-header {color: blue;}
|
||||
.cm-s-default .cm-quote {color: #090;}
|
||||
.cm-s-default .cm-hr {color: #999;}
|
||||
.cm-s-default .cm-link {color: #00c;}
|
||||
|
||||
.cm-negative {color: #d44;}
|
||||
.cm-positive {color: #292;}
|
||||
.cm-header, .cm-strong {font-weight: bold;}
|
||||
.cm-em {font-style: italic;}
|
||||
.cm-link {text-decoration: underline;}
|
||||
|
||||
.cm-s-default .cm-error {color: #f00;}
|
||||
.cm-invalidchar {color: #f00;}
|
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
.CodeMirror-activeline-background {background: #e8f2ff;}
|
||||
|
||||
/* STOP */
|
||||
|
||||
/* The rest of this file contains styles related to the mechanics of
|
||||
the editor. You probably shouldn't touch them. */
|
||||
|
||||
.CodeMirror {
|
||||
line-height: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
/* 30px is the magic margin used to hide the element's real scrollbars */
|
||||
/* See overflow: hidden in .CodeMirror */
|
||||
margin-bottom: -30px; margin-right: -30px;
|
||||
padding-bottom: 30px;
|
||||
height: 100%;
|
||||
outline: none; /* Prevent dragging from highlighting the element */
|
||||
position: relative;
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
.CodeMirror-sizer {
|
||||
position: relative;
|
||||
border-right: 30px solid transparent;
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||
before actuall scrolling happens, thus preventing shaking and
|
||||
flickering artifacts. */
|
||||
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
display: none;
|
||||
}
|
||||
.CodeMirror-vscrollbar {
|
||||
right: 0; top: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.CodeMirror-hscrollbar {
|
||||
bottom: 0; left: 0;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
.CodeMirror-scrollbar-filler {
|
||||
right: 0; bottom: 0;
|
||||
}
|
||||
.CodeMirror-gutter-filler {
|
||||
left: 0; bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
position: absolute; left: 0; top: 0;
|
||||
padding-bottom: 30px;
|
||||
z-index: 3;
|
||||
}
|
||||
.CodeMirror-gutter {
|
||||
white-space: normal;
|
||||
height: 100%;
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
padding-bottom: 30px;
|
||||
margin-bottom: -32px;
|
||||
display: inline-block;
|
||||
/* Hack to make IE7 behave */
|
||||
*zoom:1;
|
||||
*display:inline;
|
||||
}
|
||||
.CodeMirror-gutter-elt {
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.CodeMirror-lines {
|
||||
cursor: text;
|
||||
}
|
||||
.CodeMirror pre {
|
||||
/* Reset some styles that the rest of the page might have set */
|
||||
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||
border-width: 0;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
.CodeMirror-wrap pre {
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.CodeMirror-linebackground {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-linewidget {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.CodeMirror-widget {}
|
||||
|
||||
.CodeMirror-wrap .CodeMirror-scroll {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.CodeMirror-measure {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
.CodeMirror-measure pre { position: static; }
|
||||
|
||||
.CodeMirror div.CodeMirror-cursor {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
border-right: none;
|
||||
width: 0;
|
||||
}
|
||||
.CodeMirror-focused div.CodeMirror-cursor {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-selected { background: #d9d9d9; }
|
||||
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||
|
||||
.cm-searching {
|
||||
background: #ffa;
|
||||
background: rgba(255, 255, 0, .4);
|
||||
}
|
||||
|
||||
/* IE7 hack to prevent it from returning funny offsetTops on the spans */
|
||||
.CodeMirror span { *vertical-align: text-bottom; }
|
||||
|
||||
@media print {
|
||||
/* Hide the cursor when printing */
|
||||
.CodeMirror div.CodeMirror-cursor {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="./codemirror.css">
|
||||
<link rel="stylesheet" href="./main.css">
|
||||
<script src="./codemirror-compressed.js"></script>
|
||||
<views>
|
||||
<section class="task cm-s-default">
|
||||
<pre class="request "></pre>
|
||||
<pre class="response"><span class="one"></span><span class="two"></span><span class="three"></span></pre>
|
||||
</section>
|
||||
</views>
|
||||
</head>
|
||||
<body>
|
||||
<pre class="input"></pre>
|
||||
</body>
|
||||
<script>
|
||||
function debounce(fn, ms) {
|
||||
var id;
|
||||
return function(...args) {
|
||||
clearTimeout(id);
|
||||
id = setTimeout(fn, ms, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
function Try(fn) {
|
||||
return function(...args) {
|
||||
try { return fn(...args); }
|
||||
catch (error) { return null; }
|
||||
};
|
||||
}
|
||||
|
||||
var parse = Try(JSON.parse);
|
||||
|
||||
var CommandHistory = {
|
||||
init: function() {
|
||||
this._state = {};
|
||||
this._state.els = document.querySelectorAll("body > section.task > .request");
|
||||
this._state.idx = this._state.els.length;
|
||||
},
|
||||
get prev() {
|
||||
if (!!this._state.els && this._state.idx > 0) {
|
||||
this._state.idx--;
|
||||
return this._state.els[this._state.idx].textContent;
|
||||
}
|
||||
|
||||
return "";
|
||||
},
|
||||
get next() {
|
||||
if (!!this._state.els && this._state.idx < this._state.els.length-1) {
|
||||
this._state.idx++;
|
||||
return this._state.els[this._state.idx].textContent;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function cmdHistory(fn, editor) {
|
||||
editor.setValue(fn());
|
||||
document.querySelector(".input").scrollIntoView();
|
||||
}
|
||||
|
||||
var cmdHistoryNext = cmdHistory.bind(null, () => CommandHistory.next);
|
||||
var cmdHistoryBack = cmdHistory.bind(null, () => CommandHistory.prev);
|
||||
|
||||
function send(editor) {
|
||||
var input = editor.getWrapperElement().parentNode;
|
||||
var code = editor.getValue().trim();
|
||||
var packet = parse(code);
|
||||
if (packet) {
|
||||
var task = document.querySelector("views .task").cloneNode(true);
|
||||
var request = task.querySelector(".request");
|
||||
var response = task.querySelector(".response");
|
||||
|
||||
input.parentNode.insertBefore(task, input);
|
||||
|
||||
CodeMirror.runMode(JSON.stringify(packet, 2, 2),
|
||||
"application/json",
|
||||
request);
|
||||
response.classList.add("pending");
|
||||
|
||||
editor.setValue("");
|
||||
|
||||
document.querySelector(".input").scrollIntoView();
|
||||
|
||||
port.postMessage(packet);
|
||||
}
|
||||
}
|
||||
|
||||
var editor = CodeMirror(document.querySelector(".input"), {
|
||||
autofocus: true,
|
||||
mode: "application/json",
|
||||
matchBrackets: true,
|
||||
value: '{"to": "root", "type": "requestTypes"}',
|
||||
extraKeys: {"Cmd-Enter": send,
|
||||
"Ctrl-Enter": send,
|
||||
"Cmd-Down": cmdHistoryNext,
|
||||
"Ctrl-Down": cmdHistoryNext,
|
||||
"Cmd-Up": cmdHistoryBack,
|
||||
"Ctrl-Up": cmdHistoryBack}
|
||||
});
|
||||
editor.on("change", debounce(function(editor) {
|
||||
var input = editor.getWrapperElement().parentNode;
|
||||
if (parse(editor.getValue().trim())) {
|
||||
input.classList.remove("invalid");
|
||||
} else {
|
||||
input.classList.add("invalid");
|
||||
}
|
||||
}, 800));
|
||||
</script>
|
||||
<script>
|
||||
window.addEventListener("message", event => {
|
||||
window.port = event.ports[0];
|
||||
port.onmessage = onMessage;
|
||||
});
|
||||
|
||||
var onMessage = (event) => {
|
||||
var packet = event.data;
|
||||
var code = JSON.stringify(packet, 2, 2);
|
||||
|
||||
var input = document.querySelector(".input");
|
||||
var response = document.querySelector(".task .response.pending");
|
||||
|
||||
if (!response) {
|
||||
message = document.querySelector("views .task").cloneNode(true);
|
||||
response = message.querySelector(".response");
|
||||
response.classList.add("message");
|
||||
|
||||
input.parentNode.insertBefore(message, input);
|
||||
}
|
||||
|
||||
if (packet.error) {
|
||||
response.classList.add("error");
|
||||
}
|
||||
|
||||
CodeMirror.runMode(code, "application/json", response);
|
||||
response.classList.remove("pending");
|
||||
|
||||
document.querySelector(".input").scrollIntoView();
|
||||
|
||||
CommandHistory.init();
|
||||
};
|
||||
</script>
|
||||
</html>
|
||||
@@ -1,117 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
body
|
||||
{
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: white;
|
||||
}
|
||||
|
||||
|
||||
pre
|
||||
{
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
section
|
||||
{
|
||||
border-top: 1px solid rgba(150, 150, 150, 0.5);
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
height: auto;
|
||||
}
|
||||
.CodeMirror-scroll {
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.request,
|
||||
.response,
|
||||
.input
|
||||
{
|
||||
border-left: 5px solid;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.request:not(:empty),
|
||||
.response.pending
|
||||
{
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.input
|
||||
{
|
||||
padding-left: 6px;
|
||||
border-color: lightgreen;
|
||||
}
|
||||
.input.invalid
|
||||
{
|
||||
border-color: orange;
|
||||
}
|
||||
|
||||
.request
|
||||
{
|
||||
border-color: lightgrey;
|
||||
}
|
||||
|
||||
.response
|
||||
{
|
||||
border-color: grey;
|
||||
}
|
||||
.response.error
|
||||
{
|
||||
border-color: red;
|
||||
}
|
||||
|
||||
.response.message
|
||||
{
|
||||
border-color: lightblue;
|
||||
}
|
||||
|
||||
.response .one,
|
||||
.response .two,
|
||||
.response .three
|
||||
{
|
||||
width: 0;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.response.pending .one,
|
||||
.response.pending .two,
|
||||
.response.pending .three
|
||||
{
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: rgba(150, 150, 150, 0.5);
|
||||
|
||||
border-radius: 100%;
|
||||
display: inline-block;
|
||||
animation: bouncedelay 1.4s infinite ease-in-out;
|
||||
/* Prevent first frame from flickering when animation starts */
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
.response.pending .one
|
||||
{
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
.response.pending .two
|
||||
{
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
@keyframes bouncedelay {
|
||||
0%, 80%, 100% {
|
||||
transform: scale(0.0);
|
||||
} 40% {
|
||||
transform: scale(1.0);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.1 KiB |
@@ -1,37 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { Panel } = require("dev/panel");
|
||||
const { Tool } = require("dev/toolbox");
|
||||
const { Class } = require("sdk/core/heritage");
|
||||
|
||||
|
||||
const REPLPanel = Class({
|
||||
extends: Panel,
|
||||
label: "Actor REPL",
|
||||
tooltip: "Firefox debugging protocol REPL",
|
||||
icon: "./robot.png",
|
||||
url: "./index.html",
|
||||
setup: function({debuggee}) {
|
||||
this.debuggee = debuggee;
|
||||
},
|
||||
dispose: function() {
|
||||
this.debuggee = null;
|
||||
},
|
||||
onReady: function() {
|
||||
console.log("repl panel document is interactive");
|
||||
this.debuggee.start();
|
||||
this.postMessage("RDP", [this.debuggee]);
|
||||
},
|
||||
onLoad: function() {
|
||||
console.log("repl panel document is fully loaded");
|
||||
}
|
||||
});
|
||||
exports.REPLPanel = REPLPanel;
|
||||
|
||||
|
||||
const replTool = new Tool({
|
||||
panels: { repl: REPLPanel }
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "actor-repl",
|
||||
"id": "@actor-repl",
|
||||
"title": "Actor REPL",
|
||||
"description": "Actor REPL",
|
||||
"version": "0.0.1",
|
||||
"author": "Irakli Gozalishvili",
|
||||
"main": "./index.js",
|
||||
"license": "MPL-2.0"
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
exports.testMain = function(assert) {
|
||||
assert.pass("TODO: Write some tests.");
|
||||
};
|
||||
|
||||
require("sdk/test").run(exports);
|
||||
@@ -1,816 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
(function(exports) {
|
||||
"use strict";
|
||||
|
||||
|
||||
var describe = Object.getOwnPropertyDescriptor;
|
||||
var Class = fields => {
|
||||
var constructor = fields.constructor || function() {};
|
||||
var ancestor = fields.extends || Object;
|
||||
|
||||
|
||||
|
||||
var descriptor = {};
|
||||
for (var key of Object.keys(fields))
|
||||
descriptor[key] = describe(fields, key);
|
||||
|
||||
var prototype = Object.create(ancestor.prototype, descriptor);
|
||||
|
||||
constructor.prototype = prototype;
|
||||
prototype.constructor = constructor;
|
||||
|
||||
return constructor;
|
||||
};
|
||||
|
||||
|
||||
var bus = function Bus() {
|
||||
var parser = new DOMParser();
|
||||
return parser.parseFromString("<EventTarget/>", "application/xml").documentElement;
|
||||
}();
|
||||
|
||||
var GUID = new WeakMap();
|
||||
GUID.id = 0;
|
||||
var guid = x => GUID.get(x);
|
||||
var setGUID = x => {
|
||||
GUID.set(x, ++ GUID.id);
|
||||
};
|
||||
|
||||
var Emitter = Class({
|
||||
extends: EventTarget,
|
||||
constructor: function() {
|
||||
this.setupEmitter();
|
||||
},
|
||||
setupEmitter: function() {
|
||||
setGUID(this);
|
||||
},
|
||||
addEventListener: function(type, listener, capture) {
|
||||
bus.addEventListener(type + "@" + guid(this),
|
||||
listener, capture);
|
||||
},
|
||||
removeEventListener: function(type, listener, capture) {
|
||||
bus.removeEventListener(type + "@" + guid(this),
|
||||
listener, capture);
|
||||
}
|
||||
});
|
||||
|
||||
function dispatch(target, type, data) {
|
||||
var event = new MessageEvent(type + "@" + guid(target), {
|
||||
bubbles: true,
|
||||
cancelable: false,
|
||||
data: data
|
||||
});
|
||||
bus.dispatchEvent(event);
|
||||
}
|
||||
|
||||
var supervisedWorkers = new WeakMap();
|
||||
var supervised = supervisor => {
|
||||
if (!supervisedWorkers.has(supervisor)) {
|
||||
supervisedWorkers.set(supervisor, new Map());
|
||||
supervisor.connection.addActorPool(supervisor);
|
||||
}
|
||||
return supervisedWorkers.get(supervisor);
|
||||
};
|
||||
|
||||
var Supervisor = Class({
|
||||
extends: Emitter,
|
||||
constructor: function(...params) {
|
||||
this.setupEmitter(...params);
|
||||
this.setupSupervisor(...params);
|
||||
},
|
||||
Supervisor: function(connection) {
|
||||
this.connection = connection;
|
||||
},
|
||||
/**
|
||||
* Return the parent pool for this client.
|
||||
*/
|
||||
supervisor: function() {
|
||||
return this.connection.poolFor(this.actorID);
|
||||
},
|
||||
/**
|
||||
* Override this if you want actors returned by this actor
|
||||
* to belong to a different actor by default.
|
||||
*/
|
||||
marshallPool: function() { return this; },
|
||||
/**
|
||||
* Add an actor as a child of this pool.
|
||||
*/
|
||||
supervise: function(actor) {
|
||||
if (!actor.actorID)
|
||||
actor.actorID = this.connection.allocID(actor.actorPrefix ||
|
||||
actor.typeName);
|
||||
|
||||
supervised(this).set(actor.actorID, actor);
|
||||
return actor;
|
||||
},
|
||||
/**
|
||||
* Remove an actor as a child of this pool.
|
||||
*/
|
||||
abandon: function(actor) {
|
||||
supervised(this).delete(actor.actorID);
|
||||
},
|
||||
// true if the given actor ID exists in the pool.
|
||||
has: function(actorID) {
|
||||
return supervised(this).has(actorID);
|
||||
},
|
||||
// Same as actor, should update debugger connection to use 'actor'
|
||||
// and then remove this.
|
||||
get: function(actorID) {
|
||||
return supervised(this).get(actorID);
|
||||
},
|
||||
actor: function(actorID) {
|
||||
return supervised(this).get(actorID);
|
||||
},
|
||||
isEmpty: function() {
|
||||
return supervised(this).size === 0;
|
||||
},
|
||||
/**
|
||||
* For getting along with the debugger server pools, should be removable
|
||||
* eventually.
|
||||
*/
|
||||
cleanup: function() {
|
||||
this.destroy();
|
||||
},
|
||||
destroy: function() {
|
||||
var supervisor = this.supervisor();
|
||||
if (supervisor)
|
||||
supervisor.abandon(this);
|
||||
|
||||
for (var actor of supervised(this).values()) {
|
||||
if (actor !== this) {
|
||||
var destroy = actor.destroy;
|
||||
// Disconnect destroy while we're destroying in case of (misbehaving)
|
||||
// circular ownership.
|
||||
if (destroy) {
|
||||
actor.destroy = null;
|
||||
destroy.call(actor);
|
||||
actor.destroy = destroy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.connection.removeActorPool(this);
|
||||
supervised(this).clear();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
var mailbox = new WeakMap();
|
||||
var clientRequests = new WeakMap();
|
||||
|
||||
var inbox = client => mailbox.get(client).inbox;
|
||||
var outbox = client => mailbox.get(client).outbox;
|
||||
var requests = client => clientRequests.get(client);
|
||||
|
||||
|
||||
var Receiver = Class({
|
||||
receive: function(packet) {
|
||||
if (packet.error)
|
||||
this.reject(packet.error);
|
||||
else
|
||||
this.resolve(this.read(packet));
|
||||
}
|
||||
});
|
||||
|
||||
var Connection = Class({
|
||||
constructor: function() {
|
||||
// Queue of the outgoing messages.
|
||||
this.outbox = [];
|
||||
// Map of pending requests.
|
||||
this.pending = new Map();
|
||||
this.pools = new Set();
|
||||
},
|
||||
isConnected: function() {
|
||||
return !!this.port
|
||||
},
|
||||
connect: function(port) {
|
||||
this.port = port;
|
||||
port.addEventListener("message", this);
|
||||
port.start();
|
||||
|
||||
this.flush();
|
||||
},
|
||||
addPool: function(pool) {
|
||||
this.pools.add(pool);
|
||||
},
|
||||
removePool: function(pool) {
|
||||
this.pools.delete(pool);
|
||||
},
|
||||
poolFor: function(id) {
|
||||
for (let pool of this.pools.values()) {
|
||||
if (pool.has(id))
|
||||
return pool;
|
||||
}
|
||||
},
|
||||
get: function(id) {
|
||||
var pool = this.poolFor(id);
|
||||
return pool && pool.get(id);
|
||||
},
|
||||
disconnect: function() {
|
||||
this.port.stop();
|
||||
this.port = null;
|
||||
for (var request of this.pending.values()) {
|
||||
request.catch(new Error("Connection closed"));
|
||||
}
|
||||
this.pending.clear();
|
||||
|
||||
var requests = this.outbox.splice(0);
|
||||
for (var request of request) {
|
||||
requests.catch(new Error("Connection closed"));
|
||||
}
|
||||
},
|
||||
handleEvent: function(event) {
|
||||
this.receive(event.data);
|
||||
},
|
||||
flush: function() {
|
||||
if (this.isConnected()) {
|
||||
for (var request of this.outbox) {
|
||||
if (!this.pending.has(request.to)) {
|
||||
this.outbox.splice(this.outbox.indexOf(request), 1);
|
||||
this.pending.set(request.to, request);
|
||||
this.send(request.packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
send: function(packet) {
|
||||
this.port.postMessage(packet);
|
||||
},
|
||||
request: function(packet) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
this.outbox.push({
|
||||
to: packet.to,
|
||||
packet: packet,
|
||||
receive: resolve,
|
||||
catch: reject
|
||||
});
|
||||
this.flush();
|
||||
});
|
||||
},
|
||||
receive: function(packet) {
|
||||
var { from, type, why } = packet;
|
||||
var receiver = this.pending.get(from);
|
||||
if (!receiver) {
|
||||
console.warn("Unable to handle received packet", data);
|
||||
} else {
|
||||
this.pending.delete(from);
|
||||
if (packet.error)
|
||||
receiver.catch(packet.error);
|
||||
else
|
||||
receiver.receive(packet);
|
||||
}
|
||||
this.flush();
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Base class for client-side actor fronts.
|
||||
*/
|
||||
var Client = Class({
|
||||
extends: Supervisor,
|
||||
constructor: function(from=null, detail=null, connection=null) {
|
||||
this.Client(from, detail, connection);
|
||||
},
|
||||
Client: function(form, detail, connection) {
|
||||
this.Supervisor(connection);
|
||||
|
||||
if (form) {
|
||||
this.actorID = form.actor;
|
||||
this.from(form, detail);
|
||||
}
|
||||
},
|
||||
connect: function(port) {
|
||||
this.connection = new Connection(port);
|
||||
},
|
||||
actorID: null,
|
||||
actor: function() {
|
||||
return this.actorID;
|
||||
},
|
||||
/**
|
||||
* Update the actor from its representation.
|
||||
* Subclasses should override this.
|
||||
*/
|
||||
form: function(form) {
|
||||
},
|
||||
/**
|
||||
* Method is invokeid when packet received constitutes an
|
||||
* event. By default such packets are demarshalled and
|
||||
* dispatched on the client instance.
|
||||
*/
|
||||
dispatch: function(packet) {
|
||||
},
|
||||
/**
|
||||
* Method is invoked when packet is returned in response to
|
||||
* a request. By default respond delivers response to a first
|
||||
* request in a queue.
|
||||
*/
|
||||
read: function(input) {
|
||||
throw new TypeError("Subclass must implement read method");
|
||||
},
|
||||
write: function(input) {
|
||||
throw new TypeError("Subclass must implement write method");
|
||||
},
|
||||
respond: function(packet) {
|
||||
var [resolve, reject] = requests(this).shift();
|
||||
if (packet.error)
|
||||
reject(packet.error);
|
||||
else
|
||||
resolve(this.read(packet));
|
||||
},
|
||||
receive: function(packet) {
|
||||
if (this.isEventPacket(packet)) {
|
||||
this.dispatch(packet);
|
||||
}
|
||||
else if (requests(this).length) {
|
||||
this.respond(packet);
|
||||
}
|
||||
else {
|
||||
this.catch(packet);
|
||||
}
|
||||
},
|
||||
send: function(packet) {
|
||||
Promise.cast(packet.to || this.actor()).then(id => {
|
||||
packet.to = id;
|
||||
this.connection.send(packet);
|
||||
})
|
||||
},
|
||||
request: function(packet) {
|
||||
return this.connection.request(packet);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var Destructor = method => {
|
||||
return function(...args) {
|
||||
return method.apply(this, args).then(result => {
|
||||
this.destroy();
|
||||
return result;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
var Profiled = (method, id) => {
|
||||
return function(...args) {
|
||||
var start = new Date();
|
||||
return method.apply(this, args).then(result => {
|
||||
var end = new Date();
|
||||
this.telemetry.add(id, +end - start);
|
||||
return result;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
var Method = (request, response) => {
|
||||
return response ? new BidirectionalMethod(request, response) :
|
||||
new UnidirecationalMethod(request);
|
||||
};
|
||||
|
||||
var UnidirecationalMethod = request => {
|
||||
return function(...args) {
|
||||
var packet = request.write(args, this);
|
||||
this.connection.send(packet);
|
||||
return Promise.resolve(void(0));
|
||||
};
|
||||
};
|
||||
|
||||
var BidirectionalMethod = (request, response) => {
|
||||
return function(...args) {
|
||||
var packet = request.write(args, this);
|
||||
return this.connection.request(packet).then(packet => {
|
||||
return response.read(packet, this);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Client.from = ({category, typeName, methods, events}) => {
|
||||
var proto = {
|
||||
constructor: function(...args) {
|
||||
this.Client(...args);
|
||||
},
|
||||
extends: Client,
|
||||
name: typeName
|
||||
};
|
||||
|
||||
methods.forEach(({telemetry, request, response, name, oneway, release}) => {
|
||||
var [reader, writer] = oneway ? [, new Request(request)] :
|
||||
[new Request(request), new Response(response)];
|
||||
var method = new Method(request, response);
|
||||
var profiler = telemetry ? new Profiler(method) : method;
|
||||
var destructor = release ? new Destructor(profiler) : profiler;
|
||||
proto[name] = destructor;
|
||||
});
|
||||
|
||||
return Class(proto);
|
||||
};
|
||||
|
||||
|
||||
var defineType = (client, descriptor) => {
|
||||
var type = void(0)
|
||||
if (typeof(descriptor) === "string") {
|
||||
if (name.indexOf(":") > 0)
|
||||
type = makeCompoundType(descriptor);
|
||||
else if (name.indexOf("#") > 0)
|
||||
type = new ActorDetail(descriptor);
|
||||
else if (client.specification[descriptor])
|
||||
type = makeCategoryType(client.specification[descriptor]);
|
||||
} else {
|
||||
type = makeCategoryType(descriptor);
|
||||
}
|
||||
|
||||
if (type)
|
||||
client.types.set(type.name, type);
|
||||
else
|
||||
throw TypeError("Invalid type: " + descriptor);
|
||||
};
|
||||
|
||||
|
||||
var makeCompoundType = name => {
|
||||
var index = name.indexOf(":");
|
||||
var [baseType, subType] = [name.slice(0, index), parts.slice(1)];
|
||||
return baseType === "array" ? new ArrayOf(subType) :
|
||||
baseType === "nullable" ? new Maybe(subType) :
|
||||
null;
|
||||
};
|
||||
|
||||
var makeCategoryType = (descriptor) => {
|
||||
var { category } = descriptor;
|
||||
return category === "dict" ? new Dictionary(descriptor) :
|
||||
category === "actor" ? new Actor(descriptor) :
|
||||
null;
|
||||
};
|
||||
|
||||
|
||||
var typeFor = (client, type="primitive") => {
|
||||
if (!client.types.has(type))
|
||||
defineType(client, type);
|
||||
|
||||
return client.types.get(type);
|
||||
};
|
||||
|
||||
|
||||
var Client = Class({
|
||||
constructor: function() {
|
||||
},
|
||||
setupTypes: function(specification) {
|
||||
this.specification = specification;
|
||||
this.types = new Map();
|
||||
},
|
||||
read: function(input, type) {
|
||||
return typeFor(this, type).read(input, this);
|
||||
},
|
||||
write: function(input, type) {
|
||||
return typeFor(this, type).write(input, this);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var Type = Class({
|
||||
get name() {
|
||||
return this.category ? this.category + ":" + this.type :
|
||||
this.type;
|
||||
},
|
||||
read: function(input, client) {
|
||||
throw new TypeError("`Type` subclass must implement `read`");
|
||||
},
|
||||
write: function(input, client) {
|
||||
throw new TypeError("`Type` subclass must implement `write`");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var Primitve = Class({
|
||||
extends: Type,
|
||||
constuctor: function(type) {
|
||||
this.type = type;
|
||||
},
|
||||
read: function(input, client) {
|
||||
return input;
|
||||
},
|
||||
write: function(input, client) {
|
||||
return input;
|
||||
}
|
||||
});
|
||||
|
||||
var Maybe = Class({
|
||||
extends: Type,
|
||||
category: "nullable",
|
||||
constructor: function(type) {
|
||||
this.type = type;
|
||||
},
|
||||
read: function(input, client) {
|
||||
return input === null ? null :
|
||||
input === void(0) ? void(0) :
|
||||
client.read(input, this.type);
|
||||
},
|
||||
write: function(input, client) {
|
||||
return input === null ? null :
|
||||
input === void(0) ? void(0) :
|
||||
client.write(input, this.type);
|
||||
}
|
||||
});
|
||||
|
||||
var ArrayOf = Class({
|
||||
extends: Type,
|
||||
category: "array",
|
||||
constructor: function(type) {
|
||||
this.type = type;
|
||||
},
|
||||
read: function(input, client) {
|
||||
return input.map($ => client.read($, this.type));
|
||||
},
|
||||
write: function(input, client) {
|
||||
return input.map($ => client.write($, this.type));
|
||||
}
|
||||
});
|
||||
|
||||
var Dictionary = Class({
|
||||
exteds: Type,
|
||||
category: "dict",
|
||||
get name() { return this.type; },
|
||||
constructor: function({typeName, specializations}) {
|
||||
this.type = typeName;
|
||||
this.types = specifications;
|
||||
},
|
||||
read: function(input, client) {
|
||||
var output = {};
|
||||
for (var key in input) {
|
||||
output[key] = client.read(input[key], this.types[key]);
|
||||
}
|
||||
return output;
|
||||
},
|
||||
write: function(input, client) {
|
||||
var output = {};
|
||||
for (var key in input) {
|
||||
output[key] = client.write(value, this.types[key]);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
});
|
||||
|
||||
var Actor = Class({
|
||||
exteds: Type,
|
||||
category: "actor",
|
||||
get name() { return this.type; },
|
||||
constructor: function({typeName}) {
|
||||
this.type = typeName;
|
||||
},
|
||||
read: function(input, client, detail) {
|
||||
var id = value.actor;
|
||||
var actor = void(0);
|
||||
if (client.connection.has(id)) {
|
||||
return client.connection.get(id).form(input, detail, client);
|
||||
} else {
|
||||
actor = Client.from(detail, client);
|
||||
actor.actorID = id;
|
||||
client.supervise(actor);
|
||||
}
|
||||
},
|
||||
write: function(input, client, detail) {
|
||||
if (input instanceof Actor) {
|
||||
if (!input.actorID) {
|
||||
client.supervise(input);
|
||||
}
|
||||
return input.from(detail);
|
||||
}
|
||||
return input.actorID;
|
||||
}
|
||||
});
|
||||
|
||||
var Root = Client.from({
|
||||
"category": "actor",
|
||||
"typeName": "root",
|
||||
"methods": [
|
||||
{"name": "listTabs",
|
||||
"request": {},
|
||||
"response": {
|
||||
}
|
||||
},
|
||||
{"name": "listAddons"
|
||||
},
|
||||
{"name": "echo",
|
||||
|
||||
},
|
||||
{"name": "protocolDescription",
|
||||
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
var ActorDetail = Class({
|
||||
extends: Actor,
|
||||
constructor: function(name, actor, detail) {
|
||||
this.detail = detail;
|
||||
this.actor = actor;
|
||||
},
|
||||
read: function(input, client) {
|
||||
this.actor.read(input, client, this.detail);
|
||||
},
|
||||
write: function(input, client) {
|
||||
this.actor.write(input, client, this.detail);
|
||||
}
|
||||
});
|
||||
|
||||
var registeredLifetimes = new Map();
|
||||
var LifeTime = Class({
|
||||
extends: Type,
|
||||
category: "lifetime",
|
||||
constructor: function(lifetime, type) {
|
||||
this.name = lifetime + ":" + type.name;
|
||||
this.field = registeredLifetimes.get(lifetime);
|
||||
},
|
||||
read: function(input, client) {
|
||||
return this.type.read(input, client[this.field]);
|
||||
},
|
||||
write: function(input, client) {
|
||||
return this.type.write(input, client[this.field]);
|
||||
}
|
||||
});
|
||||
|
||||
var primitive = new Primitve("primitive");
|
||||
var string = new Primitve("string");
|
||||
var number = new Primitve("number");
|
||||
var boolean = new Primitve("boolean");
|
||||
var json = new Primitve("json");
|
||||
var array = new Primitve("array");
|
||||
|
||||
|
||||
var TypedValue = Class({
|
||||
extends: Type,
|
||||
constructor: function(name, type) {
|
||||
this.TypedValue(name, type);
|
||||
},
|
||||
TypedValue: function(name, type) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
},
|
||||
read: function(input, client) {
|
||||
return this.client.read(input, this.type);
|
||||
},
|
||||
write: function(input, client) {
|
||||
return this.client.write(input, this.type);
|
||||
}
|
||||
});
|
||||
|
||||
var Return = Class({
|
||||
extends: TypedValue,
|
||||
constructor: function(type) {
|
||||
this.type = type
|
||||
}
|
||||
});
|
||||
|
||||
var Argument = Class({
|
||||
extends: TypedValue,
|
||||
constructor: function(...args) {
|
||||
this.Argument(...args);
|
||||
},
|
||||
Argument: function(index, type) {
|
||||
this.index = index;
|
||||
this.TypedValue("argument[" + index + "]", type);
|
||||
},
|
||||
read: function(input, client, target) {
|
||||
return target[this.index] = client.read(input, this.type);
|
||||
}
|
||||
});
|
||||
|
||||
var Option = Class({
|
||||
extends: Argument,
|
||||
constructor: function(...args) {
|
||||
return this.Argument(...args);
|
||||
},
|
||||
read: function(input, client, target, name) {
|
||||
var param = target[this.index] || (target[this.index] = {});
|
||||
param[name] = input === void(0) ? input : client.read(input, this.type);
|
||||
},
|
||||
write: function(input, client, name) {
|
||||
var value = input && input[name];
|
||||
return value === void(0) ? value : client.write(value, this.type);
|
||||
}
|
||||
});
|
||||
|
||||
var Request = Class({
|
||||
extends: Type,
|
||||
constructor: function(template={}) {
|
||||
this.type = template.type;
|
||||
this.template = template;
|
||||
this.params = findPlaceholders(template, Argument);
|
||||
},
|
||||
read: function(packet, client) {
|
||||
var args = [];
|
||||
for (var param of this.params) {
|
||||
var {placeholder, path} = param;
|
||||
var name = path[path.length - 1];
|
||||
placeholder.read(getPath(packet, path), client, args, name);
|
||||
// TODO:
|
||||
// args[placeholder.index] = placeholder.read(query(packet, path), client);
|
||||
}
|
||||
return args;
|
||||
},
|
||||
write: function(input, client) {
|
||||
return JSON.parse(JSON.stringify(this.template, (key, value) => {
|
||||
return value instanceof Argument ? value.write(input[value.index],
|
||||
client, key) :
|
||||
value;
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
var Response = Class({
|
||||
extends: Type,
|
||||
constructor: function(template={}) {
|
||||
this.template = template;
|
||||
var [x] = findPlaceholders(template, Return);
|
||||
var {placeholder, path} = x;
|
||||
this.return = placeholder;
|
||||
this.path = path;
|
||||
},
|
||||
read: function(packet, client) {
|
||||
var value = query(packet, this.path);
|
||||
return this.return.read(value, client);
|
||||
},
|
||||
write: function(input, client) {
|
||||
return JSON.parse(JSON.stringify(this.template, (key, value) => {
|
||||
return value instanceof Return ? value.write(input) :
|
||||
input
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
// Returns array of values for the given object.
|
||||
var values = object => Object.keys(object).map(key => object[key]);
|
||||
// Returns [key, value] pairs for the given object.
|
||||
var pairs = object => Object.keys(object).map(key => [key, object[key]]);
|
||||
// Queries an object for the field nested with in it.
|
||||
var query = (object, path) => path.reduce((object, entry) => object && object[entry],
|
||||
object);
|
||||
|
||||
|
||||
var Root = Client.from({
|
||||
"category": "actor",
|
||||
"typeName": "root",
|
||||
"methods": [
|
||||
{
|
||||
"name": "echo",
|
||||
"request": {
|
||||
"string": { "_arg": 0, "type": "string" }
|
||||
},
|
||||
"response": {
|
||||
"string": { "_retval": "string" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "listTabs",
|
||||
"request": {},
|
||||
"response": { "_retval": "tablist" }
|
||||
},
|
||||
{
|
||||
"name": "actorDescriptions",
|
||||
"request": {},
|
||||
"response": { "_retval": "json" }
|
||||
}
|
||||
],
|
||||
"events": {
|
||||
"tabListChanged": {}
|
||||
}
|
||||
});
|
||||
|
||||
var Tab = Client.from({
|
||||
"category": "dict",
|
||||
"typeName": "tab",
|
||||
"specifications": {
|
||||
"title": "string",
|
||||
"url": "string",
|
||||
"outerWindowID": "number",
|
||||
"console": "console",
|
||||
"inspectorActor": "inspector",
|
||||
"callWatcherActor": "call-watcher",
|
||||
"canvasActor": "canvas",
|
||||
"webglActor": "webgl",
|
||||
"webaudioActor": "webaudio",
|
||||
"styleSheetsActor": "stylesheets",
|
||||
"styleEditorActor": "styleeditor",
|
||||
"storageActor": "storage",
|
||||
"gcliActor": "gcli",
|
||||
"memoryActor": "memory",
|
||||
"eventLoopLag": "eventLoopLag",
|
||||
|
||||
"trace": "trace", // missing
|
||||
}
|
||||
});
|
||||
|
||||
var tablist = Client.from({
|
||||
"category": "dict",
|
||||
"typeName": "tablist",
|
||||
"specializations": {
|
||||
"selected": "number",
|
||||
"tabs": "array:tab"
|
||||
}
|
||||
});
|
||||
|
||||
})(this);
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<html>
|
||||
<head>
|
||||
<script src="resource://sdk/dev/volcan.js"></script>
|
||||
<script src="./task.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
<script>
|
||||
const wait = (target, type, capture) => new Promise((resolve, reject) => {
|
||||
const listener = event => {
|
||||
target.removeEventListener(type, listener, capture);
|
||||
resolve(event);
|
||||
};
|
||||
target.addEventListener(type, listener, capture);
|
||||
});
|
||||
|
||||
const display = message =>
|
||||
document.body.innerHTML += message + "<br/>";
|
||||
|
||||
Task.spawn(function*() {
|
||||
var event = yield wait(window, "message");
|
||||
var port = event.ports[0];
|
||||
|
||||
display("Port received");
|
||||
var root = yield volcan.connect(port);
|
||||
|
||||
display("Connected to a debugger");
|
||||
|
||||
var message = yield root.echo("hello")
|
||||
|
||||
display("Received echo for: " + message);
|
||||
|
||||
var list = yield root.listTabs();
|
||||
|
||||
display("You have " + list.tabs.length + " open tabs");
|
||||
|
||||
var activeTab = list.tabs[list.selected];
|
||||
|
||||
display("Your active tab url is: " + activeTab.url);
|
||||
|
||||
var sheets = yield activeTab.styleSheetsActor.getStyleSheets();
|
||||
|
||||
display("Page in active tab has " + sheets.length + " stylesheets");
|
||||
|
||||
});
|
||||
</script>
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.7 KiB |
@@ -1,28 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
(function(exports) {
|
||||
"use strict";
|
||||
|
||||
const spawn = (task, ...args) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const routine = task(...args);
|
||||
const raise = error => routine.throw(error);
|
||||
const step = data => {
|
||||
const { done, value } = routine.next(data);
|
||||
if (done)
|
||||
resolve(value);
|
||||
else
|
||||
Promise.resolve(value).then(step, raise);
|
||||
}
|
||||
step();
|
||||
} catch(error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.spawn = spawn;
|
||||
|
||||
})(Task = {});
|
||||
@@ -1,33 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { Panel } = require("dev/panel");
|
||||
const { Tool } = require("dev/toolbox");
|
||||
const { Class } = require("sdk/core/heritage");
|
||||
|
||||
|
||||
const LadybugPanel = Class({
|
||||
extends: Panel,
|
||||
label: "Ladybug",
|
||||
tooltip: "Debug client example",
|
||||
icon: "./plugin.png",
|
||||
url: "./index.html",
|
||||
setup: function({debuggee}) {
|
||||
this.debuggee = debuggee;
|
||||
},
|
||||
dispose: function() {
|
||||
delete this.debuggee;
|
||||
},
|
||||
onReady: function() {
|
||||
this.debuggee.start();
|
||||
this.postMessage("RDP", [this.debuggee]);
|
||||
},
|
||||
});
|
||||
exports.LadybugPanel = LadybugPanel;
|
||||
|
||||
|
||||
const ladybug = new Tool({
|
||||
panels: { ladybug: LadybugPanel }
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "debug-client",
|
||||
"id": "@debug-client",
|
||||
"title": "Debug client",
|
||||
"description": "Example debug client",
|
||||
"version": "0.0.1",
|
||||
"author": "Irakli Gozalishvili",
|
||||
"main": "./index.js",
|
||||
"license": "MPL-2.0"
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
exports.testMain = function(assert) {
|
||||
assert.pass("TODO: Write some tests.");
|
||||
};
|
||||
|
||||
require("sdk/test").run(exports);
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.7 KiB |
@@ -1,7 +0,0 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<html><body>
|
||||
<h1>Hello World</h1>
|
||||
</body></html>
|
||||
@@ -1,53 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var self = require("sdk/self");
|
||||
var { Panel } = require("sdk/panel");
|
||||
var { ToggleButton } = require("sdk/ui");
|
||||
|
||||
function replaceMom(html) {
|
||||
return html.replace("World", "Mom");
|
||||
}
|
||||
exports.replaceMom = replaceMom;
|
||||
|
||||
exports.main = function(options, callbacks) {
|
||||
console.log("My ID is " + self.id);
|
||||
|
||||
// Load the sample HTML into a string.
|
||||
var helloHTML = self.data.load("sample.html");
|
||||
|
||||
// Let's now modify it...
|
||||
helloHTML = replaceMom(helloHTML);
|
||||
|
||||
// ... and then create a panel that displays it.
|
||||
var myPanel = Panel({
|
||||
contentURL: "data:text/html," + helloHTML,
|
||||
onHide: handleHide
|
||||
});
|
||||
|
||||
// Create a widget that displays the image. We'll attach the panel to it.
|
||||
// When you click the widget, the panel will pop up.
|
||||
var button = ToggleButton({
|
||||
id: "test-widget",
|
||||
label: "Mom",
|
||||
icon: './mom.png',
|
||||
onChange: handleChange
|
||||
});
|
||||
|
||||
// If you run cfx with --static-args='{"quitWhenDone":true}' this program
|
||||
// will automatically quit Firefox when it's done.
|
||||
if (options.staticArgs.quitWhenDone)
|
||||
callbacks.quit();
|
||||
}
|
||||
|
||||
function handleChange(state) {
|
||||
if (state.checked) {
|
||||
myPanel.show({ position: button });
|
||||
}
|
||||
}
|
||||
|
||||
function handleHide() {
|
||||
button.state('window', { checked: false });
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "reading-data",
|
||||
"description": "A demonstration of reading bundled data.",
|
||||
"keywords": [],
|
||||
"author": "Brian Warner",
|
||||
"contributors": [],
|
||||
"license": "MPL-2.0",
|
||||
"id": "reading-data-example@jetpack.mozillalabs.com"
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var m = require("main");
|
||||
var self = require("sdk/self");
|
||||
|
||||
exports.testReplace = function(test) {
|
||||
var input = "Hello World";
|
||||
var output = m.replaceMom(input);
|
||||
test.assertEqual(output, "Hello Mom");
|
||||
var callbacks = { quit: function() {} };
|
||||
|
||||
// Make sure it doesn't crash...
|
||||
m.main({ staticArgs: {} }, callbacks);
|
||||
};
|
||||
|
||||
exports.testID = function(test) {
|
||||
// The ID is randomly generated during tests, so we cannot compare it against
|
||||
// anything in particular. Just assert that it is not empty.
|
||||
test.assert(self.id.length > 0);
|
||||
test.assertEqual(self.data.url("sample.html"),
|
||||
"resource://reading-data-example-at-jetpack-dot-mozillalabs-dot-com/reading-data/data/sample.html");
|
||||
};
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,9 +0,0 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,7 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#devtools-theme-box {
|
||||
background-color: red !important;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { Tool } = require("dev/toolbox");
|
||||
const { Class } = require("sdk/core/heritage");
|
||||
const { onEnable, onDisable } = require("dev/theme/hooks");
|
||||
const { Theme, LightTheme } = require("dev/theme");
|
||||
|
||||
/**
|
||||
* This object represents a new theme registered within the Toolbox.
|
||||
* You can activate it by clicking on "My Light Theme" theme option
|
||||
* in the Options panel.
|
||||
* Note that the new theme derives styles from built-in Light theme.
|
||||
*/
|
||||
const MyTheme = Theme({
|
||||
name: "mytheme",
|
||||
label: "My Light Theme",
|
||||
styles: [LightTheme, "./theme.css"],
|
||||
|
||||
onEnable: function(window, oldTheme) {
|
||||
console.log("myTheme.onEnable; method override " +
|
||||
window.location.href);
|
||||
},
|
||||
onDisable: function(window, newTheme) {
|
||||
console.log("myTheme.onDisable; method override " +
|
||||
window.location.href);
|
||||
},
|
||||
});
|
||||
|
||||
// Registration
|
||||
|
||||
const mytheme = new Tool({
|
||||
name: "My Tool",
|
||||
themes: { mytheme: MyTheme }
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "theme",
|
||||
"title": "theme",
|
||||
"id": "theme@jetpack",
|
||||
"description": "How to create new theme for devtools",
|
||||
"author": "Jan Odvarko",
|
||||
"license": "MPL-2.0",
|
||||
"version": "0.1.0",
|
||||
"main": "lib/main"
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
exports.testMain = function(assert) {
|
||||
assert.pass("TODO: Write some tests.");
|
||||
};
|
||||
|
||||
require("sdk/test").run(exports);
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,21 +0,0 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<html>
|
||||
<body>
|
||||
<button id=post>post!</button>
|
||||
</body>
|
||||
<script>
|
||||
window.addEventListener("message", event => {
|
||||
console.log("Document message", event, event.data, event.source !== window && event.source === window.parent);
|
||||
});
|
||||
window.addEventListener("click", event => {
|
||||
if (event.target.id === "post") {
|
||||
console.log("click!")
|
||||
window.parent.postMessage("ping!", "*");
|
||||
}
|
||||
});
|
||||
console.log(window.parent === window)
|
||||
</script>
|
||||
</html>
|
||||
@@ -1,48 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { Toolbar } = require("sdk/ui/toolbar");
|
||||
const { Frame } = require("sdk/ui/frame");
|
||||
const { ActionButton } = require("sdk/ui/button/action");
|
||||
|
||||
var button = new ActionButton({
|
||||
id: "button",
|
||||
label: "send!",
|
||||
icon: "./favicon.ico",
|
||||
onClick: () => {
|
||||
frame.postMessage({
|
||||
hello: "content"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var frame = new Frame({
|
||||
url: "./index.html",
|
||||
onAttach: () => {
|
||||
console.log("frame was attached");
|
||||
},
|
||||
onReady: () => {
|
||||
console.log("frame document was loaded");
|
||||
},
|
||||
onLoad: () => {
|
||||
console.log("frame load complete");
|
||||
},
|
||||
onMessage: (event) => {
|
||||
console.log("got message from frame content", event);
|
||||
if (event.data === "ping!")
|
||||
event.source.postMessage("pong!", event.source.origin);
|
||||
}
|
||||
});
|
||||
var toolbar = new Toolbar({
|
||||
items: [frame],
|
||||
title: "Addon Demo",
|
||||
hidden: false,
|
||||
onShow: () => {
|
||||
console.log("toolbar was shown");
|
||||
},
|
||||
onHide: () => {
|
||||
console.log("toolbar was hidden");
|
||||
}
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "toolbar-api",
|
||||
"title": "Toolbar API",
|
||||
"main": "./lib/main.js",
|
||||
"description": "a toolbar api example",
|
||||
"author": "",
|
||||
"license": "MPL-2.0",
|
||||
"version": "0.1.1",
|
||||
"engines": {
|
||||
"firefox": ">=27.0 <=30.0"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
exports.testMain = function(assert) {
|
||||
assert.pass("TODO: Write some tests.");
|
||||
};
|
||||
|
||||
require("sdk/test").run(exports);
|
||||
@@ -1,39 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var data = require('sdk/self').data;
|
||||
var tabs = require('sdk/tabs');
|
||||
var { notify } = require('sdk/notifications');
|
||||
var { ActionButton, ToggleButton } = require('sdk/ui');
|
||||
|
||||
var icon = 'chrome://mozapps/skin/extensions/extensionGeneric.svg';
|
||||
exports.icon = icon;
|
||||
|
||||
// your basic action button
|
||||
var action = ActionButton({
|
||||
id: 'test-action-button',
|
||||
label: 'Action Button',
|
||||
icon: icon,
|
||||
onClick: function (state) {
|
||||
notify({
|
||||
title: "Action!",
|
||||
text: "This notification was triggered from an action button!",
|
||||
});
|
||||
}
|
||||
});
|
||||
exports.actionButton = action;
|
||||
|
||||
var toggle = ToggleButton({
|
||||
id: 'test-toggle-button',
|
||||
label: 'Toggle Button',
|
||||
icon: icon,
|
||||
onClick: function (state) {
|
||||
notify({
|
||||
title: "Toggled!",
|
||||
text: "The current state of the button is " + state.checked,
|
||||
});
|
||||
}
|
||||
});
|
||||
exports.toggleButton = toggle;
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "ui-button-apis",
|
||||
"title": "Australis Button API Examples",
|
||||
"id": "ui-button-apis@mozilla.org",
|
||||
"description": "A Button API example",
|
||||
"author": "jeff@canuckistani.ca (Jeff Griffiths | @canuckistani)",
|
||||
"license": "MPL-2.0",
|
||||
"version": "0.1.1",
|
||||
"main": "./lib/main.js"
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
try {
|
||||
// CFX use case..
|
||||
var { actionButton, toggleButton, icon } = require("main");
|
||||
}
|
||||
catch (e) {
|
||||
// JPM use case..
|
||||
let mainURI = "../lib/main";
|
||||
var { actionButton, toggleButton, icon } = require(mainURI);
|
||||
}
|
||||
var self = require("sdk/self");
|
||||
|
||||
exports.testActionButton = function(assert) {
|
||||
assert.equal(actionButton.id, "test-action-button", "action button id is correct");
|
||||
assert.equal(actionButton.label, "Action Button", "action button label is correct");
|
||||
assert.equal(actionButton.icon, icon, "action button icon is correct");
|
||||
}
|
||||
|
||||
exports.testToggleButton = function(assert) {
|
||||
assert.equal(toggleButton.id, "test-toggle-button", "toggle button id is correct");
|
||||
assert.equal(toggleButton.label, "Toggle Button", "toggle button label is correct");
|
||||
assert.equal(toggleButton.icon, icon, "toggle button icon is correct");
|
||||
}
|
||||
|
||||
require("sdk/test").run(exports);
|
||||
@@ -1,44 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var gulp = require('gulp');
|
||||
var patch = require("./bin/node-scripts/apply-patch");
|
||||
var ini = require("./bin/node-scripts/update-ini");
|
||||
|
||||
gulp.task('test', function(done) {
|
||||
require("./bin/jpm-test").run().then(done);
|
||||
});
|
||||
|
||||
gulp.task('test:addons', function(done) {
|
||||
require("./bin/jpm-test").run("addons").catch(console.error).then(done);
|
||||
});
|
||||
|
||||
gulp.task('test:docs', function(done) {
|
||||
require("./bin/jpm-test").run("docs").catch(console.error).then(done);
|
||||
});
|
||||
|
||||
gulp.task('test:examples', function(done) {
|
||||
require("./bin/jpm-test").run("examples").catch(console.error).then(done);
|
||||
});
|
||||
|
||||
gulp.task('test:modules', function(done) {
|
||||
require("./bin/jpm-test").run("modules").catch(console.error).then(done);
|
||||
});
|
||||
|
||||
gulp.task('test:ini', function(done) {
|
||||
require("./bin/jpm-test").run("ini").catch(console.error).then(done);
|
||||
});
|
||||
|
||||
gulp.task('test:firefox-bin', function(done) {
|
||||
require("./bin/jpm-test").run("firefox-bin").catch(console.error).then(done);
|
||||
});
|
||||
|
||||
gulp.task('patch:clean', function(done) {
|
||||
patch.clean().catch(console.error).then(done);
|
||||
});
|
||||
|
||||
gulp.task('patch:apply', function(done) {
|
||||
patch.apply().catch(console.error).then(done);
|
||||
});
|
||||
@@ -1,71 +0,0 @@
|
||||
{
|
||||
"api-utils": "sdk/deprecated/api-utils",
|
||||
"base64": "sdk/base64",
|
||||
"content": "sdk/content/content",
|
||||
"deprecate": "sdk/util/deprecate",
|
||||
"event/core": "sdk/event/core",
|
||||
"events": "sdk/deprecated/events",
|
||||
"functional": "sdk/core/functional",
|
||||
"l10n/core": "sdk/l10n/json/core",
|
||||
"l10n/html": "sdk/l10n/html",
|
||||
"l10n/loader": "sdk/l10n/loader",
|
||||
"l10n/locale": "sdk/l10n/locale",
|
||||
"l10n/prefs": "sdk/l10n/prefs",
|
||||
"list": "sdk/util/list",
|
||||
"loader": "sdk/loader/loader",
|
||||
"namespace": "sdk/core/namespace",
|
||||
"preferences-service": "sdk/preferences/service",
|
||||
"promise": "sdk/core/promise",
|
||||
"system": "sdk/system",
|
||||
"system/events": "sdk/system/events-shimmed",
|
||||
"tabs/tab": "sdk/tabs/tab",
|
||||
"tabs/utils": "sdk/tabs/utils",
|
||||
"timer": "sdk/timers",
|
||||
"traits": "sdk/deprecated/traits",
|
||||
"unload": "sdk/system/unload",
|
||||
"window-utils": "sdk/deprecated/window-utils",
|
||||
"window/utils": "sdk/window/utils",
|
||||
"windows/dom": "sdk/windows/dom",
|
||||
"windows/loader": "sdk/windows/loader",
|
||||
"xul-app": "sdk/system/xul-app",
|
||||
"url": "sdk/url",
|
||||
"traceback": "sdk/console/traceback",
|
||||
"xhr": "sdk/net/xhr",
|
||||
"match-pattern": "sdk/util/match-pattern",
|
||||
"file": "sdk/io/file",
|
||||
"runtime": "sdk/system/runtime",
|
||||
"xpcom": "sdk/platform/xpcom",
|
||||
"querystring": "sdk/querystring",
|
||||
"text-streams": "sdk/io/text-streams",
|
||||
"app-strings": "sdk/deprecated/app-strings",
|
||||
"environment": "sdk/system/environment",
|
||||
"keyboard/utils": "sdk/keyboard/utils",
|
||||
"dom/events": "sdk/dom/events-shimmed",
|
||||
"utils/data": "sdk/io/data",
|
||||
"test/assert": "sdk/test/assert",
|
||||
"hidden-frame": "sdk/frame/hidden-frame",
|
||||
"collection": "sdk/util/collection",
|
||||
"array": "sdk/util/array",
|
||||
"clipboard": "sdk/clipboard",
|
||||
"context-menu": "sdk/context-menu",
|
||||
"hotkeys": "sdk/hotkeys",
|
||||
"indexed-db": "sdk/indexed-db",
|
||||
"l10n": "sdk/l10n",
|
||||
"notifications": "sdk/notifications",
|
||||
"page-mod": "sdk/page-mod",
|
||||
"page-worker": "sdk/page-worker",
|
||||
"panel": "sdk/panel",
|
||||
"passwords": "sdk/passwords",
|
||||
"private-browsing": "sdk/private-browsing",
|
||||
"request": "sdk/request",
|
||||
"selection": "sdk/selection",
|
||||
"self": "sdk/self",
|
||||
"simple-prefs": "sdk/simple-prefs",
|
||||
"simple-storage": "sdk/simple-storage",
|
||||
"tabs": "sdk/tabs",
|
||||
"timers": "sdk/timers",
|
||||
"windows": "sdk/windows",
|
||||
"harness": "sdk/test/harness",
|
||||
"run-tests": "sdk/test/runner",
|
||||
"test": "sdk/test"
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"name": "addon-sdk",
|
||||
"description": "Add-on development made easy.",
|
||||
"keywords": [
|
||||
"javascript", "engine", "addon", "extension",
|
||||
"xulrunner", "firefox", "browser"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"unpack": true,
|
||||
"scripts": {
|
||||
"test": "gulp test"
|
||||
},
|
||||
"homepage": "https://github.com/mozilla/addon-sdk",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/mozilla/addon-sdk.git"
|
||||
},
|
||||
"version": "0.1.18",
|
||||
"main": "./lib/index.js",
|
||||
"loader": "lib/sdk/loader/cuddlefish.js",
|
||||
"devDependencies": {
|
||||
"async": "0.9.0",
|
||||
"chai": "2.1.1",
|
||||
"fs-extra": "0.18.2",
|
||||
"fx-runner": "0.0.7",
|
||||
"glob": "4.4.2",
|
||||
"gulp": "3.8.11",
|
||||
"ini-parser": "0.0.2",
|
||||
"jpm": "0.0.29",
|
||||
"lodash": "3.3.1",
|
||||
"mocha": "2.1.0",
|
||||
"patch-editor": "0.0.1",
|
||||
"promise": "6.1.0",
|
||||
"rimraf": "2.3.1",
|
||||
"teacher": "0.0.1",
|
||||
"unzip": "0.1.11",
|
||||
"xmldom": "0.1.19"
|
||||
}
|
||||
}
|
||||
@@ -1,959 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import optparse
|
||||
import time
|
||||
|
||||
from copy import copy
|
||||
import simplejson as json
|
||||
from cuddlefish import packaging
|
||||
from cuddlefish._version import get_versions
|
||||
|
||||
MOZRUNNER_BIN_NOT_FOUND = 'Mozrunner could not locate your binary'
|
||||
MOZRUNNER_BIN_NOT_FOUND_HELP = """
|
||||
I can't find the application binary in any of its default locations
|
||||
on your system. Please specify one using the -b/--binary option.
|
||||
"""
|
||||
|
||||
UPDATE_RDF_FILENAME = "%s.update.rdf"
|
||||
XPI_FILENAME = "%s.xpi"
|
||||
|
||||
usage = """
|
||||
%prog [options] command [command-specific options]
|
||||
|
||||
Supported Commands:
|
||||
init - create a sample addon in an empty directory
|
||||
test - run tests
|
||||
run - run program
|
||||
xpi - generate an xpi
|
||||
|
||||
Internal Commands:
|
||||
testcfx - test the cfx tool
|
||||
testex - test all example code
|
||||
testpkgs - test all installed packages
|
||||
testall - test whole environment
|
||||
|
||||
Experimental and internal commands and options are not supported and may be
|
||||
changed or removed in the future.
|
||||
"""
|
||||
|
||||
global_options = [
|
||||
(("-v", "--verbose",), dict(dest="verbose",
|
||||
help="enable lots of output",
|
||||
action="store_true",
|
||||
default=False)),
|
||||
]
|
||||
|
||||
parser_groups = (
|
||||
("Supported Command-Specific Options", [
|
||||
(("", "--update-url",), dict(dest="update_url",
|
||||
help="update URL in install.rdf",
|
||||
metavar=None,
|
||||
default=None,
|
||||
cmds=['xpi'])),
|
||||
(("", "--update-link",), dict(dest="update_link",
|
||||
help="generate update.rdf",
|
||||
metavar=None,
|
||||
default=None,
|
||||
cmds=['xpi'])),
|
||||
(("-p", "--profiledir",), dict(dest="profiledir",
|
||||
help=("profile directory to pass to "
|
||||
"app"),
|
||||
metavar=None,
|
||||
default=None,
|
||||
cmds=['test', 'run', 'testex',
|
||||
'testpkgs', 'testall'])),
|
||||
(("-b", "--binary",), dict(dest="binary",
|
||||
help="path to app binary",
|
||||
metavar=None,
|
||||
default=None,
|
||||
cmds=['test', 'run', 'testex', 'testpkgs',
|
||||
'testall'])),
|
||||
(("", "--binary-args",), dict(dest="cmdargs",
|
||||
help=("additional arguments passed to the "
|
||||
"binary"),
|
||||
metavar=None,
|
||||
default=None,
|
||||
cmds=['run', 'test'])),
|
||||
(("", "--dependencies",), dict(dest="dep_tests",
|
||||
help="include tests for all deps",
|
||||
action="store_true",
|
||||
default=False,
|
||||
cmds=['test', 'testex', 'testpkgs',
|
||||
'testall'])),
|
||||
(("", "--times",), dict(dest="iterations",
|
||||
type="int",
|
||||
help="number of times to run tests",
|
||||
default=1,
|
||||
cmds=['test', 'testex', 'testpkgs',
|
||||
'testall'])),
|
||||
(("-f", "--filter",), dict(dest="filter",
|
||||
help=("only run tests whose filenames "
|
||||
"match FILENAME and optionally "
|
||||
"match TESTNAME, both regexps"),
|
||||
metavar="FILENAME[:TESTNAME]",
|
||||
default='',
|
||||
cmds=['test', 'testex', 'testaddons', 'testpkgs',
|
||||
'testall'])),
|
||||
(("-g", "--use-config",), dict(dest="config",
|
||||
help="use named config from local.json",
|
||||
metavar=None,
|
||||
default="default",
|
||||
cmds=['test', 'run', 'xpi', 'testex',
|
||||
'testpkgs', 'testall'])),
|
||||
(("", "--templatedir",), dict(dest="templatedir",
|
||||
help="XULRunner app/ext. template",
|
||||
metavar=None,
|
||||
default=None,
|
||||
cmds=['run', 'xpi'])),
|
||||
(("", "--package-path",), dict(dest="packagepath", action="append",
|
||||
help="extra directories for package search",
|
||||
metavar=None,
|
||||
default=[],
|
||||
cmds=['run', 'xpi', 'test'])),
|
||||
(("", "--extra-packages",), dict(dest="extra_packages",
|
||||
help=("extra packages to include, "
|
||||
"comma-separated. Default is "
|
||||
"'addon-sdk'."),
|
||||
metavar=None,
|
||||
default="addon-sdk",
|
||||
cmds=['run', 'xpi', 'test', 'testex',
|
||||
'testpkgs', 'testall',
|
||||
'testcfx'])),
|
||||
(("", "--pkgdir",), dict(dest="pkgdir",
|
||||
help=("package dir containing "
|
||||
"package.json; default is "
|
||||
"current directory"),
|
||||
metavar=None,
|
||||
default=None,
|
||||
cmds=['run', 'xpi', 'test'])),
|
||||
(("", "--static-args",), dict(dest="static_args",
|
||||
help="extra harness options as JSON",
|
||||
type="json",
|
||||
metavar=None,
|
||||
default="{}",
|
||||
cmds=['run', 'xpi'])),
|
||||
(("", "--parseable",), dict(dest="parseable",
|
||||
help="display test output in a parseable format",
|
||||
action="store_true",
|
||||
default=False,
|
||||
cmds=['run', 'test', 'testex', 'testpkgs',
|
||||
'testaddons', 'testall'])),
|
||||
]
|
||||
),
|
||||
|
||||
("Experimental Command-Specific Options", [
|
||||
(("-a", "--app",), dict(dest="app",
|
||||
help=("app to run: firefox (default), fennec, "
|
||||
"fennec-on-device, xulrunner or "
|
||||
"thunderbird"),
|
||||
metavar=None,
|
||||
type="choice",
|
||||
choices=["firefox",
|
||||
"fennec-on-device", "thunderbird",
|
||||
"xulrunner"],
|
||||
default="firefox",
|
||||
cmds=['test', 'run', 'testex', 'testpkgs',
|
||||
'testall'])),
|
||||
(("-o", "--overload-modules",), dict(dest="overload_modules",
|
||||
help=("Overload JS modules integrated into"
|
||||
" Firefox with the one from your SDK"
|
||||
" repository"),
|
||||
action="store_true",
|
||||
default=False,
|
||||
cmds=['run', 'test', 'testex', 'testpkgs',
|
||||
'testall'])),
|
||||
(("", "--strip-sdk",), dict(dest="bundle_sdk",
|
||||
help=("Do not ship SDK modules in the xpi"),
|
||||
action="store_false",
|
||||
default=False,
|
||||
cmds=['run', 'test', 'testex', 'testpkgs',
|
||||
'testall', 'xpi'])),
|
||||
(("", "--force-use-bundled-sdk",), dict(dest="force_use_bundled_sdk",
|
||||
help=("When --strip-sdk isn't passed, "
|
||||
"force using sdk modules shipped in "
|
||||
"the xpi instead of firefox ones"),
|
||||
action="store_true",
|
||||
default=False,
|
||||
cmds=['run', 'test', 'testex', 'testpkgs',
|
||||
'testall', 'xpi'])),
|
||||
(("", "--no-run",), dict(dest="no_run",
|
||||
help=("Instead of launching the "
|
||||
"application, just show the command "
|
||||
"for doing so. Use this to launch "
|
||||
"the application in a debugger like "
|
||||
"gdb."),
|
||||
action="store_true",
|
||||
default=False,
|
||||
cmds=['run', 'test'])),
|
||||
(("", "--no-quit",), dict(dest="no_quit",
|
||||
help=("Prevent from killing Firefox when"
|
||||
"running tests"),
|
||||
action="store_true",
|
||||
default=False,
|
||||
cmds=['run', 'test'])),
|
||||
(("", "--no-strip-xpi",), dict(dest="no_strip_xpi",
|
||||
help="retain unused modules in XPI",
|
||||
action="store_true",
|
||||
default=False,
|
||||
cmds=['xpi'])),
|
||||
(("", "--force-mobile",), dict(dest="enable_mobile",
|
||||
help="Force compatibility with Firefox Mobile",
|
||||
action="store_true",
|
||||
default=False,
|
||||
cmds=['run', 'test', 'xpi', 'testall'])),
|
||||
(("", "--mobile-app",), dict(dest="mobile_app_name",
|
||||
help=("Name of your Android application to "
|
||||
"use. Possible values: 'firefox', "
|
||||
"'firefox_beta', 'fennec_aurora', "
|
||||
"'fennec' (for nightly)."),
|
||||
metavar=None,
|
||||
default=None,
|
||||
cmds=['run', 'test', 'testall'])),
|
||||
(("", "--harness-option",), dict(dest="extra_harness_option_args",
|
||||
help=("Extra properties added to "
|
||||
"harness-options.json"),
|
||||
action="append",
|
||||
metavar="KEY=VALUE",
|
||||
default=[],
|
||||
cmds=['xpi'])),
|
||||
(("", "--stop-on-error",), dict(dest="stopOnError",
|
||||
help="Stop running tests after the first failure",
|
||||
action="store_true",
|
||||
metavar=None,
|
||||
default=False,
|
||||
cmds=['test', 'testex', 'testpkgs'])),
|
||||
(("", "--check-memory",), dict(dest="check_memory",
|
||||
help="attempts to detect leaked compartments after a test run",
|
||||
action="store_true",
|
||||
default=False,
|
||||
cmds=['test', 'testpkgs', 'testaddons',
|
||||
'testall'])),
|
||||
(("", "--output-file",), dict(dest="output_file",
|
||||
help="Where to put the finished .xpi",
|
||||
default=None,
|
||||
cmds=['xpi'])),
|
||||
(("", "--abort-on-missing-module",), dict(dest="abort_on_missing",
|
||||
help="Abort if required module is missing",
|
||||
action="store_true",
|
||||
default=False,
|
||||
cmds=['test', 'run', 'xpi', 'testpkgs'])),
|
||||
(("", "--no-connections",), dict(dest="no_connections",
|
||||
help="disable/enable remote connections (on for cfx run only by default)",
|
||||
type="choice",
|
||||
choices=["on", "off", "default"],
|
||||
default="default",
|
||||
cmds=['test', 'run', 'testpkgs',
|
||||
'testall', 'testaddons', 'testex'])),
|
||||
]
|
||||
),
|
||||
|
||||
("Internal Command-Specific Options", [
|
||||
(("", "--addons",), dict(dest="addons",
|
||||
help=("paths of addons to install, "
|
||||
"comma-separated"),
|
||||
metavar=None,
|
||||
default=None,
|
||||
cmds=['test', 'run', 'testex', 'testpkgs',
|
||||
'testall'])),
|
||||
(("", "--test-runner-pkg",), dict(dest="test_runner_pkg",
|
||||
help=("name of package "
|
||||
"containing test runner "
|
||||
"program (default is "
|
||||
"test-harness)"),
|
||||
default="addon-sdk",
|
||||
cmds=['test', 'testex', 'testpkgs',
|
||||
'testall'])),
|
||||
# --keydir was removed in 1.0b5, but we keep it around in the options
|
||||
# parser to make life easier for frontends like FlightDeck which
|
||||
# might still pass it. It can go away once the frontends are updated.
|
||||
(("", "--keydir",), dict(dest="keydir",
|
||||
help=("obsolete, ignored"),
|
||||
metavar=None,
|
||||
default=None,
|
||||
cmds=['test', 'run', 'xpi', 'testex',
|
||||
'testpkgs', 'testall'])),
|
||||
(("", "--e10s",), dict(dest="enable_e10s",
|
||||
help="enable remote windows",
|
||||
action="store_true",
|
||||
default=False,
|
||||
cmds=['test', 'run', 'testex', 'testpkgs',
|
||||
'testaddons', 'testcfx', 'testall'])),
|
||||
(("", "--logfile",), dict(dest="logfile",
|
||||
help="log console output to file",
|
||||
metavar=None,
|
||||
default=None,
|
||||
cmds=['run', 'test', 'testex', 'testpkgs'])),
|
||||
# TODO: This should default to true once our memory debugging
|
||||
# issues are resolved; see bug 592774.
|
||||
(("", "--profile-memory",), dict(dest="profileMemory",
|
||||
help=("profile memory usage "
|
||||
"(default is false)"),
|
||||
type="int",
|
||||
action="store",
|
||||
default=0,
|
||||
cmds=['test', 'testex', 'testpkgs',
|
||||
'testall'])),
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
def find_parent_package(cur_dir):
|
||||
tail = True
|
||||
while tail:
|
||||
if os.path.exists(os.path.join(cur_dir, 'package.json')):
|
||||
return cur_dir
|
||||
cur_dir, tail = os.path.split(cur_dir)
|
||||
return None
|
||||
|
||||
def check_json(option, opt, value):
|
||||
# We return the parsed JSON here; see bug 610816 for background on why.
|
||||
try:
|
||||
return json.loads(value)
|
||||
except ValueError:
|
||||
raise optparse.OptionValueError("Option %s must be JSON." % opt)
|
||||
|
||||
class CfxOption(optparse.Option):
|
||||
TYPES = optparse.Option.TYPES + ('json',)
|
||||
TYPE_CHECKER = copy(optparse.Option.TYPE_CHECKER)
|
||||
TYPE_CHECKER['json'] = check_json
|
||||
|
||||
def parse_args(arguments, global_options, usage, version, parser_groups,
|
||||
defaults=None):
|
||||
parser = optparse.OptionParser(usage=usage.strip(), option_class=CfxOption,
|
||||
version=version)
|
||||
|
||||
def name_cmp(a, b):
|
||||
# a[0] = name sequence
|
||||
# a[0][0] = short name (possibly empty string)
|
||||
# a[0][1] = long name
|
||||
names = []
|
||||
for seq in (a, b):
|
||||
names.append(seq[0][0][1:] if seq[0][0] else seq[0][1][2:])
|
||||
return cmp(*names)
|
||||
|
||||
global_options.sort(name_cmp)
|
||||
for names, opts in global_options:
|
||||
parser.add_option(*names, **opts)
|
||||
|
||||
for group_name, options in parser_groups:
|
||||
group = optparse.OptionGroup(parser, group_name)
|
||||
options.sort(name_cmp)
|
||||
for names, opts in options:
|
||||
if 'cmds' in opts:
|
||||
cmds = opts['cmds']
|
||||
del opts['cmds']
|
||||
cmds.sort()
|
||||
if not 'help' in opts:
|
||||
opts['help'] = ""
|
||||
opts['help'] += " (%s)" % ", ".join(cmds)
|
||||
group.add_option(*names, **opts)
|
||||
parser.add_option_group(group)
|
||||
|
||||
if defaults:
|
||||
parser.set_defaults(**defaults)
|
||||
|
||||
(options, args) = parser.parse_args(args=arguments)
|
||||
|
||||
if not args:
|
||||
parser.print_help()
|
||||
parser.exit()
|
||||
|
||||
return (options, args)
|
||||
|
||||
# all tests emit progress messages to stderr, not stdout. (the mozrunner
|
||||
# console output goes to stderr and is hard to change, and
|
||||
# unittest.TextTestRunner prefers stderr, so we send everything else there
|
||||
# too, to keep all the messages in order)
|
||||
|
||||
def test_all(env_root, defaults):
|
||||
fail = False
|
||||
|
||||
starttime = time.time()
|
||||
|
||||
if not defaults['filter']:
|
||||
print >>sys.stderr, "Testing cfx..."
|
||||
sys.stderr.flush()
|
||||
result = test_cfx(env_root, defaults['verbose'])
|
||||
if result.failures or result.errors:
|
||||
fail = True
|
||||
|
||||
if not fail or not defaults.get("stopOnError"):
|
||||
print >>sys.stderr, "Testing all examples..."
|
||||
sys.stderr.flush()
|
||||
|
||||
try:
|
||||
test_all_examples(env_root, defaults)
|
||||
except SystemExit, e:
|
||||
fail = (e.code != 0) or fail
|
||||
|
||||
if not fail or not defaults.get("stopOnError"):
|
||||
print >>sys.stderr, "Testing all unit-test addons..."
|
||||
sys.stderr.flush()
|
||||
|
||||
try:
|
||||
test_all_testaddons(env_root, defaults)
|
||||
except SystemExit, e:
|
||||
fail = (e.code != 0) or fail
|
||||
|
||||
if not fail or not defaults.get("stopOnError"):
|
||||
print >>sys.stderr, "Testing all packages..."
|
||||
sys.stderr.flush()
|
||||
try:
|
||||
test_all_packages(env_root, defaults)
|
||||
except SystemExit, e:
|
||||
fail = (e.code != 0) or fail
|
||||
|
||||
print >>sys.stderr, "Total time for all tests: %f seconds" % (time.time() - starttime)
|
||||
|
||||
if fail:
|
||||
print >>sys.stderr, "Some tests were unsuccessful."
|
||||
sys.exit(1)
|
||||
print >>sys.stderr, "All tests were successful. Ship it!"
|
||||
sys.exit(0)
|
||||
|
||||
def test_cfx(env_root, verbose):
|
||||
import cuddlefish.tests
|
||||
|
||||
# tests write to stderr. flush everything before and after to avoid
|
||||
# confusion later.
|
||||
sys.stdout.flush(); sys.stderr.flush()
|
||||
olddir = os.getcwd()
|
||||
os.chdir(env_root)
|
||||
retval = cuddlefish.tests.run(verbose)
|
||||
os.chdir(olddir)
|
||||
sys.stdout.flush(); sys.stderr.flush()
|
||||
return retval
|
||||
|
||||
def test_all_testaddons(env_root, defaults):
|
||||
addons_dir = os.path.join(env_root, "test", "addons")
|
||||
addons = [dirname for dirname in os.listdir(addons_dir)
|
||||
if os.path.isdir(os.path.join(addons_dir, dirname))]
|
||||
addons.sort()
|
||||
fail = False
|
||||
for dirname in addons:
|
||||
# apply the filter
|
||||
if (not defaults['filter'].split(":")[0] in dirname):
|
||||
continue
|
||||
|
||||
print >>sys.stderr, "Testing %s..." % dirname
|
||||
sys.stderr.flush()
|
||||
try:
|
||||
run(arguments=["testrun",
|
||||
"--pkgdir",
|
||||
os.path.join(addons_dir, dirname)],
|
||||
defaults=defaults,
|
||||
env_root=env_root)
|
||||
except SystemExit, e:
|
||||
fail = (e.code != 0) or fail
|
||||
if fail and defaults.get("stopOnError"):
|
||||
break
|
||||
|
||||
if fail:
|
||||
print >>sys.stderr, "Some test addons tests were unsuccessful."
|
||||
sys.exit(-1)
|
||||
|
||||
def test_all_examples(env_root, defaults):
|
||||
examples_dir = os.path.join(env_root, "examples")
|
||||
examples = [dirname for dirname in os.listdir(examples_dir)
|
||||
if os.path.isdir(os.path.join(examples_dir, dirname))]
|
||||
examples.sort()
|
||||
fail = False
|
||||
for dirname in examples:
|
||||
if (not defaults['filter'].split(":")[0] in dirname):
|
||||
continue
|
||||
|
||||
print >>sys.stderr, "Testing %s..." % dirname
|
||||
sys.stderr.flush()
|
||||
try:
|
||||
run(arguments=["test",
|
||||
"--pkgdir",
|
||||
os.path.join(examples_dir, dirname)],
|
||||
defaults=defaults,
|
||||
env_root=env_root)
|
||||
except SystemExit, e:
|
||||
fail = (e.code != 0) or fail
|
||||
if fail and defaults.get("stopOnError"):
|
||||
break
|
||||
|
||||
if fail:
|
||||
print >>sys.stderr, "Some examples tests were unsuccessful."
|
||||
sys.exit(-1)
|
||||
|
||||
def test_all_packages(env_root, defaults):
|
||||
packages_dir = os.path.join(env_root, "packages")
|
||||
if os.path.isdir(packages_dir):
|
||||
packages = [dirname for dirname in os.listdir(packages_dir)
|
||||
if os.path.isdir(os.path.join(packages_dir, dirname))]
|
||||
else:
|
||||
packages = []
|
||||
packages.append(env_root)
|
||||
packages.sort()
|
||||
print >>sys.stderr, "Testing all available packages: %s." % (", ".join(packages))
|
||||
sys.stderr.flush()
|
||||
fail = False
|
||||
for dirname in packages:
|
||||
print >>sys.stderr, "Testing %s..." % dirname
|
||||
sys.stderr.flush()
|
||||
try:
|
||||
run(arguments=["test",
|
||||
"--pkgdir",
|
||||
os.path.join(packages_dir, dirname)],
|
||||
defaults=defaults,
|
||||
env_root=env_root)
|
||||
except SystemExit, e:
|
||||
fail = (e.code != 0) or fail
|
||||
if fail and defaults.get('stopOnError'):
|
||||
break
|
||||
if fail:
|
||||
print >>sys.stderr, "Some package tests were unsuccessful."
|
||||
sys.exit(-1)
|
||||
|
||||
def get_config_args(name, env_root):
|
||||
local_json = os.path.join(env_root, "local.json")
|
||||
if not (os.path.exists(local_json) and
|
||||
os.path.isfile(local_json)):
|
||||
if name == "default":
|
||||
return []
|
||||
else:
|
||||
print >>sys.stderr, "File does not exist: %s" % local_json
|
||||
sys.exit(1)
|
||||
local_json = packaging.load_json_file(local_json)
|
||||
if 'configs' not in local_json:
|
||||
print >>sys.stderr, "'configs' key not found in local.json."
|
||||
sys.exit(1)
|
||||
if name not in local_json.configs:
|
||||
if name == "default":
|
||||
return []
|
||||
else:
|
||||
print >>sys.stderr, "No config found for '%s'." % name
|
||||
sys.exit(1)
|
||||
config = local_json.configs[name]
|
||||
if type(config) != list:
|
||||
print >>sys.stderr, "Config for '%s' must be a list of strings." % name
|
||||
sys.exit(1)
|
||||
return config
|
||||
|
||||
def initializer(env_root, args, out=sys.stdout, err=sys.stderr):
|
||||
from templates import PACKAGE_JSON, TEST_MAIN_JS
|
||||
from preflight import create_jid
|
||||
path = os.getcwd()
|
||||
addon = os.path.basename(path)
|
||||
# if more than two arguments
|
||||
if len(args) > 2:
|
||||
print >>err, 'Too many arguments.'
|
||||
return {"result":1}
|
||||
if len(args) == 2:
|
||||
path = os.path.join(path,args[1])
|
||||
try:
|
||||
os.mkdir(path)
|
||||
print >>out, '*', args[1], 'package directory created'
|
||||
except OSError:
|
||||
print >>out, '*', args[1], 'already exists, testing if directory is empty'
|
||||
# avoid clobbering existing files, but we tolerate things like .git
|
||||
existing = [fn for fn in os.listdir(path) if not fn.startswith(".")]
|
||||
if existing:
|
||||
print >>err, 'This command must be run in an empty directory.'
|
||||
return {"result":1}
|
||||
for d in ['lib','data','test']:
|
||||
os.mkdir(os.path.join(path,d))
|
||||
print >>out, '*', d, 'directory created'
|
||||
jid = create_jid()
|
||||
print >>out, '* generated jID automatically:', jid
|
||||
open(os.path.join(path,'package.json'),'w').write(PACKAGE_JSON % {'name':addon.lower(),
|
||||
'title':addon,
|
||||
'id':jid })
|
||||
print >>out, '* package.json written'
|
||||
open(os.path.join(path,'test','test-main.js'),'w').write(TEST_MAIN_JS)
|
||||
print >>out, '* test/test-main.js written'
|
||||
open(os.path.join(path,'lib','main.js'),'w').write('')
|
||||
print >>out, '* lib/main.js written'
|
||||
if len(args) == 1:
|
||||
print >>out, '\nYour sample add-on is now ready.'
|
||||
print >>out, 'Do "cfx test" to test it and "cfx run" to try it. Have fun!'
|
||||
else:
|
||||
print >>out, '\nYour sample add-on is now ready in the \'' + args[1] + '\' directory.'
|
||||
print >>out, 'Change to that directory, then do "cfx test" to test it, \nand "cfx run" to try it. Have fun!'
|
||||
return {"result":0, "jid":jid}
|
||||
|
||||
def buildJID(target_cfg):
|
||||
if "id" in target_cfg:
|
||||
jid = target_cfg["id"]
|
||||
else:
|
||||
import uuid
|
||||
jid = str(uuid.uuid4())
|
||||
if not ("@" in jid or jid.startswith("{")):
|
||||
jid = jid + "@jetpack"
|
||||
return jid
|
||||
|
||||
def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None,
|
||||
defaults=None, env_root=os.environ.get('CUDDLEFISH_ROOT'),
|
||||
stdout=sys.stdout):
|
||||
versions = get_versions()
|
||||
sdk_version = versions["version"]
|
||||
display_version = "Add-on SDK %s (%s)" % (sdk_version, versions["full"])
|
||||
parser_kwargs = dict(arguments=arguments,
|
||||
global_options=global_options,
|
||||
parser_groups=parser_groups,
|
||||
usage=usage,
|
||||
version=display_version,
|
||||
defaults=defaults)
|
||||
|
||||
(options, args) = parse_args(**parser_kwargs)
|
||||
|
||||
config_args = get_config_args(options.config, env_root);
|
||||
|
||||
# reparse configs with arguments from local.json
|
||||
if config_args:
|
||||
parser_kwargs['arguments'] += config_args
|
||||
(options, args) = parse_args(**parser_kwargs)
|
||||
|
||||
command = args[0]
|
||||
|
||||
if command == "init":
|
||||
initializer(env_root, args)
|
||||
return
|
||||
if command == "testpkgs":
|
||||
test_all_packages(env_root, defaults=options.__dict__)
|
||||
return
|
||||
elif command == "testaddons":
|
||||
test_all_testaddons(env_root, defaults=options.__dict__)
|
||||
return
|
||||
elif command == "testex":
|
||||
test_all_examples(env_root, defaults=options.__dict__)
|
||||
return
|
||||
elif command == "testall":
|
||||
test_all(env_root, defaults=options.__dict__)
|
||||
return
|
||||
elif command == "testcfx":
|
||||
if options.filter:
|
||||
print >>sys.stderr, "The filter option is not valid with the testcfx command"
|
||||
return
|
||||
test_cfx(env_root, options.verbose)
|
||||
return
|
||||
elif command not in ["xpi", "test", "run", "testrun"]:
|
||||
print >>sys.stderr, "Unknown command: %s" % command
|
||||
print >>sys.stderr, "Try using '--help' for assistance."
|
||||
sys.exit(1)
|
||||
|
||||
target_cfg_json = None
|
||||
if not target_cfg:
|
||||
if not options.pkgdir:
|
||||
options.pkgdir = find_parent_package(os.getcwd())
|
||||
if not options.pkgdir:
|
||||
print >>sys.stderr, ("cannot find 'package.json' in the"
|
||||
" current directory or any parent.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
options.pkgdir = os.path.abspath(options.pkgdir)
|
||||
if not os.path.exists(os.path.join(options.pkgdir, 'package.json')):
|
||||
print >>sys.stderr, ("cannot find 'package.json' in"
|
||||
" %s." % options.pkgdir)
|
||||
sys.exit(1)
|
||||
|
||||
target_cfg_json = os.path.join(options.pkgdir, 'package.json')
|
||||
target_cfg = packaging.get_config_in_dir(options.pkgdir)
|
||||
|
||||
# At this point, we're either building an XPI or running Jetpack code in
|
||||
# a Mozilla application (which includes running tests).
|
||||
|
||||
use_main = False
|
||||
inherited_options = ['verbose', 'enable_e10s', 'parseable', 'check_memory',
|
||||
'no_quit', 'abort_on_missing']
|
||||
enforce_timeouts = False
|
||||
|
||||
if command == "xpi":
|
||||
use_main = True
|
||||
elif command == "test":
|
||||
if 'tests' not in target_cfg:
|
||||
target_cfg['tests'] = []
|
||||
inherited_options.extend(['iterations', 'filter', 'profileMemory',
|
||||
'stopOnError'])
|
||||
enforce_timeouts = True
|
||||
elif command == "run":
|
||||
use_main = True
|
||||
elif command == "testrun":
|
||||
use_main = True
|
||||
enforce_timeouts = True
|
||||
else:
|
||||
assert 0, "shouldn't get here"
|
||||
|
||||
if use_main and 'main' not in target_cfg:
|
||||
# If the user supplies a template dir, then the main
|
||||
# program may be contained in the template.
|
||||
if not options.templatedir:
|
||||
print >>sys.stderr, "package.json does not have a 'main' entry."
|
||||
sys.exit(1)
|
||||
|
||||
if not pkg_cfg:
|
||||
pkg_cfg = packaging.build_config(env_root, target_cfg, options.packagepath)
|
||||
|
||||
target = target_cfg.name
|
||||
|
||||
# TODO: Consider keeping a cache of dynamic UUIDs, based
|
||||
# on absolute filesystem pathname, in the root directory
|
||||
# or something.
|
||||
if command in ('xpi', 'run', 'testrun'):
|
||||
from cuddlefish.preflight import preflight_config
|
||||
if target_cfg_json:
|
||||
config_was_ok, modified = preflight_config(target_cfg,
|
||||
target_cfg_json)
|
||||
if not config_was_ok:
|
||||
if modified:
|
||||
# we need to re-read package.json . The safest approach
|
||||
# is to re-run the "cfx xpi"/"cfx run" command.
|
||||
print >>sys.stderr, ("package.json modified: please re-run"
|
||||
" 'cfx %s'" % command)
|
||||
else:
|
||||
print >>sys.stderr, ("package.json needs modification:"
|
||||
" please update it and then re-run"
|
||||
" 'cfx %s'" % command)
|
||||
sys.exit(1)
|
||||
# if we make it this far, we have a JID
|
||||
else:
|
||||
assert command == "test"
|
||||
|
||||
jid = buildJID(target_cfg)
|
||||
|
||||
targets = [target]
|
||||
if command == "test":
|
||||
targets.append(options.test_runner_pkg)
|
||||
|
||||
extra_packages = []
|
||||
if options.extra_packages:
|
||||
extra_packages = options.extra_packages.split(",")
|
||||
if extra_packages:
|
||||
targets.extend(extra_packages)
|
||||
target_cfg.extra_dependencies = extra_packages
|
||||
|
||||
deps = packaging.get_deps_for_targets(pkg_cfg, targets)
|
||||
|
||||
from cuddlefish.manifest import build_manifest, ModuleNotFoundError, \
|
||||
BadChromeMarkerError
|
||||
# Figure out what loader files should be scanned. This is normally
|
||||
# computed inside packaging.generate_build_for_target(), by the first
|
||||
# dependent package that defines a "loader" property in its package.json.
|
||||
# This property is interpreted as a filename relative to the top of that
|
||||
# file, and stored as a path in build.loader . generate_build_for_target()
|
||||
# cannot be called yet (it needs the list of used_deps that
|
||||
# build_manifest() computes, but build_manifest() needs the list of
|
||||
# loader files that it computes). We could duplicate or factor out this
|
||||
# build.loader logic, but that would be messy, so instead we hard-code
|
||||
# the choice of loader for manifest-generation purposes. In practice,
|
||||
# this means that alternative loaders probably won't work with
|
||||
# --strip-xpi.
|
||||
assert packaging.DEFAULT_LOADER == "addon-sdk"
|
||||
assert pkg_cfg.packages["addon-sdk"].loader == "lib/sdk/loader/cuddlefish.js"
|
||||
cuddlefish_js_path = os.path.join(pkg_cfg.packages["addon-sdk"].root_dir,
|
||||
"lib", "sdk", "loader", "cuddlefish.js")
|
||||
loader_modules = [("addon-sdk", "lib", "sdk/loader/cuddlefish", cuddlefish_js_path)]
|
||||
scan_tests = command == "test"
|
||||
|
||||
try:
|
||||
manifest = build_manifest(target_cfg, pkg_cfg, deps, scan_tests,
|
||||
None, loader_modules,
|
||||
abort_on_missing=options.abort_on_missing)
|
||||
except ModuleNotFoundError, e:
|
||||
print str(e)
|
||||
sys.exit(1)
|
||||
except BadChromeMarkerError, e:
|
||||
# An error had already been displayed on stderr in manifest code
|
||||
sys.exit(1)
|
||||
used_deps = manifest.get_used_packages()
|
||||
if command == "test":
|
||||
# The test runner doesn't appear to link against any actual packages,
|
||||
# because it loads everything at runtime (invisible to the linker).
|
||||
# If we believe that, we won't set up URI mappings for anything, and
|
||||
# tests won't be able to run.
|
||||
used_deps = deps
|
||||
for xp in extra_packages:
|
||||
if xp not in used_deps:
|
||||
used_deps.append(xp)
|
||||
|
||||
build = packaging.generate_build_for_target(
|
||||
pkg_cfg, target, used_deps,
|
||||
include_dep_tests=options.dep_tests,
|
||||
is_running_tests=(command == "test")
|
||||
)
|
||||
|
||||
harness_options = {
|
||||
'jetpackID': jid,
|
||||
'staticArgs': options.static_args,
|
||||
'name': target,
|
||||
}
|
||||
|
||||
harness_options.update(build)
|
||||
|
||||
# When cfx is run from sdk root directory, we will strip sdk modules and
|
||||
# override them with local modules.
|
||||
# So that integration tools will continue to work and use local modules
|
||||
if os.getcwd() == env_root:
|
||||
options.bundle_sdk = True
|
||||
options.force_use_bundled_sdk = False
|
||||
options.overload_modules = True
|
||||
|
||||
if options.pkgdir == env_root:
|
||||
options.bundle_sdk = True
|
||||
options.overload_modules = True
|
||||
|
||||
extra_environment = {}
|
||||
if command == "test":
|
||||
# This should be contained in the test runner package.
|
||||
harness_options['main'] = 'sdk/test/runner'
|
||||
harness_options['mainPath'] = 'sdk/test/runner'
|
||||
else:
|
||||
harness_options['main'] = target_cfg.get('main')
|
||||
harness_options['mainPath'] = manifest.top_path
|
||||
extra_environment["CFX_COMMAND"] = command
|
||||
|
||||
for option in inherited_options:
|
||||
harness_options[option] = getattr(options, option)
|
||||
|
||||
harness_options['metadata'] = packaging.get_metadata(pkg_cfg, used_deps)
|
||||
|
||||
harness_options['sdkVersion'] = sdk_version
|
||||
|
||||
packaging.call_plugins(pkg_cfg, used_deps)
|
||||
|
||||
retval = 0
|
||||
|
||||
if options.templatedir:
|
||||
app_extension_dir = os.path.abspath(options.templatedir)
|
||||
elif os.path.exists(os.path.join(options.pkgdir, "app-extension")):
|
||||
app_extension_dir = os.path.join(options.pkgdir, "app-extension")
|
||||
else:
|
||||
mydir = os.path.dirname(os.path.abspath(__file__))
|
||||
app_extension_dir = os.path.join(mydir, "../../app-extension")
|
||||
|
||||
# Do not add entries for SDK modules
|
||||
harness_options['manifest'] = manifest.get_harness_options_manifest(False)
|
||||
|
||||
# Gives an hint to tell if sdk modules are bundled or not
|
||||
harness_options['is-sdk-bundled'] = options.bundle_sdk or options.no_strip_xpi
|
||||
|
||||
if options.force_use_bundled_sdk:
|
||||
if not harness_options['is-sdk-bundled']:
|
||||
print >>sys.stderr, ("--force-use-bundled-sdk "
|
||||
"can't be used if sdk isn't bundled.")
|
||||
sys.exit(1)
|
||||
if options.overload_modules:
|
||||
print >>sys.stderr, ("--force-use-bundled-sdk and --overload-modules "
|
||||
"can't be used at the same time.")
|
||||
sys.exit(1)
|
||||
# Pass a flag in order to force using sdk modules shipped in the xpi
|
||||
harness_options['force-use-bundled-sdk'] = True
|
||||
|
||||
from cuddlefish.rdf import gen_manifest, RDFUpdate
|
||||
|
||||
manifest_rdf = gen_manifest(template_root_dir=app_extension_dir,
|
||||
target_cfg=target_cfg,
|
||||
jid=jid,
|
||||
update_url=options.update_url,
|
||||
bootstrap=True,
|
||||
enable_mobile=options.enable_mobile)
|
||||
|
||||
if command == "xpi" and options.update_link:
|
||||
if not options.update_link.startswith("https"):
|
||||
raise optparse.OptionValueError("--update-link must start with 'https': %s" % options.update_link)
|
||||
rdf_name = UPDATE_RDF_FILENAME % target_cfg.name
|
||||
print >>stdout, "Exporting update description to %s." % rdf_name
|
||||
update = RDFUpdate()
|
||||
update.add(manifest_rdf, options.update_link)
|
||||
open(rdf_name, "w").write(str(update))
|
||||
|
||||
# ask the manifest what files were used, so we can construct an XPI
|
||||
# without the rest. This will include the loader (and everything it
|
||||
# uses) because of the "loader_modules" starting points we passed to
|
||||
# build_manifest earlier
|
||||
used_files = None
|
||||
if command == "xpi":
|
||||
used_files = set(manifest.get_used_files(options.bundle_sdk))
|
||||
|
||||
if options.no_strip_xpi:
|
||||
used_files = None # disables the filter, includes all files
|
||||
|
||||
if command == 'xpi':
|
||||
from cuddlefish.xpi import build_xpi
|
||||
# Generate extra options
|
||||
extra_harness_options = {}
|
||||
for kv in options.extra_harness_option_args:
|
||||
key,value = kv.split("=", 1)
|
||||
extra_harness_options[key] = value
|
||||
# Generate xpi filepath
|
||||
if options.output_file:
|
||||
xpi_path = options.output_file
|
||||
else:
|
||||
xpi_path = XPI_FILENAME % target_cfg.name
|
||||
|
||||
print >>stdout, "Exporting extension to %s." % xpi_path
|
||||
build_xpi(template_root_dir=app_extension_dir,
|
||||
manifest=manifest_rdf,
|
||||
xpi_path=xpi_path,
|
||||
harness_options=harness_options,
|
||||
limit_to=used_files,
|
||||
extra_harness_options=extra_harness_options,
|
||||
bundle_sdk=True,
|
||||
pkgdir=options.pkgdir)
|
||||
else:
|
||||
from cuddlefish.runner import run_app
|
||||
|
||||
if options.no_connections == "default":
|
||||
if command == "run":
|
||||
no_connections = False
|
||||
else:
|
||||
no_connections = True
|
||||
elif options.no_connections == "on":
|
||||
no_connections = True
|
||||
else:
|
||||
no_connections = False
|
||||
|
||||
if options.profiledir:
|
||||
options.profiledir = os.path.expanduser(options.profiledir)
|
||||
options.profiledir = os.path.abspath(options.profiledir)
|
||||
|
||||
if options.addons is not None:
|
||||
options.addons = options.addons.split(",")
|
||||
|
||||
enable_e10s = options.enable_e10s or target_cfg.get('e10s', False)
|
||||
|
||||
try:
|
||||
retval = run_app(harness_root_dir=app_extension_dir,
|
||||
manifest_rdf=manifest_rdf,
|
||||
harness_options=harness_options,
|
||||
app_type=options.app,
|
||||
binary=options.binary,
|
||||
profiledir=options.profiledir,
|
||||
verbose=options.verbose,
|
||||
parseable=options.parseable,
|
||||
enforce_timeouts=enforce_timeouts,
|
||||
logfile=options.logfile,
|
||||
addons=options.addons,
|
||||
args=options.cmdargs,
|
||||
extra_environment=extra_environment,
|
||||
norun=options.no_run,
|
||||
noquit=options.no_quit,
|
||||
used_files=used_files,
|
||||
enable_mobile=options.enable_mobile,
|
||||
mobile_app_name=options.mobile_app_name,
|
||||
env_root=env_root,
|
||||
is_running_tests=(command == "test"),
|
||||
overload_modules=options.overload_modules,
|
||||
bundle_sdk=options.bundle_sdk,
|
||||
pkgdir=options.pkgdir,
|
||||
enable_e10s=enable_e10s,
|
||||
no_connections=no_connections)
|
||||
except ValueError, e:
|
||||
print ""
|
||||
print "A given cfx option has an inappropriate value:"
|
||||
print >>sys.stderr, " " + " \n ".join(str(e).split("\n"))
|
||||
retval = -1
|
||||
except Exception, e:
|
||||
if str(e).startswith(MOZRUNNER_BIN_NOT_FOUND):
|
||||
print >>sys.stderr, MOZRUNNER_BIN_NOT_FOUND_HELP.strip()
|
||||
retval = -1
|
||||
else:
|
||||
raise
|
||||
sys.exit(retval)
|
||||
@@ -1,174 +0,0 @@
|
||||
|
||||
# This file helps to compute a version number in source trees obtained from
|
||||
# git-archive tarball (such as those provided by githubs download-from-tag
|
||||
# feature). Distribution tarballs (build by setup.py sdist) and build
|
||||
# directories (produced by setup.py build) will contain a much shorter file
|
||||
# that just contains the computed version number.
|
||||
|
||||
# This file is released into the public domain. Generated by versioneer-0.6
|
||||
# (https://github.com/warner/python-versioneer)
|
||||
|
||||
# these strings will be replaced by git during git-archive
|
||||
git_refnames = "$Format:%d$"
|
||||
git_full = "$Format:%H$"
|
||||
|
||||
|
||||
import subprocess
|
||||
|
||||
def run_command(args, cwd=None, verbose=False):
|
||||
try:
|
||||
# remember shell=False, so use git.cmd on windows, not just git
|
||||
p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd)
|
||||
except EnvironmentError, e:
|
||||
if verbose:
|
||||
print "unable to run %s" % args[0]
|
||||
print e
|
||||
return None
|
||||
stdout = p.communicate()[0].strip()
|
||||
if p.returncode != 0:
|
||||
if verbose:
|
||||
print "unable to run %s (error)" % args[0]
|
||||
return None
|
||||
return stdout
|
||||
|
||||
|
||||
import sys
|
||||
import re
|
||||
import os.path
|
||||
|
||||
def get_expanded_variables(versionfile_source):
|
||||
"""
|
||||
the code embedded in _version.py can just fetch the value of these
|
||||
variables. When used from setup.py, we don't want to import
|
||||
_version.py, so we do it with a regexp instead. This function is not
|
||||
used from _version.py.
|
||||
"""
|
||||
variables = {}
|
||||
try:
|
||||
for line in open(versionfile_source,"r").readlines():
|
||||
if line.strip().startswith("git_refnames ="):
|
||||
mo = re.search(r'=\s*"(.*)"', line)
|
||||
if mo:
|
||||
variables["refnames"] = mo.group(1)
|
||||
if line.strip().startswith("git_full ="):
|
||||
mo = re.search(r'=\s*"(.*)"', line)
|
||||
if mo:
|
||||
variables["full"] = mo.group(1)
|
||||
except EnvironmentError:
|
||||
pass
|
||||
return variables
|
||||
|
||||
def versions_from_expanded_variables(variables, tag_prefix):
|
||||
refnames = variables["refnames"].strip()
|
||||
if refnames.startswith("$Format"):
|
||||
return {} # unexpanded, so not in an unpacked git-archive tarball
|
||||
refs = set([r.strip() for r in refnames.strip("()").split(",")])
|
||||
for ref in list(refs):
|
||||
if not re.search(r'\d', ref):
|
||||
refs.discard(ref)
|
||||
# Assume all version tags have a digit. git's %d expansion
|
||||
# behaves like git log --decorate=short and strips out the
|
||||
# refs/heads/ and refs/tags/ prefixes that would let us
|
||||
# distinguish between branches and tags. By ignoring refnames
|
||||
# without digits, we filter out many common branch names like
|
||||
# "release" and "stabilization", as well as "HEAD" and "master".
|
||||
for ref in sorted(refs):
|
||||
# sorting will prefer e.g. "2.0" over "2.0rc1"
|
||||
if ref.startswith(tag_prefix):
|
||||
r = ref[len(tag_prefix):]
|
||||
return { "version": r,
|
||||
"full": variables["full"].strip() }
|
||||
# no suitable tags, so we use the full revision id
|
||||
return { "version": variables["full"].strip(),
|
||||
"full": variables["full"].strip() }
|
||||
|
||||
def versions_from_vcs(tag_prefix, versionfile_source, verbose=False):
|
||||
"""
|
||||
this runs 'git' from the root of the source tree. That either means
|
||||
someone ran a setup.py command (and this code is in versioneer.py, thus
|
||||
the containing directory is the root of the source tree), or someone
|
||||
ran a project-specific entry point (and this code is in _version.py,
|
||||
thus the containing directory is somewhere deeper in the source tree).
|
||||
This only gets called if the git-archive 'subst' variables were *not*
|
||||
expanded, and _version.py hasn't already been rewritten with a short
|
||||
version string, meaning we're inside a checked out source tree.
|
||||
"""
|
||||
try:
|
||||
here = os.path.abspath(__file__)
|
||||
except NameError:
|
||||
# some py2exe/bbfreeze/non-CPython implementations don't do __file__
|
||||
return {} # not always correct
|
||||
|
||||
# versionfile_source is the relative path from the top of the source tree
|
||||
# (where the .git directory might live) to this file. Invert this to find
|
||||
# the root from __file__.
|
||||
root = here
|
||||
for i in range(len(versionfile_source.split("/"))):
|
||||
root = os.path.dirname(root)
|
||||
if not os.path.exists(os.path.join(root, ".git")):
|
||||
return {}
|
||||
|
||||
GIT = "git"
|
||||
if sys.platform == "win32":
|
||||
GIT = "git.cmd"
|
||||
stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"],
|
||||
cwd=root)
|
||||
if stdout is None:
|
||||
return {}
|
||||
if not stdout.startswith(tag_prefix):
|
||||
if verbose:
|
||||
print "tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix)
|
||||
return {}
|
||||
tag = stdout[len(tag_prefix):]
|
||||
stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root)
|
||||
if stdout is None:
|
||||
return {}
|
||||
full = stdout.strip()
|
||||
if tag.endswith("-dirty"):
|
||||
full += "-dirty"
|
||||
return {"version": tag, "full": full}
|
||||
|
||||
|
||||
def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False):
|
||||
try:
|
||||
here = os.path.abspath(__file__)
|
||||
# versionfile_source is the relative path from the top of the source
|
||||
# tree (where the .git directory might live) to _version.py, when
|
||||
# this is used by the runtime. Invert this to find the root from
|
||||
# __file__.
|
||||
root = here
|
||||
for i in range(len(versionfile_source.split("/"))):
|
||||
root = os.path.dirname(root)
|
||||
except NameError:
|
||||
# try a couple different things to handle py2exe, bbfreeze, and
|
||||
# non-CPython implementations which don't do __file__. This code
|
||||
# either lives in versioneer.py (used by setup.py) or _version.py
|
||||
# (used by the runtime). In the versioneer.py case, sys.argv[0] will
|
||||
# be setup.py, in the root of the source tree. In the _version.py
|
||||
# case, we have no idea what sys.argv[0] is (some
|
||||
# application-specific runner).
|
||||
root = os.path.dirname(os.path.abspath(sys.argv[0]))
|
||||
# Source tarballs conventionally unpack into a directory that includes
|
||||
# both the project name and a version string.
|
||||
dirname = os.path.basename(root)
|
||||
if not dirname.startswith(parentdir_prefix):
|
||||
if verbose:
|
||||
print "dirname '%s' doesn't start with prefix '%s'" % (dirname, parentdir_prefix)
|
||||
return None
|
||||
return {"version": dirname[len(parentdir_prefix):], "full": ""}
|
||||
|
||||
tag_prefix = ""
|
||||
parentdir_prefix = "addon-sdk-"
|
||||
versionfile_source = "python-lib/cuddlefish/_version.py"
|
||||
|
||||
def get_versions():
|
||||
variables = { "refnames": git_refnames, "full": git_full }
|
||||
ver = versions_from_expanded_variables(variables, tag_prefix)
|
||||
if not ver:
|
||||
ver = versions_from_vcs(tag_prefix, versionfile_source)
|
||||
if not ver:
|
||||
ver = versions_from_parentdir(parentdir_prefix, versionfile_source)
|
||||
if not ver:
|
||||
ver = {"version": "unknown", "full": ""}
|
||||
return ver
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# Taken from Paver's paver.options module.
|
||||
|
||||
class Bunch(dict):
|
||||
"""A dictionary that provides attribute-style access."""
|
||||
|
||||
def __repr__(self):
|
||||
keys = self.keys()
|
||||
keys.sort()
|
||||
args = ', '.join(['%s=%r' % (key, self[key]) for key in keys])
|
||||
return '%s(%s)' % (self.__class__.__name__, args)
|
||||
|
||||
def __getitem__(self, key):
|
||||
item = dict.__getitem__(self, key)
|
||||
if callable(item):
|
||||
return item()
|
||||
return item
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return self[name]
|
||||
except KeyError:
|
||||
raise AttributeError(name)
|
||||
|
||||
__setattr__ = dict.__setitem__
|
||||
|
||||
def __delattr__(self, name):
|
||||
try:
|
||||
del self[name]
|
||||
except KeyError:
|
||||
raise AttributeError(name)
|
||||
@@ -1,807 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
|
||||
import os, sys, re, hashlib
|
||||
import simplejson as json
|
||||
SEP = os.path.sep
|
||||
from cuddlefish.util import filter_filenames, filter_dirnames
|
||||
|
||||
# Load new layout mapping hashtable
|
||||
path = os.path.join(os.environ.get('CUDDLEFISH_ROOT'), "mapping.json")
|
||||
data = open(path, 'r').read()
|
||||
NEW_LAYOUT_MAPPING = json.loads(data)
|
||||
|
||||
def js_zipname(packagename, modulename):
|
||||
return "%s-lib/%s.js" % (packagename, modulename)
|
||||
def docs_zipname(packagename, modulename):
|
||||
return "%s-docs/%s.md" % (packagename, modulename)
|
||||
def datamap_zipname(packagename):
|
||||
return "%s-data.json" % packagename
|
||||
def datafile_zipname(packagename, datapath):
|
||||
return "%s-data/%s" % (packagename, datapath)
|
||||
|
||||
def to_json(o):
|
||||
return json.dumps(o, indent=1).encode("utf-8")+"\n"
|
||||
|
||||
class ModuleNotFoundError(Exception):
|
||||
def __init__(self, requirement_type, requirement_name,
|
||||
used_by, line_number, looked_in):
|
||||
Exception.__init__(self)
|
||||
self.requirement_type = requirement_type # "require" or "define"
|
||||
self.requirement_name = requirement_name # string, what they require()d
|
||||
self.used_by = used_by # string, full path to module which did require()
|
||||
self.line_number = line_number # int, 1-indexed line number of first require()
|
||||
self.looked_in = looked_in # list of full paths to potential .js files
|
||||
def __str__(self):
|
||||
what = "%s(%s)" % (self.requirement_type, self.requirement_name)
|
||||
where = self.used_by
|
||||
if self.line_number is not None:
|
||||
where = "%s:%d" % (self.used_by, self.line_number)
|
||||
searched = "Looked for it in:\n %s\n" % "\n ".join(self.looked_in)
|
||||
return ("ModuleNotFoundError: unable to satisfy: %s from\n"
|
||||
" %s:\n" % (what, where)) + searched
|
||||
|
||||
class BadModuleIdentifier(Exception):
|
||||
pass
|
||||
class BadSection(Exception):
|
||||
pass
|
||||
class UnreachablePrefixError(Exception):
|
||||
pass
|
||||
|
||||
class ManifestEntry:
|
||||
def __init__(self):
|
||||
self.docs_filename = None
|
||||
self.docs_hash = None
|
||||
self.requirements = {}
|
||||
self.datamap = None
|
||||
|
||||
def get_path(self):
|
||||
name = self.moduleName
|
||||
|
||||
if name.endswith(".js"):
|
||||
name = name[:-3]
|
||||
items = []
|
||||
# Only add package name for addons, so that system module paths match
|
||||
# the path from the commonjs root directory and also match the loader
|
||||
# mappings.
|
||||
if self.packageName != "addon-sdk":
|
||||
items.append(self.packageName)
|
||||
# And for the same reason, do not append `lib/`.
|
||||
if self.sectionName == "tests":
|
||||
items.append(self.sectionName)
|
||||
items.append(name)
|
||||
|
||||
return "/".join(items)
|
||||
|
||||
def get_entry_for_manifest(self):
|
||||
entry = { "packageName": self.packageName,
|
||||
"sectionName": self.sectionName,
|
||||
"moduleName": self.moduleName,
|
||||
"jsSHA256": self.js_hash,
|
||||
"docsSHA256": self.docs_hash,
|
||||
"requirements": {},
|
||||
}
|
||||
for req in self.requirements:
|
||||
if isinstance(self.requirements[req], ManifestEntry):
|
||||
them = self.requirements[req] # this is another ManifestEntry
|
||||
entry["requirements"][req] = them.get_path()
|
||||
else:
|
||||
# something magic. The manifest entry indicates that they're
|
||||
# allowed to require() it
|
||||
entry["requirements"][req] = self.requirements[req]
|
||||
assert isinstance(entry["requirements"][req], unicode) or \
|
||||
isinstance(entry["requirements"][req], str)
|
||||
return entry
|
||||
|
||||
def add_js(self, js_filename):
|
||||
self.js_filename = js_filename
|
||||
self.js_hash = hash_file(js_filename)
|
||||
def add_docs(self, docs_filename):
|
||||
self.docs_filename = docs_filename
|
||||
self.docs_hash = hash_file(docs_filename)
|
||||
def add_requirement(self, reqname, reqdata):
|
||||
self.requirements[reqname] = reqdata
|
||||
def add_data(self, datamap):
|
||||
self.datamap = datamap
|
||||
|
||||
def get_js_zipname(self):
|
||||
return js_zipname(self.packagename, self.modulename)
|
||||
def get_docs_zipname(self):
|
||||
if self.docs_hash:
|
||||
return docs_zipname(self.packagename, self.modulename)
|
||||
return None
|
||||
# self.js_filename
|
||||
# self.docs_filename
|
||||
|
||||
|
||||
def hash_file(fn):
|
||||
return hashlib.sha256(open(fn,"rb").read()).hexdigest()
|
||||
|
||||
def get_datafiles(datadir):
|
||||
"""
|
||||
yields pathnames relative to DATADIR, ignoring some files
|
||||
"""
|
||||
for dirpath, dirnames, filenames in os.walk(datadir):
|
||||
filenames = list(filter_filenames(filenames))
|
||||
# this tells os.walk to prune the search
|
||||
dirnames[:] = filter_dirnames(dirnames)
|
||||
for filename in filenames:
|
||||
fullname = os.path.join(dirpath, filename)
|
||||
assert fullname.startswith(datadir+SEP), "%s%s not in %s" % (datadir, SEP, fullname)
|
||||
yield fullname[len(datadir+SEP):]
|
||||
|
||||
|
||||
class DataMap:
|
||||
# one per package
|
||||
def __init__(self, pkg):
|
||||
self.pkg = pkg
|
||||
self.name = pkg.name
|
||||
self.files_to_copy = []
|
||||
datamap = {}
|
||||
datadir = os.path.join(pkg.root_dir, "data")
|
||||
for dataname in get_datafiles(datadir):
|
||||
absname = os.path.join(datadir, dataname)
|
||||
zipname = datafile_zipname(pkg.name, dataname)
|
||||
datamap[dataname] = hash_file(absname)
|
||||
self.files_to_copy.append( (zipname, absname) )
|
||||
self.data_manifest = to_json(datamap)
|
||||
self.data_manifest_hash = hashlib.sha256(self.data_manifest).hexdigest()
|
||||
self.data_manifest_zipname = datamap_zipname(pkg.name)
|
||||
self.data_uri_prefix = "%s/data/" % (self.name)
|
||||
|
||||
class BadChromeMarkerError(Exception):
|
||||
pass
|
||||
|
||||
class ModuleInfo:
|
||||
def __init__(self, package, section, name, js, docs):
|
||||
self.package = package
|
||||
self.section = section
|
||||
self.name = name
|
||||
self.js = js
|
||||
self.docs = docs
|
||||
|
||||
def __hash__(self):
|
||||
return hash( (self.package.name, self.section, self.name,
|
||||
self.js, self.docs) )
|
||||
def __eq__(self, them):
|
||||
if them.__class__ is not self.__class__:
|
||||
return False
|
||||
if ((them.package.name, them.section, them.name, them.js, them.docs) !=
|
||||
(self.package.name, self.section, self.name, self.js, self.docs) ):
|
||||
return False
|
||||
return True
|
||||
|
||||
def __repr__(self):
|
||||
return "ModuleInfo [%s %s %s] (%s, %s)" % (self.package.name,
|
||||
self.section,
|
||||
self.name,
|
||||
self.js, self.docs)
|
||||
|
||||
class ManifestBuilder:
|
||||
def __init__(self, target_cfg, pkg_cfg, deps, extra_modules,
|
||||
stderr=sys.stderr, abort_on_missing=False):
|
||||
self.manifest = {} # maps (package,section,module) to ManifestEntry
|
||||
self.target_cfg = target_cfg # the entry point
|
||||
self.pkg_cfg = pkg_cfg # all known packages
|
||||
self.deps = deps # list of package names to search
|
||||
self.used_packagenames = set()
|
||||
self.stderr = stderr
|
||||
self.extra_modules = extra_modules
|
||||
self.modules = {} # maps ModuleInfo to URI in self.manifest
|
||||
self.datamaps = {} # maps package name to DataMap instance
|
||||
self.files = [] # maps manifest index to (absfn,absfn) js/docs pair
|
||||
self.test_modules = [] # for runtime
|
||||
self.abort_on_missing = abort_on_missing # cfx eol
|
||||
|
||||
def build(self, scan_tests, test_filter_re):
|
||||
"""
|
||||
process the top module, which recurses to process everything it reaches
|
||||
"""
|
||||
if "main" in self.target_cfg:
|
||||
top_mi = self.find_top(self.target_cfg)
|
||||
top_me = self.process_module(top_mi)
|
||||
self.top_path = top_me.get_path()
|
||||
self.datamaps[self.target_cfg.name] = DataMap(self.target_cfg)
|
||||
if scan_tests:
|
||||
mi = self._find_module_in_package("addon-sdk", "lib", "sdk/test/runner", [])
|
||||
self.process_module(mi)
|
||||
# also scan all test files in all packages that we use. By making
|
||||
# a copy of self.used_packagenames first, we refrain from
|
||||
# processing tests in packages that our own tests depend upon. If
|
||||
# we're running tests for package A, and either modules in A or
|
||||
# tests in A depend upon modules from package B, we *don't* want
|
||||
# to run tests for package B.
|
||||
test_modules = []
|
||||
dirnames = self.target_cfg["tests"]
|
||||
if isinstance(dirnames, basestring):
|
||||
dirnames = [dirnames]
|
||||
dirnames = [os.path.join(self.target_cfg.root_dir, d)
|
||||
for d in dirnames]
|
||||
for d in dirnames:
|
||||
for filename in os.listdir(d):
|
||||
if filename.startswith("test-") and filename.endswith(".js"):
|
||||
testname = filename[:-3] # require(testname)
|
||||
if test_filter_re:
|
||||
if not re.search(test_filter_re, testname):
|
||||
continue
|
||||
tmi = ModuleInfo(self.target_cfg, "tests", testname,
|
||||
os.path.join(d, filename), None)
|
||||
# scan the test's dependencies
|
||||
tme = self.process_module(tmi)
|
||||
test_modules.append( (testname, tme) )
|
||||
# also add it as an artificial dependency of unit-test-finder, so
|
||||
# the runtime dynamic load can work.
|
||||
test_finder = self.get_manifest_entry("addon-sdk", "lib",
|
||||
"sdk/deprecated/unit-test-finder")
|
||||
for (testname,tme) in test_modules:
|
||||
test_finder.add_requirement(testname, tme)
|
||||
# finally, tell the runtime about it, so they won't have to
|
||||
# search for all tests. self.test_modules will be passed
|
||||
# through the harness-options.json file in the
|
||||
# .allTestModules property.
|
||||
# Pass the absolute module path.
|
||||
self.test_modules.append(tme.get_path())
|
||||
|
||||
# include files used by the loader
|
||||
for em in self.extra_modules:
|
||||
(pkgname, section, modname, js) = em
|
||||
mi = ModuleInfo(self.pkg_cfg.packages[pkgname], section, modname,
|
||||
js, None)
|
||||
self.process_module(mi)
|
||||
|
||||
|
||||
def get_module_entries(self):
|
||||
return frozenset(self.manifest.values())
|
||||
def get_data_entries(self):
|
||||
return frozenset(self.datamaps.values())
|
||||
|
||||
def get_used_packages(self):
|
||||
used = set()
|
||||
for index in self.manifest:
|
||||
(package, section, module) = index
|
||||
used.add(package)
|
||||
return sorted(used)
|
||||
|
||||
def get_used_files(self, bundle_sdk_modules):
|
||||
"""
|
||||
returns all .js files that we reference, plus data/ files. You will
|
||||
need to add the loader, off-manifest files that it needs, and
|
||||
generated metadata.
|
||||
"""
|
||||
for datamap in self.datamaps.values():
|
||||
for (zipname, absname) in datamap.files_to_copy:
|
||||
yield absname
|
||||
|
||||
for me in self.get_module_entries():
|
||||
# Only ship SDK files if we are told to do so
|
||||
if me.packageName != "addon-sdk" or bundle_sdk_modules:
|
||||
yield me.js_filename
|
||||
|
||||
def get_harness_options_manifest(self, bundle_sdk_modules):
|
||||
manifest = {}
|
||||
for me in self.get_module_entries():
|
||||
path = me.get_path()
|
||||
# Do not add manifest entries for system modules.
|
||||
# Doesn't prevent from shipping modules.
|
||||
# Shipping modules is decided in `get_used_files`.
|
||||
if me.packageName != "addon-sdk" or bundle_sdk_modules:
|
||||
manifest[path] = me.get_entry_for_manifest()
|
||||
return manifest
|
||||
|
||||
def get_manifest_entry(self, package, section, module):
|
||||
index = (package, section, module)
|
||||
if index not in self.manifest:
|
||||
m = self.manifest[index] = ManifestEntry()
|
||||
m.packageName = package
|
||||
m.sectionName = section
|
||||
m.moduleName = module
|
||||
self.used_packagenames.add(package)
|
||||
return self.manifest[index]
|
||||
|
||||
def uri_name_from_path(self, pkg, fn):
|
||||
# given a filename like .../pkg1/lib/bar/foo.js, and a package
|
||||
# specification (with a .root_dir like ".../pkg1" and a .lib list of
|
||||
# paths where .lib[0] is like "lib"), return the appropriate NAME
|
||||
# that can be put into a URI like resource://JID-pkg1-lib/NAME . This
|
||||
# will throw an exception if the file is outside of the lib/
|
||||
# directory, since that means we can't construct a URI that points to
|
||||
# it.
|
||||
#
|
||||
# This should be a lot easier, and shouldn't fail when the file is in
|
||||
# the root of the package. Both should become possible when the XPI
|
||||
# is rearranged and our URI scheme is simplified.
|
||||
fn = os.path.abspath(fn)
|
||||
pkglib = pkg.lib[0]
|
||||
libdir = os.path.abspath(os.path.join(pkg.root_dir, pkglib))
|
||||
# AARGH, section and name! we need to reverse-engineer a
|
||||
# ModuleInfo instance that will produce a URI (in the form
|
||||
# PREFIX/PKGNAME-SECTION/JS) that will map to the existing file.
|
||||
# Until we fix URI generation to get rid of "sections", this is
|
||||
# limited to files in the same .directories.lib as the rest of
|
||||
# the package uses. So if the package's main files are in lib/,
|
||||
# but the main.js is in the package root, there is no URI we can
|
||||
# construct that will point to it, and we must fail.
|
||||
#
|
||||
# This will become much easier (and the failure case removed)
|
||||
# when we get rid of sections and change the URIs to look like
|
||||
# (PREFIX/PKGNAME/PATH-TO-JS).
|
||||
|
||||
# AARGH 2, allowing .lib to be a list is really getting in the
|
||||
# way. That needs to go away eventually too.
|
||||
if not fn.startswith(libdir):
|
||||
raise UnreachablePrefixError("Sorry, but the 'main' file (%s) in package %s is outside that package's 'lib' directory (%s), so I cannot construct a URI to reach it."
|
||||
% (fn, pkg.name, pkglib))
|
||||
name = fn[len(libdir):].lstrip(SEP)[:-len(".js")]
|
||||
return name
|
||||
|
||||
|
||||
def parse_main(self, root_dir, main, check_lib_dir=None):
|
||||
# 'main' can be like one of the following:
|
||||
# a: ./lib/main.js b: ./lib/main c: lib/main
|
||||
# we require it to be a path to the file, though, and ignore the
|
||||
# .directories stuff. So just "main" is insufficient if you really
|
||||
# want something in a "lib/" subdirectory.
|
||||
if main.endswith(".js"):
|
||||
main = main[:-len(".js")]
|
||||
if main.startswith("./"):
|
||||
main = main[len("./"):]
|
||||
# package.json must always use "/", but on windows we'll replace that
|
||||
# with "\" before using it as an actual filename
|
||||
main = os.sep.join(main.split("/"))
|
||||
paths = [os.path.join(root_dir, main+".js")]
|
||||
if check_lib_dir is not None:
|
||||
paths.append(os.path.join(root_dir, check_lib_dir, main+".js"))
|
||||
return paths
|
||||
|
||||
def find_top_js(self, target_cfg):
|
||||
for libdir in target_cfg.lib:
|
||||
for n in self.parse_main(target_cfg.root_dir, target_cfg.main,
|
||||
libdir):
|
||||
if os.path.exists(n):
|
||||
return n
|
||||
raise KeyError("unable to find main module '%s.js' in top-level package" % target_cfg.main)
|
||||
|
||||
def find_top(self, target_cfg):
|
||||
top_js = self.find_top_js(target_cfg)
|
||||
n = os.path.join(target_cfg.root_dir, "README.md")
|
||||
if os.path.exists(n):
|
||||
top_docs = n
|
||||
else:
|
||||
top_docs = None
|
||||
name = self.uri_name_from_path(target_cfg, top_js)
|
||||
return ModuleInfo(target_cfg, "lib", name, top_js, top_docs)
|
||||
|
||||
def process_module(self, mi):
|
||||
pkg = mi.package
|
||||
#print "ENTERING", pkg.name, mi.name
|
||||
# mi.name must be fully-qualified
|
||||
assert (not mi.name.startswith("./") and
|
||||
not mi.name.startswith("../"))
|
||||
# create and claim the manifest row first
|
||||
me = self.get_manifest_entry(pkg.name, mi.section, mi.name)
|
||||
|
||||
me.add_js(mi.js)
|
||||
if mi.docs:
|
||||
me.add_docs(mi.docs)
|
||||
|
||||
js_lines = open(mi.js,"r").readlines()
|
||||
requires, problems, locations = scan_module(mi.js,js_lines,self.stderr)
|
||||
if problems:
|
||||
# the relevant instructions have already been written to stderr
|
||||
raise BadChromeMarkerError()
|
||||
|
||||
# We update our requirements on the way out of the depth-first
|
||||
# traversal of the module graph
|
||||
|
||||
for reqname in sorted(requires.keys()):
|
||||
# If requirement is chrome or a pseudo-module (starts with @) make
|
||||
# path a requirement name.
|
||||
if reqname == "chrome" or reqname.startswith("@"):
|
||||
me.add_requirement(reqname, reqname)
|
||||
else:
|
||||
# when two modules require() the same name, do they get a
|
||||
# shared instance? This is a deep question. For now say yes.
|
||||
|
||||
# find_req_for() returns an entry to put in our
|
||||
# 'requirements' dict, and will recursively process
|
||||
# everything transitively required from here. It will also
|
||||
# populate the self.modules[] cache. Note that we must
|
||||
# tolerate cycles in the reference graph.
|
||||
looked_in = [] # populated by subroutines
|
||||
them_me = self.find_req_for(mi, reqname, looked_in, locations)
|
||||
if them_me is None:
|
||||
if mi.section == "tests":
|
||||
# tolerate missing modules in tests, because
|
||||
# test-securable-module.js, and the modules/red.js
|
||||
# that it imports, both do that intentionally
|
||||
continue
|
||||
if reqname.endswith(".jsm"):
|
||||
# ignore JSM modules
|
||||
continue
|
||||
if not self.abort_on_missing:
|
||||
# print a warning, but tolerate missing modules
|
||||
# unless cfx --abort-on-missing-module flag was set
|
||||
print >>self.stderr, "Warning: missing module: %s" % reqname
|
||||
me.add_requirement(reqname, reqname)
|
||||
continue
|
||||
lineno = locations.get(reqname) # None means define()
|
||||
if lineno is None:
|
||||
reqtype = "define"
|
||||
else:
|
||||
reqtype = "require"
|
||||
err = ModuleNotFoundError(reqtype, reqname,
|
||||
mi.js, lineno, looked_in)
|
||||
raise err
|
||||
else:
|
||||
me.add_requirement(reqname, them_me)
|
||||
|
||||
return me
|
||||
#print "LEAVING", pkg.name, mi.name
|
||||
|
||||
def find_req_for(self, from_module, reqname, looked_in, locations):
|
||||
# handle a single require(reqname) statement from from_module .
|
||||
# Return a uri that exists in self.manifest
|
||||
# Populate looked_in with places we looked.
|
||||
def BAD(msg):
|
||||
return BadModuleIdentifier(msg + " in require(%s) from %s" %
|
||||
(reqname, from_module))
|
||||
|
||||
if not reqname:
|
||||
raise BAD("no actual modulename")
|
||||
|
||||
# Allow things in tests/*.js to require both test code and real code.
|
||||
# But things in lib/*.js can only require real code.
|
||||
if from_module.section == "tests":
|
||||
lookfor_sections = ["tests", "lib"]
|
||||
elif from_module.section == "lib":
|
||||
lookfor_sections = ["lib"]
|
||||
else:
|
||||
raise BadSection(from_module.section)
|
||||
modulename = from_module.name
|
||||
|
||||
#print " %s require(%s))" % (from_module, reqname)
|
||||
|
||||
if reqname.startswith("./") or reqname.startswith("../"):
|
||||
# 1: they want something relative to themselves, always from
|
||||
# their own package
|
||||
them = modulename.split("/")[:-1]
|
||||
bits = reqname.split("/")
|
||||
while bits[0] in (".", ".."):
|
||||
if not bits:
|
||||
raise BAD("no actual modulename")
|
||||
if bits[0] == "..":
|
||||
if not them:
|
||||
raise BAD("too many ..")
|
||||
them.pop()
|
||||
bits.pop(0)
|
||||
bits = them+bits
|
||||
lookfor_pkg = from_module.package.name
|
||||
lookfor_mod = "/".join(bits)
|
||||
return self._get_module_from_package(lookfor_pkg,
|
||||
lookfor_sections, lookfor_mod,
|
||||
looked_in)
|
||||
|
||||
# non-relative import. Might be a short name (requiring a search
|
||||
# through "library" packages), or a fully-qualified one.
|
||||
|
||||
if "/" in reqname:
|
||||
# 2: PKG/MOD: find PKG, look inside for MOD
|
||||
bits = reqname.split("/")
|
||||
lookfor_pkg = bits[0]
|
||||
lookfor_mod = "/".join(bits[1:])
|
||||
mi = self._get_module_from_package(lookfor_pkg,
|
||||
lookfor_sections, lookfor_mod,
|
||||
looked_in)
|
||||
if mi: # caution, 0==None
|
||||
return mi
|
||||
else:
|
||||
# 3: try finding PKG, if found, use its main.js entry point
|
||||
lookfor_pkg = reqname
|
||||
mi = self._get_entrypoint_from_package(lookfor_pkg, looked_in)
|
||||
if mi:
|
||||
return mi
|
||||
|
||||
# 4: search packages for MOD or MODPARENT/MODCHILD. We always search
|
||||
# their own package first, then the list of packages defined by their
|
||||
# .dependencies list
|
||||
from_pkg = from_module.package.name
|
||||
mi = self._search_packages_for_module(from_pkg,
|
||||
lookfor_sections, reqname,
|
||||
looked_in)
|
||||
if mi:
|
||||
return mi
|
||||
|
||||
# Only after we look for module in the addon itself, search for a module
|
||||
# in new layout.
|
||||
# First normalize require argument in order to easily find a mapping
|
||||
normalized = reqname
|
||||
if normalized.endswith(".js"):
|
||||
normalized = normalized[:-len(".js")]
|
||||
if normalized.startswith("addon-kit/"):
|
||||
normalized = normalized[len("addon-kit/"):]
|
||||
if normalized.startswith("api-utils/"):
|
||||
normalized = normalized[len("api-utils/"):]
|
||||
if normalized in NEW_LAYOUT_MAPPING:
|
||||
# get the new absolute path for this module
|
||||
original_reqname = reqname
|
||||
reqname = NEW_LAYOUT_MAPPING[normalized]
|
||||
from_pkg = from_module.package.name
|
||||
|
||||
# If the addon didn't explicitely told us to ignore deprecated
|
||||
# require path, warn the developer:
|
||||
# (target_cfg is the package.json file)
|
||||
if not "ignore-deprecated-path" in self.target_cfg:
|
||||
lineno = locations.get(original_reqname)
|
||||
print >>self.stderr, "Warning: Use of deprecated require path:"
|
||||
print >>self.stderr, " In %s:%d:" % (from_module.js, lineno)
|
||||
print >>self.stderr, " require('%s')." % original_reqname
|
||||
print >>self.stderr, " New path should be:"
|
||||
print >>self.stderr, " require('%s')" % reqname
|
||||
|
||||
return self._search_packages_for_module(from_pkg,
|
||||
lookfor_sections, reqname,
|
||||
looked_in)
|
||||
else:
|
||||
# We weren't able to find this module, really.
|
||||
return None
|
||||
|
||||
def _handle_module(self, mi):
|
||||
if not mi:
|
||||
return None
|
||||
|
||||
# we tolerate cycles in the reference graph, which means we need to
|
||||
# populate the self.modules cache before recursing into
|
||||
# process_module() . We must also check the cache first, so recursion
|
||||
# can terminate.
|
||||
if mi in self.modules:
|
||||
return self.modules[mi]
|
||||
|
||||
# this creates the entry
|
||||
new_entry = self.get_manifest_entry(mi.package.name, mi.section, mi.name)
|
||||
# and populates the cache
|
||||
self.modules[mi] = new_entry
|
||||
self.process_module(mi)
|
||||
return new_entry
|
||||
|
||||
def _get_module_from_package(self, pkgname, sections, modname, looked_in):
|
||||
if pkgname not in self.pkg_cfg.packages:
|
||||
return None
|
||||
mi = self._find_module_in_package(pkgname, sections, modname,
|
||||
looked_in)
|
||||
return self._handle_module(mi)
|
||||
|
||||
def _get_entrypoint_from_package(self, pkgname, looked_in):
|
||||
if pkgname not in self.pkg_cfg.packages:
|
||||
return None
|
||||
pkg = self.pkg_cfg.packages[pkgname]
|
||||
main = pkg.get("main", None)
|
||||
if not main:
|
||||
return None
|
||||
for js in self.parse_main(pkg.root_dir, main):
|
||||
looked_in.append(js)
|
||||
if os.path.exists(js):
|
||||
section = "lib"
|
||||
name = self.uri_name_from_path(pkg, js)
|
||||
docs = None
|
||||
mi = ModuleInfo(pkg, section, name, js, docs)
|
||||
return self._handle_module(mi)
|
||||
return None
|
||||
|
||||
def _search_packages_for_module(self, from_pkg, sections, reqname,
|
||||
looked_in):
|
||||
searchpath = [] # list of package names
|
||||
searchpath.append(from_pkg) # search self first
|
||||
us = self.pkg_cfg.packages[from_pkg]
|
||||
if 'dependencies' in us:
|
||||
# only look in dependencies
|
||||
searchpath.extend(us['dependencies'])
|
||||
else:
|
||||
# they didn't declare any dependencies (or they declared an empty
|
||||
# list, but we'll treat that as not declaring one, because it's
|
||||
# easier), so look in all deps, sorted alphabetically, so
|
||||
# addon-kit comes first. Note that self.deps includes all
|
||||
# packages found by traversing the ".dependencies" lists in each
|
||||
# package.json, starting from the main addon package, plus
|
||||
# everything added by --extra-packages
|
||||
searchpath.extend(sorted(self.deps))
|
||||
for pkgname in searchpath:
|
||||
mi = self._find_module_in_package(pkgname, sections, reqname,
|
||||
looked_in)
|
||||
if mi:
|
||||
return self._handle_module(mi)
|
||||
return None
|
||||
|
||||
def _find_module_in_package(self, pkgname, sections, name, looked_in):
|
||||
# require("a/b/c") should look at ...\a\b\c.js on windows
|
||||
filename = os.sep.join(name.split("/"))
|
||||
# normalize filename, make sure that we do not add .js if it already has
|
||||
# it.
|
||||
if not filename.endswith(".js") and not filename.endswith(".json"):
|
||||
filename += ".js"
|
||||
|
||||
if filename.endswith(".js"):
|
||||
basename = filename[:-3]
|
||||
if filename.endswith(".json"):
|
||||
basename = filename[:-5]
|
||||
|
||||
pkg = self.pkg_cfg.packages[pkgname]
|
||||
if isinstance(sections, basestring):
|
||||
sections = [sections]
|
||||
for section in sections:
|
||||
for sdir in pkg.get(section, []):
|
||||
js = os.path.join(pkg.root_dir, sdir, filename)
|
||||
looked_in.append(js)
|
||||
if os.path.exists(js):
|
||||
docs = None
|
||||
maybe_docs = os.path.join(pkg.root_dir, "docs",
|
||||
basename+".md")
|
||||
if section == "lib" and os.path.exists(maybe_docs):
|
||||
docs = maybe_docs
|
||||
return ModuleInfo(pkg, section, name, js, docs)
|
||||
return None
|
||||
|
||||
def build_manifest(target_cfg, pkg_cfg, deps, scan_tests,
|
||||
test_filter_re=None, extra_modules=[], abort_on_missing=False):
|
||||
"""
|
||||
Perform recursive dependency analysis starting from entry_point,
|
||||
building up a manifest of modules that need to be included in the XPI.
|
||||
Each entry will map require() names to the URL of the module that will
|
||||
be used to satisfy that dependency. The manifest will be used by the
|
||||
runtime's require() code.
|
||||
|
||||
This returns a ManifestBuilder object, with two public methods. The
|
||||
first, get_module_entries(), returns a set of ManifestEntry objects, each
|
||||
of which can be asked for the following:
|
||||
|
||||
* its contribution to the harness-options.json '.manifest'
|
||||
* the local disk name
|
||||
* the name in the XPI at which it should be placed
|
||||
|
||||
The second is get_data_entries(), which returns a set of DataEntry
|
||||
objects, each of which has:
|
||||
|
||||
* local disk name
|
||||
* name in the XPI
|
||||
|
||||
note: we don't build the XPI here, but our manifest is passed to the
|
||||
code which does, so it knows what to copy into the XPI.
|
||||
"""
|
||||
|
||||
mxt = ManifestBuilder(target_cfg, pkg_cfg, deps, extra_modules,
|
||||
abort_on_missing=abort_on_missing)
|
||||
mxt.build(scan_tests, test_filter_re)
|
||||
return mxt
|
||||
|
||||
|
||||
|
||||
COMMENT_PREFIXES = ["//", "/*", "*", "dump("]
|
||||
|
||||
REQUIRE_RE = r"(?<![\'\"])require\s*\(\s*[\'\"]([^\'\"]+?)[\'\"]\s*\)"
|
||||
|
||||
# detect the define idiom of the form:
|
||||
# define("module name", ["dep1", "dep2", "dep3"], function() {})
|
||||
# by capturing the contents of the list in a group.
|
||||
DEF_RE = re.compile(r"(require|define)\s*\(\s*([\'\"][^\'\"]+[\'\"]\s*,)?\s*\[([^\]]+)\]")
|
||||
|
||||
# Out of the async dependencies, do not allow quotes in them.
|
||||
DEF_RE_ALLOWED = re.compile(r"^[\'\"][^\'\"]+[\'\"]$")
|
||||
|
||||
def scan_requirements_with_grep(fn, lines):
|
||||
requires = {}
|
||||
first_location = {}
|
||||
for (lineno0, line) in enumerate(lines):
|
||||
for clause in line.split(";"):
|
||||
clause = clause.strip()
|
||||
iscomment = False
|
||||
for commentprefix in COMMENT_PREFIXES:
|
||||
if clause.startswith(commentprefix):
|
||||
iscomment = True
|
||||
if iscomment:
|
||||
continue
|
||||
mo = re.finditer(REQUIRE_RE, clause)
|
||||
if mo:
|
||||
for mod in mo:
|
||||
modname = mod.group(1)
|
||||
requires[modname] = {}
|
||||
if modname not in first_location:
|
||||
first_location[modname] = lineno0 + 1
|
||||
|
||||
# define() can happen across multiple lines, so join everyone up.
|
||||
wholeshebang = "\n".join(lines)
|
||||
for match in DEF_RE.finditer(wholeshebang):
|
||||
# this should net us a list of string literals separated by commas
|
||||
for strbit in match.group(3).split(","):
|
||||
strbit = strbit.strip()
|
||||
# There could be a trailing comma netting us just whitespace, so
|
||||
# filter that out. Make sure that only string values with
|
||||
# quotes around them are allowed, and no quotes are inside
|
||||
# the quoted value.
|
||||
if strbit and DEF_RE_ALLOWED.match(strbit):
|
||||
modname = strbit[1:-1]
|
||||
if modname not in ["exports"]:
|
||||
requires[modname] = {}
|
||||
# joining all the lines means we lose line numbers, so we
|
||||
# can't fill first_location[]
|
||||
|
||||
return requires, first_location
|
||||
|
||||
CHROME_ALIASES = [
|
||||
(re.compile(r"Components\.classes"), "Cc"),
|
||||
(re.compile(r"Components\.interfaces"), "Ci"),
|
||||
(re.compile(r"Components\.utils"), "Cu"),
|
||||
(re.compile(r"Components\.results"), "Cr"),
|
||||
(re.compile(r"Components\.manager"), "Cm"),
|
||||
]
|
||||
OTHER_CHROME = re.compile(r"Components\.[a-zA-Z]")
|
||||
|
||||
def scan_for_bad_chrome(fn, lines, stderr):
|
||||
problems = False
|
||||
old_chrome = set() # i.e. "Cc" when we see "Components.classes"
|
||||
old_chrome_lines = [] # list of (lineno, line.strip()) tuples
|
||||
for lineno,line in enumerate(lines):
|
||||
# note: this scanner is not obligated to spot all possible forms of
|
||||
# chrome access. The scanner is detecting voluntary requests for
|
||||
# chrome. Runtime tools will enforce allowance or denial of access.
|
||||
line = line.strip()
|
||||
iscomment = False
|
||||
for commentprefix in COMMENT_PREFIXES:
|
||||
if line.startswith(commentprefix):
|
||||
iscomment = True
|
||||
break
|
||||
if iscomment:
|
||||
continue
|
||||
old_chrome_in_this_line = set()
|
||||
for (regexp,alias) in CHROME_ALIASES:
|
||||
if regexp.search(line):
|
||||
old_chrome_in_this_line.add(alias)
|
||||
if not old_chrome_in_this_line:
|
||||
if OTHER_CHROME.search(line):
|
||||
old_chrome_in_this_line.add("components")
|
||||
old_chrome.update(old_chrome_in_this_line)
|
||||
if old_chrome_in_this_line:
|
||||
old_chrome_lines.append( (lineno+1, line) )
|
||||
|
||||
if old_chrome:
|
||||
print >>stderr, """
|
||||
The following lines from file %(fn)s:
|
||||
%(lines)s
|
||||
use 'Components' to access chrome authority. To do so, you need to add a
|
||||
line somewhat like the following:
|
||||
|
||||
const {%(needs)s} = require("chrome");
|
||||
|
||||
Then you can use any shortcuts to its properties that you import from the
|
||||
'chrome' module ('Cc', 'Ci', 'Cm', 'Cr', and 'Cu' for the 'classes',
|
||||
'interfaces', 'manager', 'results', and 'utils' properties, respectively. And
|
||||
`components` for `Components` object itself).
|
||||
""" % { "fn": fn, "needs": ",".join(sorted(old_chrome)),
|
||||
"lines": "\n".join([" %3d: %s" % (lineno,line)
|
||||
for (lineno, line) in old_chrome_lines]),
|
||||
}
|
||||
problems = True
|
||||
return problems
|
||||
|
||||
def scan_module(fn, lines, stderr=sys.stderr):
|
||||
filename = os.path.basename(fn)
|
||||
requires, locations = scan_requirements_with_grep(fn, lines)
|
||||
if filename == "cuddlefish.js":
|
||||
# this is the loader: don't scan for chrome
|
||||
problems = False
|
||||
else:
|
||||
problems = scan_for_bad_chrome(fn, lines, stderr)
|
||||
return requires, problems, locations
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
for fn in sys.argv[1:]:
|
||||
requires, problems, locations = scan_module(fn, open(fn).readlines())
|
||||
print
|
||||
print "---", fn
|
||||
if problems:
|
||||
print "PROBLEMS"
|
||||
sys.exit(1)
|
||||
print "requires: %s" % (",".join(sorted(requires.keys())))
|
||||
print "locations: %s" % locations
|
||||
@@ -1,48 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var { log } = console;
|
||||
|
||||
function startup(data, reason) {
|
||||
// This code allow to make all stdIO work
|
||||
try {
|
||||
Cu.import("resource://gre/modules/ctypes.jsm");
|
||||
let libdvm = ctypes.open("libdvm.so");
|
||||
let dvmStdioConverterStartup;
|
||||
// Starting with Android ICS, dalvik uses C++.
|
||||
// So that the symbol isn't a simple C one
|
||||
try {
|
||||
dvmStdioConverterStartup = libdvm.declare("_Z24dvmStdioConverterStartupv", ctypes.default_abi, ctypes.bool);
|
||||
}
|
||||
catch(e) {
|
||||
// Otherwise, before ICS, it was a pure C library
|
||||
dvmStdioConverterStartup = libdvm.declare("dvmStdioConverterStartup", ctypes.default_abi, ctypes.void_t);
|
||||
}
|
||||
dvmStdioConverterStartup();
|
||||
log("MU: console redirected to adb logcat.");
|
||||
} catch(e) {
|
||||
Cu.reportError("MU: unable to execute jsctype hack: "+e);
|
||||
}
|
||||
|
||||
try {
|
||||
let QuitObserver = {
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
Services.obs.removeObserver(QuitObserver, "quit-application");
|
||||
dump("MU: APPLICATION-QUIT\n");
|
||||
}
|
||||
};
|
||||
Services.obs.addObserver(QuitObserver, "quit-application", false);
|
||||
log("MU: ready to watch firefox exit.");
|
||||
} catch(e) {
|
||||
log("MU: unable to register quit-application observer: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
function install() {}
|
||||
function shutdown() {}
|
||||
@@ -1,39 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>mobile-utils@mozilla.com</em:id>
|
||||
<em:version>1.0</em:version>
|
||||
<em:type>2</em:type>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
|
||||
<!-- Fennec-XUL -->
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>{a23983c0-fd0e-11dc-95ff-0800200c9a66}</em:id>
|
||||
<em:minVersion>1</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
<!-- Fennec-NativeUI -->
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>{aa3c5121-dab2-40e2-81ca-7ea25febc110}</em:id>
|
||||
<em:minVersion>1</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Mobile Addon-SDK utility addon</em:name>
|
||||
<em:description>Allow better integration with cfx tool.</em:description>
|
||||
<em:creator>Mozilla Corporation</em:creator>
|
||||
|
||||
</Description>
|
||||
</RDF>
|
||||
@@ -1,463 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import copy
|
||||
|
||||
import simplejson as json
|
||||
from cuddlefish.bunch import Bunch
|
||||
|
||||
MANIFEST_NAME = 'package.json'
|
||||
DEFAULT_LOADER = 'addon-sdk'
|
||||
|
||||
# Is different from root_dir when running tests
|
||||
env_root = os.environ.get('CUDDLEFISH_ROOT')
|
||||
|
||||
DEFAULT_PROGRAM_MODULE = 'main'
|
||||
|
||||
DEFAULT_ICON = 'icon.png'
|
||||
DEFAULT_ICON64 = 'icon64.png'
|
||||
|
||||
METADATA_PROPS = ['name', 'description', 'keywords', 'author', 'version',
|
||||
'developers', 'translators', 'contributors', 'license', 'homepage',
|
||||
'icon', 'icon64', 'main', 'directories', 'permissions', 'preferences']
|
||||
|
||||
RESOURCE_HOSTNAME_RE = re.compile(r'^[a-z0-9_\-]+$')
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
class MalformedPackageError(Error):
|
||||
pass
|
||||
|
||||
class MalformedJsonFileError(Error):
|
||||
pass
|
||||
|
||||
class DuplicatePackageError(Error):
|
||||
pass
|
||||
|
||||
class PackageNotFoundError(Error):
|
||||
def __init__(self, missing_package, reason):
|
||||
self.missing_package = missing_package
|
||||
self.reason = reason
|
||||
def __str__(self):
|
||||
return "%s (%s)" % (self.missing_package, self.reason)
|
||||
|
||||
class BadChromeMarkerError(Error):
|
||||
pass
|
||||
|
||||
def validate_resource_hostname(name):
|
||||
"""
|
||||
Validates the given hostname for a resource: URI.
|
||||
|
||||
For more information, see:
|
||||
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=566812#c13
|
||||
|
||||
Examples:
|
||||
|
||||
>>> validate_resource_hostname('blarg')
|
||||
|
||||
>>> validate_resource_hostname('bl arg')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Error: the name of your package contains an invalid character.
|
||||
Package names can contain only lower-case letters, numbers, underscores, and dashes.
|
||||
Current package name: bl arg
|
||||
|
||||
>>> validate_resource_hostname('BLARG')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Error: the name of your package contains upper-case letters.
|
||||
Package names can contain only lower-case letters, numbers, underscores, and dashes.
|
||||
Current package name: BLARG
|
||||
|
||||
>>> validate_resource_hostname('foo@bar')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Error: the name of your package contains an invalid character.
|
||||
Package names can contain only lower-case letters, numbers, underscores, and dashes.
|
||||
Current package name: foo@bar
|
||||
"""
|
||||
|
||||
# See https://bugzilla.mozilla.org/show_bug.cgi?id=568131 for details.
|
||||
if not name.lower() == name:
|
||||
raise ValueError("""Error: the name of your package contains upper-case letters.
|
||||
Package names can contain only lower-case letters, numbers, underscores, and dashes.
|
||||
Current package name: %s""" % name)
|
||||
|
||||
if not RESOURCE_HOSTNAME_RE.match(name):
|
||||
raise ValueError("""Error: the name of your package contains an invalid character.
|
||||
Package names can contain only lower-case letters, numbers, underscores, and dashes.
|
||||
Current package name: %s""" % name)
|
||||
|
||||
def find_packages_with_module(pkg_cfg, name):
|
||||
# TODO: Make this support more than just top-level modules.
|
||||
filename = "%s.js" % name
|
||||
packages = []
|
||||
for cfg in pkg_cfg.packages.itervalues():
|
||||
if 'lib' in cfg:
|
||||
matches = [dirname for dirname in resolve_dirs(cfg, cfg.lib)
|
||||
if os.path.exists(os.path.join(dirname, filename))]
|
||||
if matches:
|
||||
packages.append(cfg.name)
|
||||
return packages
|
||||
|
||||
def resolve_dirs(pkg_cfg, dirnames):
|
||||
for dirname in dirnames:
|
||||
yield resolve_dir(pkg_cfg, dirname)
|
||||
|
||||
def resolve_dir(pkg_cfg, dirname):
|
||||
return os.path.join(pkg_cfg.root_dir, dirname)
|
||||
|
||||
def validate_permissions(perms):
|
||||
if (perms.get('cross-domain-content') and
|
||||
not isinstance(perms.get('cross-domain-content'), list)):
|
||||
raise ValueError("Error: `cross-domain-content` permissions in \
|
||||
package.json file must be an array of strings:\n %s" % perms)
|
||||
|
||||
def get_metadata(pkg_cfg, deps):
|
||||
metadata = Bunch()
|
||||
for pkg_name in deps:
|
||||
cfg = pkg_cfg.packages[pkg_name]
|
||||
metadata[pkg_name] = Bunch()
|
||||
for prop in METADATA_PROPS:
|
||||
if cfg.get(prop):
|
||||
if prop == 'permissions':
|
||||
validate_permissions(cfg[prop])
|
||||
metadata[pkg_name][prop] = cfg[prop]
|
||||
return metadata
|
||||
|
||||
def set_section_dir(base_json, name, base_path, dirnames, allow_root=False):
|
||||
resolved = compute_section_dir(base_json, base_path, dirnames, allow_root)
|
||||
if resolved:
|
||||
base_json[name] = os.path.abspath(resolved)
|
||||
|
||||
def compute_section_dir(base_json, base_path, dirnames, allow_root):
|
||||
# PACKAGE_JSON.lib is highest priority
|
||||
# then PACKAGE_JSON.directories.lib
|
||||
# then lib/ (if it exists)
|
||||
# then . (but only if allow_root=True)
|
||||
for dirname in dirnames:
|
||||
if base_json.get(dirname):
|
||||
return os.path.join(base_path, base_json[dirname])
|
||||
if "directories" in base_json:
|
||||
for dirname in dirnames:
|
||||
if dirname in base_json.directories:
|
||||
return os.path.join(base_path, base_json.directories[dirname])
|
||||
for dirname in dirnames:
|
||||
if os.path.isdir(os.path.join(base_path, dirname)):
|
||||
return os.path.join(base_path, dirname)
|
||||
if allow_root:
|
||||
return os.path.abspath(base_path)
|
||||
return None
|
||||
|
||||
def normalize_string_or_array(base_json, key):
|
||||
if base_json.get(key):
|
||||
if isinstance(base_json[key], basestring):
|
||||
base_json[key] = [base_json[key]]
|
||||
|
||||
def load_json_file(path):
|
||||
data = open(path, 'r').read()
|
||||
try:
|
||||
return Bunch(json.loads(data))
|
||||
except ValueError, e:
|
||||
raise MalformedJsonFileError('%s when reading "%s"' % (str(e),
|
||||
path))
|
||||
|
||||
def get_config_in_dir(path):
|
||||
package_json = os.path.join(path, MANIFEST_NAME)
|
||||
if not (os.path.exists(package_json) and
|
||||
os.path.isfile(package_json)):
|
||||
raise MalformedPackageError('%s not found in "%s"' % (MANIFEST_NAME,
|
||||
path))
|
||||
base_json = load_json_file(package_json)
|
||||
|
||||
if 'name' not in base_json:
|
||||
base_json.name = os.path.basename(path)
|
||||
|
||||
# later processing steps will expect to see the following keys in the
|
||||
# base_json that we return:
|
||||
#
|
||||
# name: name of the package
|
||||
# lib: list of directories with .js files
|
||||
# test: list of directories with test-*.js files
|
||||
# doc: list of directories with documentation .md files
|
||||
# data: list of directories with bundled arbitrary data files
|
||||
# packages: ?
|
||||
|
||||
if (not base_json.get('tests') and
|
||||
os.path.isdir(os.path.join(path, 'test'))):
|
||||
base_json['tests'] = 'test'
|
||||
|
||||
set_section_dir(base_json, 'lib', path, ['lib'], True)
|
||||
set_section_dir(base_json, 'tests', path, ['test', 'tests'], False)
|
||||
set_section_dir(base_json, 'doc', path, ['doc', 'docs'])
|
||||
set_section_dir(base_json, 'data', path, ['data'])
|
||||
set_section_dir(base_json, 'packages', path, ['packages'])
|
||||
set_section_dir(base_json, 'locale', path, ['locale'])
|
||||
|
||||
if (not base_json.get('icon') and
|
||||
os.path.isfile(os.path.join(path, DEFAULT_ICON))):
|
||||
base_json['icon'] = DEFAULT_ICON
|
||||
|
||||
if (not base_json.get('icon64') and
|
||||
os.path.isfile(os.path.join(path, DEFAULT_ICON64))):
|
||||
base_json['icon64'] = DEFAULT_ICON64
|
||||
|
||||
for key in ['lib', 'tests', 'dependencies', 'packages']:
|
||||
# TODO: lib/tests can be an array?? consider interaction with
|
||||
# compute_section_dir above
|
||||
normalize_string_or_array(base_json, key)
|
||||
|
||||
if 'main' not in base_json and 'lib' in base_json:
|
||||
for dirname in base_json['lib']:
|
||||
program = os.path.join(path, dirname,
|
||||
'%s.js' % DEFAULT_PROGRAM_MODULE)
|
||||
if os.path.exists(program):
|
||||
base_json['main'] = DEFAULT_PROGRAM_MODULE
|
||||
break
|
||||
|
||||
base_json.root_dir = path
|
||||
|
||||
if "dependencies" in base_json:
|
||||
deps = base_json["dependencies"]
|
||||
deps = [x for x in deps if x not in ["addon-kit", "api-utils"]]
|
||||
deps.append("addon-sdk")
|
||||
base_json["dependencies"] = deps
|
||||
|
||||
return base_json
|
||||
|
||||
def _is_same_file(a, b):
|
||||
if hasattr(os.path, 'samefile'):
|
||||
return os.path.samefile(a, b)
|
||||
return a == b
|
||||
|
||||
def build_config(root_dir, target_cfg, packagepath=[]):
|
||||
dirs_to_scan = [env_root] # root is addon-sdk dir, diff from root_dir in tests
|
||||
|
||||
def add_packages_from_config(pkgconfig):
|
||||
if 'packages' in pkgconfig:
|
||||
for package_dir in resolve_dirs(pkgconfig, pkgconfig.packages):
|
||||
dirs_to_scan.append(package_dir)
|
||||
|
||||
add_packages_from_config(target_cfg)
|
||||
|
||||
packages_dir = os.path.join(root_dir, 'packages')
|
||||
if os.path.exists(packages_dir) and os.path.isdir(packages_dir):
|
||||
dirs_to_scan.append(packages_dir)
|
||||
dirs_to_scan.extend(packagepath)
|
||||
|
||||
packages = Bunch({target_cfg.name: target_cfg})
|
||||
|
||||
while dirs_to_scan:
|
||||
packages_dir = dirs_to_scan.pop()
|
||||
if os.path.exists(os.path.join(packages_dir, "package.json")):
|
||||
package_paths = [packages_dir]
|
||||
else:
|
||||
package_paths = [os.path.join(packages_dir, dirname)
|
||||
for dirname in os.listdir(packages_dir)
|
||||
if not dirname.startswith('.')]
|
||||
package_paths = [dirname for dirname in package_paths
|
||||
if os.path.isdir(dirname)]
|
||||
|
||||
for path in package_paths:
|
||||
pkgconfig = get_config_in_dir(path)
|
||||
if pkgconfig.name in packages:
|
||||
otherpkg = packages[pkgconfig.name]
|
||||
if not _is_same_file(otherpkg.root_dir, path):
|
||||
raise DuplicatePackageError(path, otherpkg.root_dir)
|
||||
else:
|
||||
packages[pkgconfig.name] = pkgconfig
|
||||
add_packages_from_config(pkgconfig)
|
||||
|
||||
return Bunch(packages=packages)
|
||||
|
||||
def get_deps_for_targets(pkg_cfg, targets):
|
||||
visited = []
|
||||
deps_left = [[dep, None] for dep in list(targets)]
|
||||
|
||||
while deps_left:
|
||||
[dep, required_by] = deps_left.pop()
|
||||
if dep not in visited:
|
||||
visited.append(dep)
|
||||
if dep not in pkg_cfg.packages:
|
||||
required_reason = ("required by '%s'" % (required_by)) \
|
||||
if required_by is not None \
|
||||
else "specified as target"
|
||||
raise PackageNotFoundError(dep, required_reason)
|
||||
dep_cfg = pkg_cfg.packages[dep]
|
||||
deps_left.extend([[i, dep] for i in dep_cfg.get('dependencies', [])])
|
||||
deps_left.extend([[i, dep] for i in dep_cfg.get('extra_dependencies', [])])
|
||||
|
||||
return visited
|
||||
|
||||
def generate_build_for_target(pkg_cfg, target, deps,
|
||||
include_tests=True,
|
||||
include_dep_tests=False,
|
||||
is_running_tests=False,
|
||||
default_loader=DEFAULT_LOADER):
|
||||
|
||||
build = Bunch(# Contains section directories for all packages:
|
||||
packages=Bunch(),
|
||||
locale=Bunch()
|
||||
)
|
||||
|
||||
def add_section_to_build(cfg, section, is_code=False,
|
||||
is_data=False):
|
||||
if section in cfg:
|
||||
dirnames = cfg[section]
|
||||
if isinstance(dirnames, basestring):
|
||||
# This is just for internal consistency within this
|
||||
# function, it has nothing to do w/ a non-canonical
|
||||
# configuration dict.
|
||||
dirnames = [dirnames]
|
||||
for dirname in resolve_dirs(cfg, dirnames):
|
||||
# ensure that package name is valid
|
||||
try:
|
||||
validate_resource_hostname(cfg.name)
|
||||
except ValueError, err:
|
||||
print err
|
||||
sys.exit(1)
|
||||
# ensure that this package has an entry
|
||||
if not cfg.name in build.packages:
|
||||
build.packages[cfg.name] = Bunch()
|
||||
# detect duplicated sections
|
||||
if section in build.packages[cfg.name]:
|
||||
raise KeyError("package's section already defined",
|
||||
cfg.name, section)
|
||||
# Register this section (lib, data, tests)
|
||||
build.packages[cfg.name][section] = dirname
|
||||
|
||||
def add_locale_to_build(cfg):
|
||||
# Bug 730776: Ignore locales for addon-kit, that are only for unit tests
|
||||
if not is_running_tests and cfg.name == "addon-sdk":
|
||||
return
|
||||
|
||||
path = resolve_dir(cfg, cfg['locale'])
|
||||
files = os.listdir(path)
|
||||
for filename in files:
|
||||
fullpath = os.path.join(path, filename)
|
||||
if os.path.isfile(fullpath) and filename.endswith('.properties'):
|
||||
language = filename[:-len('.properties')]
|
||||
|
||||
from property_parser import parse_file, MalformedLocaleFileError
|
||||
try:
|
||||
content = parse_file(fullpath)
|
||||
except MalformedLocaleFileError, msg:
|
||||
print msg[0]
|
||||
sys.exit(1)
|
||||
|
||||
# Merge current locales into global locale hashtable.
|
||||
# Locale files only contains one big JSON object
|
||||
# that act as an hastable of:
|
||||
# "keys to translate" => "translated keys"
|
||||
if language in build.locale:
|
||||
merge = (build.locale[language].items() +
|
||||
content.items())
|
||||
build.locale[language] = Bunch(merge)
|
||||
else:
|
||||
build.locale[language] = content
|
||||
|
||||
def add_dep_to_build(dep):
|
||||
dep_cfg = pkg_cfg.packages[dep]
|
||||
add_section_to_build(dep_cfg, "lib", is_code=True)
|
||||
add_section_to_build(dep_cfg, "data", is_data=True)
|
||||
if include_tests and include_dep_tests:
|
||||
add_section_to_build(dep_cfg, "tests", is_code=True)
|
||||
if 'locale' in dep_cfg:
|
||||
add_locale_to_build(dep_cfg)
|
||||
if ("loader" in dep_cfg) and ("loader" not in build):
|
||||
build.loader = "%s/%s" % (dep,
|
||||
dep_cfg.loader)
|
||||
|
||||
target_cfg = pkg_cfg.packages[target]
|
||||
|
||||
if include_tests and not include_dep_tests:
|
||||
add_section_to_build(target_cfg, "tests", is_code=True)
|
||||
|
||||
for dep in deps:
|
||||
add_dep_to_build(dep)
|
||||
|
||||
if 'loader' not in build:
|
||||
add_dep_to_build(DEFAULT_LOADER)
|
||||
|
||||
if 'icon' in target_cfg:
|
||||
build['icon'] = os.path.join(target_cfg.root_dir, target_cfg.icon)
|
||||
del target_cfg['icon']
|
||||
|
||||
if 'icon64' in target_cfg:
|
||||
build['icon64'] = os.path.join(target_cfg.root_dir, target_cfg.icon64)
|
||||
del target_cfg['icon64']
|
||||
|
||||
if 'id' in target_cfg:
|
||||
# NOTE: logic duplicated from buildJID()
|
||||
jid = target_cfg['id']
|
||||
if not ('@' in jid or jid.startswith('{')):
|
||||
jid += '@jetpack'
|
||||
build['preferencesBranch'] = jid
|
||||
|
||||
if 'preferences-branch' in target_cfg:
|
||||
build['preferencesBranch'] = target_cfg['preferences-branch']
|
||||
|
||||
return build
|
||||
|
||||
def _get_files_in_dir(path):
|
||||
data = {}
|
||||
files = os.listdir(path)
|
||||
for filename in files:
|
||||
fullpath = os.path.join(path, filename)
|
||||
if os.path.isdir(fullpath):
|
||||
data[filename] = _get_files_in_dir(fullpath)
|
||||
else:
|
||||
try:
|
||||
info = os.stat(fullpath)
|
||||
data[filename] = ("file", dict(size=info.st_size))
|
||||
except OSError:
|
||||
pass
|
||||
return ("directory", data)
|
||||
|
||||
def build_pkg_index(pkg_cfg):
|
||||
pkg_cfg = copy.deepcopy(pkg_cfg)
|
||||
for pkg in pkg_cfg.packages:
|
||||
root_dir = pkg_cfg.packages[pkg].root_dir
|
||||
files = _get_files_in_dir(root_dir)
|
||||
pkg_cfg.packages[pkg].files = files
|
||||
try:
|
||||
readme = open(root_dir + '/README.md').read()
|
||||
pkg_cfg.packages[pkg].readme = readme
|
||||
except IOError:
|
||||
pass
|
||||
del pkg_cfg.packages[pkg].root_dir
|
||||
return pkg_cfg.packages
|
||||
|
||||
def build_pkg_cfg(root):
|
||||
pkg_cfg = build_config(root, Bunch(name='dummy'))
|
||||
del pkg_cfg.packages['dummy']
|
||||
return pkg_cfg
|
||||
|
||||
def call_plugins(pkg_cfg, deps):
|
||||
for dep in deps:
|
||||
dep_cfg = pkg_cfg.packages[dep]
|
||||
dirnames = dep_cfg.get('python-lib', [])
|
||||
for dirname in resolve_dirs(dep_cfg, dirnames):
|
||||
sys.path.append(dirname)
|
||||
module_names = dep_cfg.get('python-plugins', [])
|
||||
for module_name in module_names:
|
||||
module = __import__(module_name)
|
||||
module.init(root_dir=dep_cfg.root_dir)
|
||||
|
||||
def call_cmdline_tool(env_root, pkg_name):
|
||||
pkg_cfg = build_config(env_root, Bunch(name='dummy'))
|
||||
if pkg_name not in pkg_cfg.packages:
|
||||
print "This tool requires the '%s' package." % pkg_name
|
||||
sys.exit(1)
|
||||
cfg = pkg_cfg.packages[pkg_name]
|
||||
for dirname in resolve_dirs(cfg, cfg['python-lib']):
|
||||
sys.path.append(dirname)
|
||||
module_name = cfg.get('python-cmdline-tool')
|
||||
module = __import__(module_name)
|
||||
module.run()
|
||||
@@ -1,77 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os, sys
|
||||
import base64
|
||||
import simplejson as json
|
||||
|
||||
def create_jid():
|
||||
"""Return 'jid1-XYZ', where 'XYZ' is a randomly-generated string. (in the
|
||||
previous jid0- series, the string securely identified a specific public
|
||||
key). To get a suitable add-on ID, append '@jetpack' to this string.
|
||||
"""
|
||||
# per https://developer.mozilla.org/en/Install_Manifests#id all XPI id
|
||||
# values must either be in the form of a 128-bit GUID (crazy braces
|
||||
# and all) or in the form of an email address (crazy @ and all).
|
||||
# Firefox will refuse to install an add-on with an id that doesn't
|
||||
# match one of these forms. The actual regexp is at:
|
||||
# http://mxr.mozilla.org/mozilla-central/source/toolkit/mozapps/extensions/internal/XPIProvider.jsm#130
|
||||
# So the JID needs an @-suffix, and the only legal punctuation is
|
||||
# "-._". So we start with a base64 encoding, and replace the
|
||||
# punctuation (+/) with letters (AB), losing a few bits of integrity.
|
||||
|
||||
# even better: windows has a maximum path length limitation of 256
|
||||
# characters:
|
||||
# http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx
|
||||
# (unless all paths are prefixed with "\\?\", I kid you not). The
|
||||
# typical install will put add-on code in a directory like:
|
||||
# C:\Documents and Settings\<username>\Application Data\Mozilla\Firefox\Profiles\232353483.default\extensions\$JID\...
|
||||
# (which is 108 chars long without the $JID).
|
||||
# Then the unpacked XPI contains packaged resources like:
|
||||
# resources/$JID-api-utils-lib/main.js (35 chars plus the $JID)
|
||||
#
|
||||
# We create a random 80 bit string, base64 encode that (with
|
||||
# AB instead of +/ to be path-safe), then bundle it into
|
||||
# "jid1-XYZ@jetpack". This gives us 27 characters. The resulting
|
||||
# main.js will have a path length of 211 characters, leaving us 45
|
||||
# characters of margin.
|
||||
#
|
||||
# 80 bits is enough to generate one billion JIDs and still maintain lower
|
||||
# than a one-in-a-million chance of accidental collision. (1e9 JIDs is 30
|
||||
# bits, square for the "birthday-paradox" to get 60 bits, add 20 bits for
|
||||
# the one-in-a-million margin to get 80 bits)
|
||||
|
||||
# if length were no issue, we'd prefer to use this:
|
||||
h = os.urandom(80/8)
|
||||
s = base64.b64encode(h, "AB").strip("=")
|
||||
jid = "jid1-" + s
|
||||
return jid
|
||||
|
||||
def preflight_config(target_cfg, filename, stderr=sys.stderr):
|
||||
modified = False
|
||||
config = json.load(open(filename, 'r'))
|
||||
|
||||
if "id" not in config:
|
||||
print >>stderr, ("No 'id' in package.json: creating a new ID for you.")
|
||||
jid = create_jid()
|
||||
config["id"] = jid
|
||||
modified = True
|
||||
|
||||
if modified:
|
||||
i = 0
|
||||
backup = filename + ".backup"
|
||||
while os.path.exists(backup):
|
||||
if i > 1000:
|
||||
raise ValueError("I'm having problems finding a good name"
|
||||
" for the backup file. Please move %s out"
|
||||
" of the way and try again."
|
||||
% (filename + ".backup"))
|
||||
backup = filename + ".backup-%d" % i
|
||||
i += 1
|
||||
os.rename(filename, backup)
|
||||
new_json = json.dumps(config, indent=4)
|
||||
open(filename, 'w').write(new_json+"\n")
|
||||
return False, True
|
||||
|
||||
return True, False
|
||||
@@ -1,239 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DEFAULT_COMMON_PREFS = {
|
||||
# allow debug output via dump to be printed to the system console
|
||||
# (setting it here just in case, even though PlainTextConsole also
|
||||
# sets this preference)
|
||||
'browser.dom.window.dump.enabled': True,
|
||||
# warn about possibly incorrect code
|
||||
'javascript.options.showInConsole': True,
|
||||
|
||||
# Allow remote connections to the debugger
|
||||
'devtools.debugger.remote-enabled' : True,
|
||||
|
||||
'extensions.sdk.console.logLevel': 'info',
|
||||
|
||||
'extensions.checkCompatibility.nightly' : False,
|
||||
|
||||
# Disable extension updates and notifications.
|
||||
'extensions.update.enabled' : False,
|
||||
'lightweightThemes.update.enabled' : False,
|
||||
'extensions.update.notifyUser' : False,
|
||||
|
||||
# From:
|
||||
# http://hg.mozilla.org/mozilla-central/file/1dd81c324ac7/build/automation.py.in#l372
|
||||
# Only load extensions from the application and user profile.
|
||||
# AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION
|
||||
'extensions.enabledScopes' : 5,
|
||||
# Disable metadata caching for installed add-ons by default
|
||||
'extensions.getAddons.cache.enabled' : False,
|
||||
# Disable intalling any distribution add-ons
|
||||
'extensions.installDistroAddons' : False,
|
||||
# Allow installing extensions dropped into the profile folder
|
||||
'extensions.autoDisableScopes' : 10,
|
||||
|
||||
# shut up some warnings on `about:` page
|
||||
'app.releaseNotesURL': 'http://localhost/app-dummy/',
|
||||
'app.vendorURL': 'http://localhost/app-dummy/',
|
||||
}
|
||||
|
||||
DEFAULT_NO_CONNECTIONS_PREFS = {
|
||||
'toolkit.telemetry.enabled': False,
|
||||
'toolkit.telemetry.server': 'https://localhost/telemetry-dummy/',
|
||||
'app.update.auto' : False,
|
||||
'app.update.url': 'http://localhost/app-dummy/update',
|
||||
# Make sure GMPInstallManager won't hit the network.
|
||||
'media.gmp-gmpopenh264.autoupdate' : False,
|
||||
'media.gmp-manager.cert.checkAttributes' : False,
|
||||
'media.gmp-manager.cert.requireBuiltIn' : False,
|
||||
'media.gmp-manager.url' : 'http://localhost/media-dummy/gmpmanager',
|
||||
'media.gmp-manager.url.override': 'http://localhost/dummy-gmp-manager.xml',
|
||||
'media.gmp-manager.updateEnabled': False,
|
||||
'browser.aboutHomeSnippets.updateUrl': 'https://localhost/snippet-dummy',
|
||||
'browser.newtab.url' : 'about:blank',
|
||||
'browser.search.update': False,
|
||||
'browser.search.suggest.enabled' : False,
|
||||
'browser.safebrowsing.phishing.enabled' : False,
|
||||
'browser.safebrowsing.provider.google.updateURL': 'http://localhost/safebrowsing-dummy/update',
|
||||
'browser.safebrowsing.provider.google.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
|
||||
'browser.safebrowsing.malware.reportURL': 'http://localhost/safebrowsing-dummy/malwarereport',
|
||||
'browser.selfsupport.url': 'https://localhost/selfsupport-dummy',
|
||||
'browser.safebrowsing.provider.mozilla.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
|
||||
'browser.safebrowsing.provider.mozilla.updateURL': 'http://localhost/safebrowsing-dummy/update',
|
||||
|
||||
# Disable app update
|
||||
'app.update.enabled' : False,
|
||||
'app.update.staging.enabled': False,
|
||||
|
||||
# Disable about:newtab content fetch and ping
|
||||
'browser.newtabpage.directory.source': 'data:application/json,{"jetpack":1}',
|
||||
'browser.newtabpage.directory.ping': '',
|
||||
|
||||
# Point update checks to a nonexistent local URL for fast failures.
|
||||
'extensions.update.url' : 'http://localhost/extensions-dummy/updateURL',
|
||||
'extensions.update.background.url': 'http://localhost/extensions-dummy/updateBackgroundURL',
|
||||
'extensions.blocklist.url' : 'http://localhost/extensions-dummy/blocklistURL',
|
||||
# Make sure opening about:addons won't hit the network.
|
||||
'extensions.webservice.discoverURL' : 'http://localhost/extensions-dummy/discoveryURL',
|
||||
'extensions.getAddons.maxResults': 0,
|
||||
|
||||
# Disable webapp updates. Yes, it is supposed to be an integer.
|
||||
'browser.webapps.checkForUpdates': 0,
|
||||
|
||||
# Location services
|
||||
'geo.wifi.uri': 'http://localhost/location-dummy/locationURL',
|
||||
'browser.search.geoip.url': 'http://localhost/location-dummy/locationURL',
|
||||
|
||||
# Tell the search service we are running in the US. This also has the
|
||||
# desired side-effect of preventing our geoip lookup.
|
||||
'browser.search.isUS' : True,
|
||||
'browser.search.countryCode' : 'US',
|
||||
|
||||
'geo.wifi.uri' : 'http://localhost/extensions-dummy/geowifiURL',
|
||||
'geo.wifi.scan' : False,
|
||||
|
||||
# We don't want to hit the real Firefox Accounts server for tests. We don't
|
||||
# actually need a functioning FxA server, so just set it to something that
|
||||
# resolves and accepts requests, even if they all fail.
|
||||
'identity.fxaccounts.auth.uri': 'http://localhost/fxa-dummy/'
|
||||
}
|
||||
|
||||
DEFAULT_FENNEC_PREFS = {
|
||||
'browser.console.showInPanel': True,
|
||||
'browser.firstrun.show.uidiscovery': False
|
||||
}
|
||||
|
||||
# When launching a temporary new Firefox profile, use these preferences.
|
||||
DEFAULT_FIREFOX_PREFS = {
|
||||
'browser.startup.homepage' : 'about:blank',
|
||||
'startup.homepage_welcome_url' : 'about:blank',
|
||||
'devtools.browsertoolbox.panel': 'jsdebugger',
|
||||
'devtools.chrome.enabled' : True,
|
||||
|
||||
# From:
|
||||
# http://hg.mozilla.org/mozilla-central/file/1dd81c324ac7/build/automation.py.in#l388
|
||||
# Make url-classifier updates so rare that they won't affect tests.
|
||||
'urlclassifier.updateinterval' : 172800,
|
||||
# Point the url-classifier to a nonexistent local URL for fast failures.
|
||||
'browser.safebrowsing.provider.google.gethashURL' : 'http://localhost/safebrowsing-dummy/gethash',
|
||||
'browser.safebrowsing.provider.google.updateURL' : 'http://localhost/safebrowsing-dummy/update',
|
||||
'browser.safebrowsing.provider.mozilla.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
|
||||
'browser.safebrowsing.provider.mozilla.updateURL': 'http://localhost/safebrowsing-dummy/update',
|
||||
}
|
||||
|
||||
# When launching a temporary new Thunderbird profile, use these preferences.
|
||||
# Note that these were taken from:
|
||||
# http://dxr.mozilla.org/comm-central/source/mail/test/mozmill/runtest.py
|
||||
DEFAULT_THUNDERBIRD_PREFS = {
|
||||
# say no to slow script warnings
|
||||
'dom.max_chrome_script_run_time': 200,
|
||||
'dom.max_script_run_time': 0,
|
||||
# do not ask about being the default mail client
|
||||
'mail.shell.checkDefaultClient': False,
|
||||
# disable non-gloda indexing daemons
|
||||
'mail.winsearch.enable': False,
|
||||
'mail.winsearch.firstRunDone': True,
|
||||
'mail.spotlight.enable': False,
|
||||
'mail.spotlight.firstRunDone': True,
|
||||
# disable address books for undisclosed reasons
|
||||
'ldap_2.servers.osx.position': 0,
|
||||
'ldap_2.servers.oe.position': 0,
|
||||
# disable the first use junk dialog
|
||||
'mailnews.ui.junk.firstuse': False,
|
||||
# other unknown voodoo
|
||||
# -- dummied up local accounts to stop the account wizard
|
||||
'mail.account.account1.server' : "server1",
|
||||
'mail.account.account2.identities' : "id1",
|
||||
'mail.account.account2.server' : "server2",
|
||||
'mail.accountmanager.accounts' : "account1,account2",
|
||||
'mail.accountmanager.defaultaccount' : "account2",
|
||||
'mail.accountmanager.localfoldersserver' : "server1",
|
||||
'mail.identity.id1.fullName' : "Tinderbox",
|
||||
'mail.identity.id1.smtpServer' : "smtp1",
|
||||
'mail.identity.id1.useremail' : "tinderbox@invalid.com",
|
||||
'mail.identity.id1.valid' : True,
|
||||
'mail.root.none-rel' : "[ProfD]Mail",
|
||||
'mail.root.pop3-rel' : "[ProfD]Mail",
|
||||
'mail.server.server1.directory-rel' : "[ProfD]Mail/Local Folders",
|
||||
'mail.server.server1.hostname' : "Local Folders",
|
||||
'mail.server.server1.name' : "Local Folders",
|
||||
'mail.server.server1.type' : "none",
|
||||
'mail.server.server1.userName' : "nobody",
|
||||
'mail.server.server2.check_new_mail' : False,
|
||||
'mail.server.server2.directory-rel' : "[ProfD]Mail/tinderbox",
|
||||
'mail.server.server2.download_on_biff' : True,
|
||||
'mail.server.server2.hostname' : "tinderbox",
|
||||
'mail.server.server2.login_at_startup' : False,
|
||||
'mail.server.server2.name' : "tinderbox@invalid.com",
|
||||
'mail.server.server2.type' : "pop3",
|
||||
'mail.server.server2.userName' : "tinderbox",
|
||||
'mail.smtp.defaultserver' : "smtp1",
|
||||
'mail.smtpserver.smtp1.hostname' : "tinderbox",
|
||||
'mail.smtpserver.smtp1.username' : "tinderbox",
|
||||
'mail.smtpservers' : "smtp1",
|
||||
'mail.startup.enabledMailCheckOnce' : True,
|
||||
'mailnews.start_page_override.mstone' : "ignore",
|
||||
}
|
||||
|
||||
DEFAULT_TEST_PREFS = {
|
||||
'browser.console.showInPanel': True,
|
||||
'browser.startup.page': 0,
|
||||
'browser.firstrun.show.localepicker': False,
|
||||
'browser.firstrun.show.uidiscovery': False,
|
||||
'browser.ui.layout.tablet': 0,
|
||||
'dom.disable_open_during_load': False,
|
||||
'dom.experimental_forms': True,
|
||||
'dom.forms.number': True,
|
||||
'dom.forms.color': True,
|
||||
'dom.max_script_run_time': 0,
|
||||
'hangmonitor.timeout': 0,
|
||||
'dom.max_chrome_script_run_time': 0,
|
||||
'dom.popup_maximum': -1,
|
||||
'dom.send_after_paint_to_content': True,
|
||||
'dom.successive_dialog_time_limit': 0,
|
||||
'browser.shell.checkDefaultBrowser': False,
|
||||
'shell.checkDefaultClient': False,
|
||||
'browser.warnOnQuit': False,
|
||||
'accessibility.typeaheadfind.autostart': False,
|
||||
'browser.EULA.override': True,
|
||||
'gfx.color_management.force_srgb': True,
|
||||
'network.manage-offline-status': False,
|
||||
# Disable speculative connections so they aren't reported as leaking when they're hanging around.
|
||||
'network.http.speculative-parallel-limit': 0,
|
||||
'test.mousescroll': True,
|
||||
# Need to client auth test be w/o any dialogs
|
||||
'security.default_personal_cert': 'Select Automatically',
|
||||
'network.http.prompt-temp-redirect': False,
|
||||
'security.warn_viewing_mixed': False,
|
||||
'extensions.defaultProviders.enabled': True,
|
||||
'datareporting.policy.dataSubmissionPolicyBypassNotification': True,
|
||||
'layout.css.report_errors': True,
|
||||
'layout.css.grid.enabled': True,
|
||||
'layout.spammy_warnings.enabled': False,
|
||||
'dom.mozSettings.enabled': True,
|
||||
# Make sure the disk cache doesn't get auto disabled
|
||||
'network.http.bypass-cachelock-threshold': 200000,
|
||||
# Always use network provider for geolocation tests
|
||||
# so we bypass the OSX dialog raised by the corelocation provider
|
||||
'geo.provider.testing': True,
|
||||
# Background thumbnails in particular cause grief, and disabling thumbnails
|
||||
# in general can't hurt - we re-enable them when tests need them.
|
||||
'browser.pagethumbnails.capturing_disabled': True,
|
||||
# Indicate that the download panel has been shown once so that whichever
|
||||
# download test runs first doesn't show the popup inconsistently.
|
||||
'browser.download.panel.shown': True,
|
||||
# Assume the about:newtab page's intro panels have been shown to not depend on
|
||||
# which test runs first and happens to open about:newtab
|
||||
'browser.newtabpage.introShown': True,
|
||||
# Disable useragent updates.
|
||||
'general.useragent.updates.enabled': False,
|
||||
'media.eme.enabled': True,
|
||||
'media.eme.apiVisible': True,
|
||||
# Don't forceably kill content processes after a timeout
|
||||
'dom.ipc.tabs.shutdownTimeoutSecs': 0,
|
||||
'general.useragent.locale': "en-US",
|
||||
'intl.locale.matchOS': "en-US",
|
||||
'dom.indexedDB.experimental': True
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import re
|
||||
import codecs
|
||||
|
||||
class MalformedLocaleFileError(Exception):
|
||||
pass
|
||||
|
||||
def parse_file(path):
|
||||
return parse(read_file(path), path)
|
||||
|
||||
def read_file(path):
|
||||
try:
|
||||
return codecs.open( path, "r", "utf-8" ).readlines()
|
||||
except UnicodeDecodeError, e:
|
||||
raise MalformedLocaleFileError(
|
||||
'Following locale file is not a valid ' +
|
||||
'UTF-8 file: %s\n%s"' % (path, str(e)))
|
||||
|
||||
COMMENT = re.compile(r'\s*#')
|
||||
EMPTY = re.compile(r'^\s+$')
|
||||
KEYVALUE = re.compile(r"\s*([^=:]+)(=|:)\s*(.*)")
|
||||
|
||||
def parse(lines, path=None):
|
||||
lines = iter(lines)
|
||||
lineNo = 1
|
||||
pairs = dict()
|
||||
for line in lines:
|
||||
if COMMENT.match(line) or EMPTY.match(line) or len(line) == 0:
|
||||
continue
|
||||
m = KEYVALUE.match(line)
|
||||
if not m:
|
||||
raise MalformedLocaleFileError(
|
||||
'Following locale file is not a valid .properties file: %s\n'
|
||||
'Line %d is incorrect:\n%s' % (path, lineNo, line))
|
||||
|
||||
# All spaces are strip. Spaces at the beginning are stripped
|
||||
# by the regular expression. We have to strip spaces at the end.
|
||||
key = m.group(1).rstrip()
|
||||
val = m.group(3).rstrip()
|
||||
val = val.encode('raw-unicode-escape').decode('raw-unicode-escape')
|
||||
|
||||
# `key` can be empty when key is only made of spaces
|
||||
if not key:
|
||||
raise MalformedLocaleFileError(
|
||||
'Following locale file is not a valid .properties file: %s\n'
|
||||
'Key is invalid on line %d is incorrect:\n%s' %
|
||||
(path, lineNo, line))
|
||||
|
||||
# Multiline value: keep reading lines, while lines end with backslash
|
||||
# and strip spaces at the beginning of lines except the last line
|
||||
# that doesn't end up with backslash, we strip all spaces for this one.
|
||||
if val.endswith("\\"):
|
||||
val = val[:-1]
|
||||
try:
|
||||
# remove spaces before/after and especially the \n at EOL
|
||||
line = lines.next().strip()
|
||||
while line.endswith("\\"):
|
||||
val += line[:-1].lstrip()
|
||||
line = lines.next()
|
||||
lineNo += 1
|
||||
val += line.strip()
|
||||
except StopIteration:
|
||||
raise MalformedLocaleFileError(
|
||||
'Following locale file is not a valid .properties file: %s\n'
|
||||
'Unexpected EOF in multiline sequence at line %d:\n%s' %
|
||||
(path, lineNo, line))
|
||||
# Save this new pair
|
||||
pairs[key] = val
|
||||
lineNo += 1
|
||||
|
||||
normalize_plural(path, pairs)
|
||||
return pairs
|
||||
|
||||
# Plural forms in properties files are defined like this:
|
||||
# key = other form
|
||||
# key[one] = one form
|
||||
# key[...] = ...
|
||||
# Parse them and merge each key into one object containing all forms:
|
||||
# key: {
|
||||
# other: "other form",
|
||||
# one: "one form",
|
||||
# ...: ...
|
||||
# }
|
||||
PLURAL_FORM = re.compile(r'^(.*)\[(zero|one|two|few|many|other)\]$')
|
||||
def normalize_plural(path, pairs):
|
||||
for key in list(pairs.keys()):
|
||||
m = PLURAL_FORM.match(key)
|
||||
if not m:
|
||||
continue
|
||||
main_key = m.group(1)
|
||||
plural_form = m.group(2)
|
||||
# Allows not specifying a generic key (i.e a key without [form])
|
||||
if not main_key in pairs:
|
||||
pairs[main_key] = {}
|
||||
# Ensure that we always have the [other] form
|
||||
if not main_key + "[other]" in pairs:
|
||||
raise MalformedLocaleFileError(
|
||||
'Following locale file is not a valid UTF-8 file: %s\n'
|
||||
'This plural form doesn\'t have a matching `%s[other]` form:\n'
|
||||
'%s\n'
|
||||
'You have to defined following key:\n%s'
|
||||
% (path, main_key, key, main_key))
|
||||
# convert generic form into an object if it is still a string
|
||||
if isinstance(pairs[main_key], unicode):
|
||||
pairs[main_key] = {"other": pairs[main_key]}
|
||||
# then, add this new plural form
|
||||
pairs[main_key][plural_form] = pairs[key]
|
||||
del pairs[key]
|
||||
@@ -1,214 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
import xml.dom.minidom
|
||||
import StringIO
|
||||
|
||||
RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
EM_NS = "http://www.mozilla.org/2004/em-rdf#"
|
||||
|
||||
class RDF(object):
|
||||
def __str__(self):
|
||||
# real files have an .encoding attribute and use it when you
|
||||
# write() unicode into them: they read()/write() unicode and
|
||||
# put encoded bytes in the backend file. StringIO objects
|
||||
# read()/write() unicode and put unicode in the backing store,
|
||||
# so we must encode the output of getvalue() to get a
|
||||
# bytestring. (cStringIO objects are weirder: they effectively
|
||||
# have .encoding hardwired to "ascii" and put only bytes in
|
||||
# the backing store, so we can't use them here).
|
||||
#
|
||||
# The encoding= argument to dom.writexml() merely sets the XML header's
|
||||
# encoding= attribute. It still writes unencoded unicode to the output file,
|
||||
# so we have to encode it for real afterwards.
|
||||
#
|
||||
# Also see: https://bugzilla.mozilla.org/show_bug.cgi?id=567660
|
||||
|
||||
buf = StringIO.StringIO()
|
||||
self.dom.writexml(buf, encoding="utf-8")
|
||||
return buf.getvalue().encode('utf-8')
|
||||
|
||||
class RDFUpdate(RDF):
|
||||
def __init__(self):
|
||||
impl = xml.dom.minidom.getDOMImplementation()
|
||||
self.dom = impl.createDocument(RDF_NS, "RDF", None)
|
||||
self.dom.documentElement.setAttribute("xmlns", RDF_NS)
|
||||
self.dom.documentElement.setAttribute("xmlns:em", EM_NS)
|
||||
|
||||
def _make_node(self, name, value, parent):
|
||||
elem = self.dom.createElement(name)
|
||||
elem.appendChild(self.dom.createTextNode(value))
|
||||
parent.appendChild(elem)
|
||||
return elem
|
||||
|
||||
def add(self, manifest, update_link):
|
||||
desc = self.dom.createElement("Description")
|
||||
desc.setAttribute(
|
||||
"about",
|
||||
"urn:mozilla:extension:%s" % manifest.get("em:id")
|
||||
)
|
||||
self.dom.documentElement.appendChild(desc)
|
||||
|
||||
updates = self.dom.createElement("em:updates")
|
||||
desc.appendChild(updates)
|
||||
|
||||
seq = self.dom.createElement("Seq")
|
||||
updates.appendChild(seq)
|
||||
|
||||
li = self.dom.createElement("li")
|
||||
seq.appendChild(li)
|
||||
|
||||
li_desc = self.dom.createElement("Description")
|
||||
li.appendChild(li_desc)
|
||||
|
||||
self._make_node("em:version", manifest.get("em:version"),
|
||||
li_desc)
|
||||
|
||||
apps = manifest.dom.documentElement.getElementsByTagName(
|
||||
"em:targetApplication"
|
||||
)
|
||||
|
||||
for app in apps:
|
||||
target_app = self.dom.createElement("em:targetApplication")
|
||||
li_desc.appendChild(target_app)
|
||||
|
||||
ta_desc = self.dom.createElement("Description")
|
||||
target_app.appendChild(ta_desc)
|
||||
|
||||
for name in ["em:id", "em:minVersion", "em:maxVersion"]:
|
||||
elem = app.getElementsByTagName(name)[0]
|
||||
self._make_node(name, elem.firstChild.nodeValue, ta_desc)
|
||||
|
||||
self._make_node("em:updateLink", update_link, ta_desc)
|
||||
|
||||
class RDFManifest(RDF):
|
||||
def __init__(self, path):
|
||||
self.dom = xml.dom.minidom.parse(path)
|
||||
|
||||
def set(self, property, value):
|
||||
elements = self.dom.documentElement.getElementsByTagName(property)
|
||||
if not elements:
|
||||
raise ValueError("Element with value not found: %s" % property)
|
||||
if not elements[0].firstChild:
|
||||
elements[0].appendChild(self.dom.createTextNode(value))
|
||||
else:
|
||||
elements[0].firstChild.nodeValue = value
|
||||
|
||||
def get(self, property, default=None):
|
||||
elements = self.dom.documentElement.getElementsByTagName(property)
|
||||
if not elements:
|
||||
return default
|
||||
return elements[0].firstChild.nodeValue
|
||||
|
||||
def remove(self, property):
|
||||
elements = self.dom.documentElement.getElementsByTagName(property)
|
||||
if not elements:
|
||||
return True
|
||||
else:
|
||||
for i in elements:
|
||||
i.parentNode.removeChild(i);
|
||||
|
||||
return True;
|
||||
|
||||
def gen_manifest(template_root_dir, target_cfg, jid,
|
||||
update_url=None, bootstrap=True, enable_mobile=False):
|
||||
install_rdf = os.path.join(template_root_dir, "install.rdf")
|
||||
manifest = RDFManifest(install_rdf)
|
||||
dom = manifest.dom
|
||||
|
||||
manifest.set("em:id", jid)
|
||||
manifest.set("em:version",
|
||||
target_cfg.get('version', '1.0'))
|
||||
manifest.set("em:name",
|
||||
target_cfg.get('title', target_cfg.get('fullName', target_cfg['name'])))
|
||||
manifest.set("em:description",
|
||||
target_cfg.get("description", ""))
|
||||
manifest.set("em:creator",
|
||||
target_cfg.get("author", ""))
|
||||
manifest.set("em:bootstrap", str(bootstrap).lower())
|
||||
# XPIs remain packed by default, but package.json can override that. The
|
||||
# RDF format accepts "true" as True, anything else as False. We expect
|
||||
# booleans in the .json file, not strings.
|
||||
manifest.set("em:unpack", "true" if target_cfg.get("unpack") else "false")
|
||||
|
||||
if target_cfg.get('hasEmbeddedWebExtension', False):
|
||||
elem = dom.createElement("em:hasEmbeddedWebExtension");
|
||||
elem.appendChild(dom.createTextNode("true"))
|
||||
dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem)
|
||||
|
||||
for translator in target_cfg.get("translators", [ ]):
|
||||
elem = dom.createElement("em:translator");
|
||||
elem.appendChild(dom.createTextNode(translator))
|
||||
dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem)
|
||||
|
||||
for developer in target_cfg.get("developers", [ ]):
|
||||
elem = dom.createElement("em:developer");
|
||||
elem.appendChild(dom.createTextNode(developer))
|
||||
dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem)
|
||||
|
||||
for contributor in target_cfg.get("contributors", [ ]):
|
||||
elem = dom.createElement("em:contributor");
|
||||
elem.appendChild(dom.createTextNode(contributor))
|
||||
dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem)
|
||||
|
||||
if update_url:
|
||||
manifest.set("em:updateURL", update_url)
|
||||
else:
|
||||
manifest.remove("em:updateURL")
|
||||
|
||||
if target_cfg.get("preferences"):
|
||||
manifest.set("em:optionsType", "2")
|
||||
|
||||
# workaround until bug 971249 is fixed
|
||||
# https://bugzilla.mozilla.org/show_bug.cgi?id=971249
|
||||
manifest.set("em:optionsURL", "data:text/xml,<placeholder/>")
|
||||
|
||||
# workaround for workaround, for testing simple-prefs-regression
|
||||
if (os.path.exists(os.path.join(template_root_dir, "options.xul"))):
|
||||
manifest.remove("em:optionsURL")
|
||||
else:
|
||||
manifest.remove("em:optionsType")
|
||||
manifest.remove("em:optionsURL")
|
||||
|
||||
if enable_mobile:
|
||||
target_app = dom.createElement("em:targetApplication")
|
||||
dom.documentElement.getElementsByTagName("Description")[0].appendChild(target_app)
|
||||
|
||||
ta_desc = dom.createElement("Description")
|
||||
target_app.appendChild(ta_desc)
|
||||
|
||||
elem = dom.createElement("em:id")
|
||||
elem.appendChild(dom.createTextNode("{aa3c5121-dab2-40e2-81ca-7ea25febc110}"))
|
||||
ta_desc.appendChild(elem)
|
||||
|
||||
elem = dom.createElement("em:minVersion")
|
||||
elem.appendChild(dom.createTextNode("26.0"))
|
||||
ta_desc.appendChild(elem)
|
||||
|
||||
elem = dom.createElement("em:maxVersion")
|
||||
elem.appendChild(dom.createTextNode("30.0a1"))
|
||||
ta_desc.appendChild(elem)
|
||||
|
||||
if target_cfg.get("homepage"):
|
||||
manifest.set("em:homepageURL", target_cfg.get("homepage"))
|
||||
else:
|
||||
manifest.remove("em:homepageURL")
|
||||
|
||||
return manifest
|
||||
|
||||
if __name__ == "__main__":
|
||||
print "Running smoke test."
|
||||
root = os.path.join(os.path.dirname(__file__), '../../app-extension')
|
||||
manifest = gen_manifest(root, {'name': 'test extension'},
|
||||
'fakeid', 'http://foo.com/update.rdf')
|
||||
update = RDFUpdate()
|
||||
update.add(manifest, "https://foo.com/foo.xpi")
|
||||
exercise_str = str(manifest) + str(update)
|
||||
for tagname in ["em:targetApplication", "em:version", "em:id"]:
|
||||
if not len(update.dom.getElementsByTagName(tagname)):
|
||||
raise Exception("tag does not exist: %s" % tagname)
|
||||
if not update.dom.getElementsByTagName(tagname)[0].firstChild:
|
||||
raise Exception("tag has no children: %s" % tagname)
|
||||
print "Success!"
|
||||
@@ -1,767 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import tempfile
|
||||
import atexit
|
||||
import shlex
|
||||
import subprocess
|
||||
import re
|
||||
import shutil
|
||||
|
||||
import mozrunner
|
||||
from cuddlefish.prefs import DEFAULT_COMMON_PREFS
|
||||
from cuddlefish.prefs import DEFAULT_FIREFOX_PREFS
|
||||
from cuddlefish.prefs import DEFAULT_THUNDERBIRD_PREFS
|
||||
from cuddlefish.prefs import DEFAULT_FENNEC_PREFS
|
||||
from cuddlefish.prefs import DEFAULT_NO_CONNECTIONS_PREFS
|
||||
from cuddlefish.prefs import DEFAULT_TEST_PREFS
|
||||
|
||||
# Used to remove noise from ADB output
|
||||
CLEANUP_ADB = re.compile(r'^(I|E)/(stdout|stderr|GeckoConsole)\s*\(\s*\d+\):\s*(.*)$')
|
||||
# Used to filter only messages send by `console` module
|
||||
FILTER_ONLY_CONSOLE_FROM_ADB = re.compile(r'^I/(stdout|stderr)\s*\(\s*\d+\):\s*((info|warning|error|debug): .*)$')
|
||||
|
||||
# Used to detect the currently running test
|
||||
PARSEABLE_TEST_NAME = re.compile(r'TEST-START \| ([^\n]+)\n')
|
||||
|
||||
# Maximum time we'll wait for tests to finish, in seconds.
|
||||
# The purpose of this timeout is to recover from infinite loops. It should be
|
||||
# longer than the amount of time any test run takes, including those on slow
|
||||
# machines running slow (debug) versions of Firefox.
|
||||
RUN_TIMEOUT = 5400 #1.5 hours (1.5 * 60 * 60 sec)
|
||||
|
||||
# Maximum time we'll wait for tests to emit output, in seconds.
|
||||
# The purpose of this timeout is to recover from hangs. It should be longer
|
||||
# than the amount of time any test takes to report results.
|
||||
OUTPUT_TIMEOUT = 300 #five minutes (60 * 5 sec)
|
||||
|
||||
def follow_file(filename):
|
||||
"""
|
||||
Generator that yields the latest unread content from the given
|
||||
file, or None if no new content is available.
|
||||
|
||||
For example:
|
||||
|
||||
>>> f = open('temp.txt', 'w')
|
||||
>>> f.write('hello')
|
||||
>>> f.flush()
|
||||
>>> tail = follow_file('temp.txt')
|
||||
>>> tail.next()
|
||||
'hello'
|
||||
>>> tail.next() is None
|
||||
True
|
||||
>>> f.write('there')
|
||||
>>> f.flush()
|
||||
>>> tail.next()
|
||||
'there'
|
||||
>>> f.close()
|
||||
>>> os.remove('temp.txt')
|
||||
"""
|
||||
|
||||
last_pos = 0
|
||||
last_size = 0
|
||||
while True:
|
||||
newstuff = None
|
||||
if os.path.exists(filename):
|
||||
size = os.stat(filename).st_size
|
||||
if size > last_size:
|
||||
last_size = size
|
||||
f = open(filename, 'r')
|
||||
f.seek(last_pos)
|
||||
newstuff = f.read()
|
||||
last_pos = f.tell()
|
||||
f.close()
|
||||
yield newstuff
|
||||
|
||||
# subprocess.check_output only appeared in python2.7, so this code is taken
|
||||
# from python source code for compatibility with py2.5/2.6
|
||||
class CalledProcessError(Exception):
|
||||
def __init__(self, returncode, cmd, output=None):
|
||||
self.returncode = returncode
|
||||
self.cmd = cmd
|
||||
self.output = output
|
||||
def __str__(self):
|
||||
return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
|
||||
|
||||
def check_output(*popenargs, **kwargs):
|
||||
if 'stdout' in kwargs:
|
||||
raise ValueError('stdout argument not allowed, it will be overridden.')
|
||||
process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
|
||||
output, unused_err = process.communicate()
|
||||
retcode = process.poll()
|
||||
if retcode:
|
||||
cmd = kwargs.get("args")
|
||||
if cmd is None:
|
||||
cmd = popenargs[0]
|
||||
raise CalledProcessError(retcode, cmd, output=output)
|
||||
return output
|
||||
|
||||
|
||||
class FennecProfile(mozrunner.Profile):
|
||||
preferences = {}
|
||||
names = ['fennec']
|
||||
|
||||
FENNEC_REMOTE_PATH = '/mnt/sdcard/jetpack-profile'
|
||||
|
||||
class RemoteFennecRunner(mozrunner.Runner):
|
||||
profile_class = FennecProfile
|
||||
|
||||
names = ['fennec']
|
||||
|
||||
_INTENT_PREFIX = 'org.mozilla.'
|
||||
|
||||
_adb_path = None
|
||||
|
||||
def __init__(self, binary=None, **kwargs):
|
||||
# Check that we have a binary set
|
||||
if not binary:
|
||||
raise ValueError("You have to define `--binary` option set to the "
|
||||
"path to your ADB executable.")
|
||||
# Ensure that binary refer to a valid ADB executable
|
||||
output = subprocess.Popen([binary], stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE).communicate()
|
||||
output = "".join(output)
|
||||
if not ("Android Debug Bridge" in output):
|
||||
raise ValueError("`--binary` option should be the path to your "
|
||||
"ADB executable.")
|
||||
self.binary = binary
|
||||
|
||||
mobile_app_name = kwargs['cmdargs'][0]
|
||||
self.profile = kwargs['profile']
|
||||
self._adb_path = binary
|
||||
|
||||
# This pref has to be set to `false` otherwise, we do not receive
|
||||
# output of adb commands!
|
||||
subprocess.call([self._adb_path, "shell",
|
||||
"setprop log.redirect-stdio false"])
|
||||
|
||||
# Android apps are launched by their "intent" name,
|
||||
# Automatically detect already installed firefox by using `pm` program
|
||||
# or use name given as cfx `--mobile-app` argument.
|
||||
intents = self.getIntentNames()
|
||||
if not intents:
|
||||
raise ValueError("Unable to find any Firefox "
|
||||
"application on your device.")
|
||||
elif mobile_app_name:
|
||||
if not mobile_app_name in intents:
|
||||
raise ValueError("Unable to find Firefox application "
|
||||
"with intent name '%s'\n"
|
||||
"Available ones are: %s" %
|
||||
(mobile_app_name, ", ".join(intents)))
|
||||
self._intent_name = self._INTENT_PREFIX + mobile_app_name
|
||||
else:
|
||||
if "firefox" in intents:
|
||||
self._intent_name = self._INTENT_PREFIX + "firefox"
|
||||
elif "firefox_beta" in intents:
|
||||
self._intent_name = self._INTENT_PREFIX + "firefox_beta"
|
||||
elif "firefox_nightly" in intents:
|
||||
self._intent_name = self._INTENT_PREFIX + "firefox_nightly"
|
||||
else:
|
||||
self._intent_name = self._INTENT_PREFIX + intents[0]
|
||||
|
||||
print "Launching mobile application with intent name " + self._intent_name
|
||||
|
||||
# First try to kill firefox if it is already running
|
||||
pid = self.getProcessPID(self._intent_name)
|
||||
if pid != None:
|
||||
print "Killing running Firefox instance ..."
|
||||
subprocess.call([self._adb_path, "shell",
|
||||
"am force-stop " + self._intent_name])
|
||||
time.sleep(7)
|
||||
# It appears recently that the PID still exists even after
|
||||
# Fennec closes, so removing this error still allows the tests
|
||||
# to pass as the new Fennec instance is able to start.
|
||||
# Leaving error in but commented out for now.
|
||||
#
|
||||
#if self.getProcessPID(self._intent_name) != None:
|
||||
# raise Exception("Unable to automatically kill running Firefox" +
|
||||
# " instance. Please close it manually before " +
|
||||
# "executing cfx.")
|
||||
|
||||
print "Pushing the addon to your device"
|
||||
|
||||
# Create a clean empty profile on the sd card
|
||||
subprocess.call([self._adb_path, "shell", "rm -r " + FENNEC_REMOTE_PATH])
|
||||
subprocess.call([self._adb_path, "shell", "mkdir " + FENNEC_REMOTE_PATH])
|
||||
|
||||
# Push the profile folder created by mozrunner to the device
|
||||
# (we can't simply use `adb push` as it doesn't copy empty folders)
|
||||
localDir = self.profile.profile
|
||||
remoteDir = FENNEC_REMOTE_PATH
|
||||
for root, dirs, files in os.walk(localDir, followlinks='true'):
|
||||
relRoot = os.path.relpath(root, localDir)
|
||||
# Note about os.path usage below:
|
||||
# Local files may be using Windows `\` separators but
|
||||
# remote are always `/`, so we need to convert local ones to `/`
|
||||
for file in files:
|
||||
localFile = os.path.join(root, file)
|
||||
remoteFile = remoteDir.replace("/", os.sep)
|
||||
if relRoot != ".":
|
||||
remoteFile = os.path.join(remoteFile, relRoot)
|
||||
remoteFile = os.path.join(remoteFile, file)
|
||||
remoteFile = "/".join(remoteFile.split(os.sep))
|
||||
subprocess.Popen([self._adb_path, "push", localFile, remoteFile],
|
||||
stderr=subprocess.PIPE).wait()
|
||||
for dir in dirs:
|
||||
targetDir = remoteDir.replace("/", os.sep)
|
||||
if relRoot != ".":
|
||||
targetDir = os.path.join(targetDir, relRoot)
|
||||
targetDir = os.path.join(targetDir, dir)
|
||||
targetDir = "/".join(targetDir.split(os.sep))
|
||||
# `-p` option is not supported on all devices!
|
||||
subprocess.call([self._adb_path, "shell", "mkdir " + targetDir])
|
||||
|
||||
@property
|
||||
def command(self):
|
||||
"""Returns the command list to run."""
|
||||
return [self._adb_path,
|
||||
"shell",
|
||||
"am start " +
|
||||
"-a android.activity.MAIN " +
|
||||
"-n " + self._intent_name + "/" + self._intent_name + ".App " +
|
||||
"--es args \"-profile " + FENNEC_REMOTE_PATH + "\""
|
||||
]
|
||||
|
||||
def start(self):
|
||||
subprocess.call(self.command)
|
||||
|
||||
def getProcessPID(self, processName):
|
||||
p = subprocess.Popen([self._adb_path, "shell", "ps"],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
line = p.stdout.readline()
|
||||
while line:
|
||||
columns = line.split()
|
||||
pid = columns[1]
|
||||
name = columns[-1]
|
||||
line = p.stdout.readline()
|
||||
if processName in name:
|
||||
return pid
|
||||
return None
|
||||
|
||||
def getIntentNames(self):
|
||||
p = subprocess.Popen([self._adb_path, "shell", "pm list packages"],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
names = []
|
||||
for line in p.stdout.readlines():
|
||||
line = re.sub("(^package:)|\s", "", line)
|
||||
if self._INTENT_PREFIX in line:
|
||||
names.append(line.replace(self._INTENT_PREFIX, ""))
|
||||
return names
|
||||
|
||||
|
||||
class XulrunnerAppProfile(mozrunner.Profile):
|
||||
preferences = {}
|
||||
names = []
|
||||
|
||||
class XulrunnerAppRunner(mozrunner.Runner):
|
||||
"""
|
||||
Runner for any XULRunner app. Can use a Firefox binary in XULRunner
|
||||
mode to execute the app, or can use XULRunner itself. Expects the
|
||||
app's application.ini to be passed in as one of the items in
|
||||
'cmdargs' in the constructor.
|
||||
|
||||
This class relies a lot on the particulars of mozrunner.Runner's
|
||||
implementation, and does some unfortunate acrobatics to get around
|
||||
some of the class' limitations/assumptions.
|
||||
"""
|
||||
|
||||
profile_class = XulrunnerAppProfile
|
||||
|
||||
# This is a default, and will be overridden in the instance if
|
||||
# Firefox is used in XULRunner mode.
|
||||
names = ['xulrunner']
|
||||
|
||||
# Default location of XULRunner on OS X.
|
||||
__DARWIN_PATH = "/Library/Frameworks/XUL.framework/xulrunner-bin"
|
||||
__LINUX_PATH = "/usr/bin/xulrunner"
|
||||
|
||||
# What our application.ini's path looks like if it's part of
|
||||
# an "installed" XULRunner app on OS X.
|
||||
__DARWIN_APP_INI_SUFFIX = '.app/Contents/Resources/application.ini'
|
||||
|
||||
def __init__(self, binary=None, **kwargs):
|
||||
if sys.platform == 'darwin' and binary and binary.endswith('.app'):
|
||||
# Assume it's a Firefox app dir.
|
||||
binary = os.path.join(binary, 'Contents/MacOS/firefox-bin')
|
||||
|
||||
self.__app_ini = None
|
||||
self.__real_binary = binary
|
||||
|
||||
mozrunner.Runner.__init__(self, **kwargs)
|
||||
|
||||
# See if we're using a genuine xulrunner-bin from the XULRunner SDK,
|
||||
# or if we're being asked to use Firefox in XULRunner mode.
|
||||
self.__is_xulrunner_sdk = 'xulrunner' in self.binary
|
||||
|
||||
if sys.platform == 'linux2' and not self.env.get('LD_LIBRARY_PATH'):
|
||||
self.env['LD_LIBRARY_PATH'] = os.path.dirname(self.binary)
|
||||
|
||||
newargs = []
|
||||
for item in self.cmdargs:
|
||||
if 'application.ini' in item:
|
||||
self.__app_ini = item
|
||||
else:
|
||||
newargs.append(item)
|
||||
self.cmdargs = newargs
|
||||
|
||||
if not self.__app_ini:
|
||||
raise ValueError('application.ini not found in cmdargs')
|
||||
if not os.path.exists(self.__app_ini):
|
||||
raise ValueError("file does not exist: '%s'" % self.__app_ini)
|
||||
|
||||
if (sys.platform == 'darwin' and
|
||||
self.binary == self.__DARWIN_PATH and
|
||||
self.__app_ini.endswith(self.__DARWIN_APP_INI_SUFFIX)):
|
||||
# If the application.ini is in an app bundle, then
|
||||
# it could be inside an "installed" XULRunner app.
|
||||
# If this is the case, use the app's actual
|
||||
# binary instead of the XUL framework's, so we get
|
||||
# a proper app icon, etc.
|
||||
new_binary = '/'.join(self.__app_ini.split('/')[:-2] +
|
||||
['MacOS', 'xulrunner'])
|
||||
if os.path.exists(new_binary):
|
||||
self.binary = new_binary
|
||||
|
||||
@property
|
||||
def command(self):
|
||||
"""Returns the command list to run."""
|
||||
|
||||
if self.__is_xulrunner_sdk:
|
||||
return [self.binary, self.__app_ini, '-profile',
|
||||
self.profile.profile]
|
||||
else:
|
||||
return [self.binary, '-app', self.__app_ini, '-profile',
|
||||
self.profile.profile]
|
||||
|
||||
def __find_xulrunner_binary(self):
|
||||
if sys.platform == 'darwin':
|
||||
if os.path.exists(self.__DARWIN_PATH):
|
||||
return self.__DARWIN_PATH
|
||||
if sys.platform == 'linux2':
|
||||
if os.path.exists(self.__LINUX_PATH):
|
||||
return self.__LINUX_PATH
|
||||
return None
|
||||
|
||||
def find_binary(self):
|
||||
# This gets called by the superclass constructor. It will
|
||||
# always get called, even if a binary was passed into the
|
||||
# constructor, because we want to have full control over
|
||||
# what the exact setting of self.binary is.
|
||||
|
||||
if not self.__real_binary:
|
||||
self.__real_binary = self.__find_xulrunner_binary()
|
||||
if not self.__real_binary:
|
||||
dummy_profile = {}
|
||||
runner = mozrunner.FirefoxRunner(profile=dummy_profile)
|
||||
self.__real_binary = runner.find_binary()
|
||||
self.names = runner.names
|
||||
return self.__real_binary
|
||||
|
||||
def set_overloaded_modules(env_root, app_type, addon_id, preferences, overloads):
|
||||
# win32 file scheme needs 3 slashes
|
||||
desktop_file_scheme = "file://"
|
||||
if not env_root.startswith("/"):
|
||||
desktop_file_scheme = desktop_file_scheme + "/"
|
||||
|
||||
pref_prefix = "extensions.modules." + addon_id + ".path"
|
||||
|
||||
# Set preferences that will map require prefix to a given path
|
||||
for name, path in overloads.items():
|
||||
if len(name) == 0:
|
||||
prefName = pref_prefix
|
||||
else:
|
||||
prefName = pref_prefix + "." + name
|
||||
if app_type == "fennec-on-device":
|
||||
# For testing on device, we have to copy overloaded files from fs
|
||||
# to the device and use device path instead of local fs path.
|
||||
# Actual copy of files if done after the call to Profile constructor
|
||||
preferences[prefName] = "file://" + \
|
||||
FENNEC_REMOTE_PATH + "/overloads/" + name
|
||||
else:
|
||||
preferences[prefName] = desktop_file_scheme + \
|
||||
path.replace("\\", "/") + "/"
|
||||
|
||||
def run_app(harness_root_dir, manifest_rdf, harness_options,
|
||||
app_type, binary=None, profiledir=None, verbose=False,
|
||||
parseable=False, enforce_timeouts=False,
|
||||
logfile=None, addons=None, args=None, extra_environment={},
|
||||
norun=None, noquit=None,
|
||||
used_files=None, enable_mobile=False,
|
||||
mobile_app_name=None,
|
||||
env_root=None,
|
||||
is_running_tests=False,
|
||||
overload_modules=False,
|
||||
bundle_sdk=True,
|
||||
pkgdir="",
|
||||
enable_e10s=False,
|
||||
no_connections=False):
|
||||
if binary:
|
||||
binary = os.path.expanduser(binary)
|
||||
|
||||
if addons is None:
|
||||
addons = []
|
||||
else:
|
||||
addons = list(addons)
|
||||
|
||||
cmdargs = []
|
||||
preferences = dict(DEFAULT_COMMON_PREFS)
|
||||
|
||||
if is_running_tests:
|
||||
preferences.update(DEFAULT_TEST_PREFS)
|
||||
|
||||
if no_connections:
|
||||
preferences.update(DEFAULT_NO_CONNECTIONS_PREFS)
|
||||
|
||||
if enable_e10s:
|
||||
preferences['browser.tabs.remote.autostart'] = True
|
||||
else:
|
||||
preferences['browser.tabs.remote.autostart'] = False
|
||||
preferences['browser.tabs.remote.autostart.1'] = False
|
||||
preferences['browser.tabs.remote.autostart.2'] = False
|
||||
|
||||
# For now, only allow running on Mobile with --force-mobile argument
|
||||
if app_type in ["fennec-on-device"] and not enable_mobile:
|
||||
print """
|
||||
WARNING: Firefox Mobile support is still experimental.
|
||||
If you would like to run an addon on this platform, use --force-mobile flag:
|
||||
|
||||
cfx --force-mobile"""
|
||||
return 0
|
||||
|
||||
if app_type == "fennec-on-device":
|
||||
profile_class = FennecProfile
|
||||
preferences.update(DEFAULT_FENNEC_PREFS)
|
||||
runner_class = RemoteFennecRunner
|
||||
# We pass the intent name through command arguments
|
||||
cmdargs.append(mobile_app_name)
|
||||
elif app_type == "xulrunner":
|
||||
profile_class = XulrunnerAppProfile
|
||||
runner_class = XulrunnerAppRunner
|
||||
cmdargs.append(os.path.join(harness_root_dir, 'application.ini'))
|
||||
elif app_type == "firefox":
|
||||
profile_class = mozrunner.FirefoxProfile
|
||||
preferences.update(DEFAULT_FIREFOX_PREFS)
|
||||
runner_class = mozrunner.FirefoxRunner
|
||||
elif app_type == "thunderbird":
|
||||
profile_class = mozrunner.ThunderbirdProfile
|
||||
preferences.update(DEFAULT_THUNDERBIRD_PREFS)
|
||||
runner_class = mozrunner.ThunderbirdRunner
|
||||
else:
|
||||
raise ValueError("Unknown app: %s" % app_type)
|
||||
if sys.platform == 'darwin' and app_type != 'xulrunner':
|
||||
cmdargs.append('-foreground')
|
||||
|
||||
if args:
|
||||
cmdargs.extend(shlex.split(args))
|
||||
|
||||
# TODO: handle logs on remote device
|
||||
if app_type != "fennec-on-device":
|
||||
# tempfile.gettempdir() was constant, preventing two simultaneous "cfx
|
||||
# run"/"cfx test" on the same host. On unix it points at /tmp (which is
|
||||
# world-writeable), enabling a symlink attack (e.g. imagine some bad guy
|
||||
# does 'ln -s ~/.ssh/id_rsa /tmp/harness_result'). NamedTemporaryFile
|
||||
# gives us a unique filename that fixes both problems. We leave the
|
||||
# (0-byte) file in place until the browser-side code starts writing to
|
||||
# it, otherwise the symlink attack becomes possible again.
|
||||
fileno,resultfile = tempfile.mkstemp(prefix="harness-result-")
|
||||
os.close(fileno)
|
||||
harness_options['resultFile'] = resultfile
|
||||
|
||||
def maybe_remove_logfile():
|
||||
if os.path.exists(logfile):
|
||||
os.remove(logfile)
|
||||
|
||||
logfile_tail = None
|
||||
|
||||
# We always buffer output through a logfile for two reasons:
|
||||
# 1. On Windows, it's the only way to print console output to stdout/err.
|
||||
# 2. It enables us to keep track of the last time output was emitted,
|
||||
# so we can raise an exception if the test runner hangs.
|
||||
if not logfile:
|
||||
fileno,logfile = tempfile.mkstemp(prefix="harness-log-")
|
||||
os.close(fileno)
|
||||
logfile_tail = follow_file(logfile)
|
||||
atexit.register(maybe_remove_logfile)
|
||||
|
||||
logfile = os.path.abspath(os.path.expanduser(logfile))
|
||||
maybe_remove_logfile()
|
||||
|
||||
env = {}
|
||||
env.update(os.environ)
|
||||
if no_connections:
|
||||
env['MOZ_DISABLE_NONLOCAL_CONNECTIONS'] = '1'
|
||||
env['MOZ_NO_REMOTE'] = '1'
|
||||
env['XPCOM_DEBUG_BREAK'] = 'stack'
|
||||
env.update(extra_environment)
|
||||
if norun:
|
||||
cmdargs.append("-no-remote")
|
||||
|
||||
# Create the addon XPI so mozrunner will copy it to the profile it creates.
|
||||
# We delete it below after getting mozrunner to create the profile.
|
||||
from cuddlefish.xpi import build_xpi
|
||||
xpi_path = tempfile.mktemp(suffix='cfx-tmp.xpi')
|
||||
build_xpi(template_root_dir=harness_root_dir,
|
||||
manifest=manifest_rdf,
|
||||
xpi_path=xpi_path,
|
||||
harness_options=harness_options,
|
||||
limit_to=used_files,
|
||||
bundle_sdk=bundle_sdk,
|
||||
pkgdir=pkgdir)
|
||||
addons.append(xpi_path)
|
||||
|
||||
starttime = last_output_time = time.time()
|
||||
|
||||
# Redirect runner output to a file so we can catch output not generated
|
||||
# by us.
|
||||
# In theory, we could do this using simple redirection on all platforms
|
||||
# other than Windows, but this way we only have a single codepath to
|
||||
# maintain.
|
||||
fileno,outfile = tempfile.mkstemp(prefix="harness-stdout-")
|
||||
os.close(fileno)
|
||||
outfile_tail = follow_file(outfile)
|
||||
def maybe_remove_outfile():
|
||||
if os.path.exists(outfile):
|
||||
try:
|
||||
os.remove(outfile)
|
||||
except Exception, e:
|
||||
print "Error Cleaning up: " + str(e)
|
||||
atexit.register(maybe_remove_outfile)
|
||||
outf = open(outfile, "w")
|
||||
popen_kwargs = { 'stdout': outf, 'stderr': outf}
|
||||
|
||||
profile = None
|
||||
|
||||
if app_type == "fennec-on-device":
|
||||
# Install a special addon when we run firefox on mobile device
|
||||
# in order to be able to kill it
|
||||
mydir = os.path.dirname(os.path.abspath(__file__))
|
||||
addon_dir = os.path.join(mydir, "mobile-utils")
|
||||
addons.append(addon_dir)
|
||||
|
||||
# Overload addon-specific commonjs modules path with lib/ folder
|
||||
overloads = dict()
|
||||
if overload_modules:
|
||||
overloads[""] = os.path.join(env_root, "lib")
|
||||
|
||||
# Overload tests/ mapping with test/ folder, only when running test
|
||||
if is_running_tests:
|
||||
overloads["tests"] = os.path.join(env_root, "test")
|
||||
|
||||
set_overloaded_modules(env_root, app_type, harness_options["jetpackID"], \
|
||||
preferences, overloads)
|
||||
|
||||
# the XPI file is copied into the profile here
|
||||
profile = profile_class(addons=addons,
|
||||
profile=profiledir,
|
||||
preferences=preferences)
|
||||
|
||||
# Delete the temporary xpi file
|
||||
os.remove(xpi_path)
|
||||
|
||||
# Copy overloaded files registered in set_overloaded_modules
|
||||
# For testing on device, we have to copy overloaded files from fs
|
||||
# to the device and use device path instead of local fs path.
|
||||
# (has to be done after the call to profile_class() which eventualy creates
|
||||
# profile folder)
|
||||
if app_type == "fennec-on-device":
|
||||
profile_path = profile.profile
|
||||
for name, path in overloads.items():
|
||||
shutil.copytree(path, \
|
||||
os.path.join(profile_path, "overloads", name))
|
||||
|
||||
runner = runner_class(profile=profile,
|
||||
binary=binary,
|
||||
env=env,
|
||||
cmdargs=cmdargs,
|
||||
kp_kwargs=popen_kwargs)
|
||||
|
||||
sys.stdout.flush(); sys.stderr.flush()
|
||||
|
||||
if app_type == "fennec-on-device":
|
||||
if not enable_mobile:
|
||||
print >>sys.stderr, """
|
||||
WARNING: Firefox Mobile support is still experimental.
|
||||
If you would like to run an addon on this platform, use --force-mobile flag:
|
||||
|
||||
cfx --force-mobile"""
|
||||
return 0
|
||||
|
||||
# In case of mobile device, we need to get stdio from `adb logcat` cmd:
|
||||
|
||||
# First flush logs in order to avoid catching previous ones
|
||||
subprocess.call([binary, "logcat", "-c"])
|
||||
|
||||
# Launch adb command
|
||||
runner.start()
|
||||
|
||||
# We can immediatly remove temporary profile folder
|
||||
# as it has been uploaded to the device
|
||||
profile.cleanup()
|
||||
# We are not going to use the output log file
|
||||
outf.close()
|
||||
|
||||
# Then we simply display stdout of `adb logcat`
|
||||
p = subprocess.Popen([binary, "logcat", "stderr:V stdout:V GeckoConsole:V *:S"], stdout=subprocess.PIPE)
|
||||
while True:
|
||||
line = p.stdout.readline()
|
||||
if line == '':
|
||||
break
|
||||
# mobile-utils addon contains an application quit event observer
|
||||
# that will print this string:
|
||||
if "APPLICATION-QUIT" in line:
|
||||
break
|
||||
|
||||
if verbose:
|
||||
# if --verbose is given, we display everything:
|
||||
# All JS Console messages, stdout and stderr.
|
||||
m = CLEANUP_ADB.match(line)
|
||||
if not m:
|
||||
print line.rstrip()
|
||||
continue
|
||||
print m.group(3)
|
||||
else:
|
||||
# Otherwise, display addons messages dispatched through
|
||||
# console.[info, log, debug, warning, error](msg)
|
||||
m = FILTER_ONLY_CONSOLE_FROM_ADB.match(line)
|
||||
if m:
|
||||
print m.group(2)
|
||||
|
||||
print >>sys.stderr, "Program terminated successfully."
|
||||
return 0
|
||||
|
||||
|
||||
print >>sys.stderr, "Using binary at '%s'." % runner.binary
|
||||
|
||||
# Ensure cfx is being used with Firefox 4.0+.
|
||||
# TODO: instead of dying when Firefox is < 4, warn when Firefox is outside
|
||||
# the minVersion/maxVersion boundaries.
|
||||
version_output = check_output(runner.command + ["-v"])
|
||||
# Note: this regex doesn't handle all valid versions in the Toolkit Version
|
||||
# Format <https://developer.mozilla.org/en/Toolkit_version_format>, just the
|
||||
# common subset that we expect Mozilla apps to use.
|
||||
mo = re.search(r"Mozilla (Firefox|Iceweasel|Fennec)\b[^ ]* ((\d+)\.\S*)",
|
||||
version_output)
|
||||
if not mo:
|
||||
# cfx may be used with Thunderbird, SeaMonkey or an exotic Firefox
|
||||
# version.
|
||||
print """
|
||||
WARNING: cannot determine Firefox version; please ensure you are running
|
||||
a Mozilla application equivalent to Firefox 4.0 or greater.
|
||||
"""
|
||||
elif mo.group(1) == "Fennec":
|
||||
# For now, only allow running on Mobile with --force-mobile argument
|
||||
if not enable_mobile:
|
||||
print """
|
||||
WARNING: Firefox Mobile support is still experimental.
|
||||
If you would like to run an addon on this platform, use --force-mobile flag:
|
||||
|
||||
cfx --force-mobile"""
|
||||
return
|
||||
else:
|
||||
version = mo.group(3)
|
||||
if int(version) < 4:
|
||||
print """
|
||||
cfx requires Firefox 4 or greater and is unable to find a compatible
|
||||
binary. Please install a newer version of Firefox or provide the path to
|
||||
your existing compatible version with the --binary flag:
|
||||
|
||||
cfx --binary=PATH_TO_FIREFOX_BINARY"""
|
||||
return
|
||||
|
||||
# Set the appropriate extensions.checkCompatibility preference to false,
|
||||
# so the tests run even if the SDK is not marked as compatible with the
|
||||
# version of Firefox on which they are running, and we don't have to
|
||||
# ensure we update the maxVersion before the version of Firefox changes
|
||||
# every six weeks.
|
||||
#
|
||||
# The regex we use here is effectively the same as BRANCH_REGEX from
|
||||
# /toolkit/mozapps/extensions/content/extensions.js, which toolkit apps
|
||||
# use to determine whether or not to load an incompatible addon.
|
||||
#
|
||||
br = re.search(r"^([^\.]+\.[0-9]+[a-z]*).*", mo.group(2), re.I)
|
||||
if br:
|
||||
prefname = 'extensions.checkCompatibility.' + br.group(1)
|
||||
profile.preferences[prefname] = False
|
||||
# Calling profile.set_preferences here duplicates the list of prefs
|
||||
# in prefs.js, since the profile calls self.set_preferences in its
|
||||
# constructor, but that is ok, because it doesn't change the set of
|
||||
# preferences that are ultimately registered in Firefox.
|
||||
profile.set_preferences(profile.preferences)
|
||||
|
||||
print >>sys.stderr, "Using profile at '%s'." % profile.profile
|
||||
sys.stderr.flush()
|
||||
|
||||
if norun:
|
||||
print "To launch the application, enter the following command:"
|
||||
print " ".join(runner.command) + " " + (" ".join(runner.cmdargs))
|
||||
return 0
|
||||
|
||||
runner.start()
|
||||
|
||||
done = False
|
||||
result = None
|
||||
test_name = "Jetpack startup"
|
||||
|
||||
def Timeout(message, test_name, parseable):
|
||||
if parseable:
|
||||
sys.stderr.write("TEST-UNEXPECTED-FAIL | %s | %s\n" % (test_name, message))
|
||||
sys.stderr.flush()
|
||||
return Exception(message)
|
||||
|
||||
try:
|
||||
while not done:
|
||||
time.sleep(0.05)
|
||||
for tail in (logfile_tail, outfile_tail):
|
||||
if tail:
|
||||
new_chars = tail.next()
|
||||
if new_chars:
|
||||
last_output_time = time.time()
|
||||
sys.stderr.write(new_chars)
|
||||
sys.stderr.flush()
|
||||
if is_running_tests and parseable:
|
||||
match = PARSEABLE_TEST_NAME.search(new_chars)
|
||||
if match:
|
||||
test_name = match.group(1)
|
||||
if os.path.exists(resultfile):
|
||||
result = open(resultfile).read()
|
||||
if result:
|
||||
if result in ['OK', 'FAIL']:
|
||||
done = True
|
||||
else:
|
||||
sys.stderr.write("Hrm, resultfile (%s) contained something weird (%d bytes)\n" % (resultfile, len(result)))
|
||||
sys.stderr.write("'"+result+"'\n")
|
||||
if enforce_timeouts:
|
||||
if time.time() - last_output_time > OUTPUT_TIMEOUT:
|
||||
raise Timeout("Test output exceeded timeout (%ds)." %
|
||||
OUTPUT_TIMEOUT, test_name, parseable)
|
||||
if time.time() - starttime > RUN_TIMEOUT:
|
||||
raise Timeout("Test run exceeded timeout (%ds)." %
|
||||
RUN_TIMEOUT, test_name, parseable)
|
||||
except:
|
||||
if not noquit:
|
||||
runner.stop()
|
||||
raise
|
||||
else:
|
||||
runner.wait(10)
|
||||
# double kill - hack for bugs 942111, 1006043..
|
||||
try:
|
||||
runner.stop()
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
outf.close()
|
||||
if profile:
|
||||
profile.cleanup()
|
||||
|
||||
print >>sys.stderr, "Total time: %f seconds" % (time.time() - starttime)
|
||||
|
||||
if result == 'OK':
|
||||
print >>sys.stderr, "Program terminated successfully."
|
||||
return 0
|
||||
else:
|
||||
print >>sys.stderr, "Program terminated unsuccessfully."
|
||||
return -1
|
||||
@@ -1,32 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#Template used by test-main.js
|
||||
TEST_MAIN_JS = '''\
|
||||
var main = require("./main");
|
||||
|
||||
exports["test main"] = function(assert) {
|
||||
assert.pass("Unit test running!");
|
||||
};
|
||||
|
||||
exports["test main async"] = function(assert, done) {
|
||||
assert.pass("async Unit test running!");
|
||||
done();
|
||||
};
|
||||
|
||||
require("sdk/test").run(exports);
|
||||
'''
|
||||
|
||||
#Template used by package.json
|
||||
PACKAGE_JSON = '''\
|
||||
{
|
||||
"name": "%(name)s",
|
||||
"title": "%(title)s",
|
||||
"id": "%(id)s",
|
||||
"description": "a basic add-on",
|
||||
"author": "",
|
||||
"license": "MPL-2.0",
|
||||
"version": "0.1"
|
||||
}
|
||||
'''
|
||||
@@ -1,52 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
import unittest
|
||||
import doctest
|
||||
import glob
|
||||
|
||||
env_root = os.environ['CUDDLEFISH_ROOT']
|
||||
|
||||
def get_tests():
|
||||
import cuddlefish
|
||||
import cuddlefish.tests
|
||||
|
||||
tests = []
|
||||
packages = [cuddlefish, cuddlefish.tests]
|
||||
for package in packages:
|
||||
path = os.path.abspath(package.__path__[0])
|
||||
pynames = glob.glob(os.path.join(path, '*.py'))
|
||||
for filename in pynames:
|
||||
basename = os.path.basename(filename)
|
||||
module_name = os.path.splitext(basename)[0]
|
||||
full_name = "%s.%s" % (package.__name__, module_name)
|
||||
module = __import__(full_name, fromlist=[package.__name__])
|
||||
|
||||
loader = unittest.TestLoader()
|
||||
suite = loader.loadTestsFromModule(module)
|
||||
for test in suite:
|
||||
tests.append(test)
|
||||
|
||||
finder = doctest.DocTestFinder()
|
||||
doctests = finder.find(module)
|
||||
for test in doctests:
|
||||
if len(test.examples) > 0:
|
||||
tests.append(doctest.DocTestCase(test))
|
||||
|
||||
return tests
|
||||
|
||||
def run(verbose=False):
|
||||
if verbose:
|
||||
verbosity = 2
|
||||
else:
|
||||
verbosity = 1
|
||||
|
||||
tests = get_tests()
|
||||
suite = unittest.TestSuite(tests)
|
||||
runner = unittest.TextTestRunner(verbosity=verbosity)
|
||||
return runner.run(suite)
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"loader": "lib/main.js",
|
||||
"icon": "explicit-icon.png",
|
||||
"icon64": "explicit-icon64.png"
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"loader": "lib/main.js"
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"loader": "lib/main.js"
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"loader": "lib/bar-loader.js"
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"loader": "lib/foo-loader.js",
|
||||
"dependencies": ["bar"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
minimal docs
|
||||
@@ -1,8 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
exports.main = function(options, callbacks) {
|
||||
console.log("minimal");
|
||||
callbacks.quit();
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user