зеркало из https://github.com/mozilla/gecko-dev.git
Bug 944451 - Land the simulator addon into mozilla-central. r=vingtetun, r=gps, r=paul
This commit is contained in:
Родитель
4be17b826f
Коммит
4d357d8a2c
|
@ -2,11 +2,16 @@
|
|||
# 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/.
|
||||
|
||||
include $(topsrcdir)/toolkit/mozapps/installer/package-name.mk
|
||||
|
||||
installer:
|
||||
@$(MAKE) -C b2g/installer installer
|
||||
|
||||
package:
|
||||
@$(MAKE) -C b2g/installer
|
||||
#ifdef FXOS_SIMULATOR
|
||||
$(PYTHON) $(srcdir)/b2g/simulator/build_xpi.py $(MOZ_PKG_PLATFORM)
|
||||
#endif
|
||||
|
||||
install::
|
||||
@echo 'B2G can't be installed directly.'
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
# 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/.
|
||||
|
||||
# Generate xpi for the simulator addon by:
|
||||
# - building a special gaia profile for it, as we need:
|
||||
# * more languages, and,
|
||||
# * less apps
|
||||
# than b2g desktop's one
|
||||
# - retrieve usefull app version metadata from the build system
|
||||
# - finally, use addon sdk's cfx tool to build the addon xpi
|
||||
# that ships:
|
||||
# * a small firefox addon registering to the app manager
|
||||
# * b2g desktop runtime
|
||||
# * gaia profile
|
||||
|
||||
import sys, os, re, subprocess
|
||||
from mozbuild.preprocessor import Preprocessor
|
||||
from mozbuild.base import MozbuildObject
|
||||
from mozbuild.util import ensureParentDir
|
||||
from zipfile import ZipFile
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
ftp_root_path = "/pub/mozilla.org/labs/fxos-simulator"
|
||||
UPDATE_LINK = "https://ftp.mozilla.org" + ftp_root_path + "/%(update_path)s/%(xpi_name)s"
|
||||
UPDATE_URL = "https://ftp.mozilla.org" + ftp_root_path + "/%(update_path)s/update.rdf"
|
||||
XPI_NAME = "fxos-simulator-%(version)s-%(platform)s.xpi"
|
||||
|
||||
class GaiaBuilder(object):
|
||||
def __init__(self, build, gaia_path):
|
||||
self.build = build
|
||||
self.gaia_path = gaia_path
|
||||
|
||||
def clean(self):
|
||||
self.build._run_make(target="clean", directory=self.gaia_path)
|
||||
|
||||
def profile(self, env):
|
||||
self.build._run_make(target="profile", directory=self.gaia_path, num_jobs=1, silent=False, append_env=env)
|
||||
|
||||
def override_prefs(self, srcfile):
|
||||
# Note that each time we call `make profile` in gaia, a fresh new pref file is created
|
||||
# cat srcfile >> profile/user.js
|
||||
with open(os.path.join(self.gaia_path, "profile", "user.js"), "a") as userJs:
|
||||
userJs.write(open(srcfile).read())
|
||||
|
||||
def process_package_overload(src, dst, version, app_buildid):
|
||||
ensureParentDir(dst)
|
||||
# First replace numeric version like '1.3'
|
||||
# Then replace with 'slashed' version like '1_4'
|
||||
# Finally set the full length addon version like 1.3.20131230
|
||||
defines = {
|
||||
"NUM_VERSION": version,
|
||||
"SLASH_VERSION": version.replace(".", "_"),
|
||||
"FULL_VERSION": ("%s.%s" % (version, app_buildid))
|
||||
}
|
||||
pp = Preprocessor(defines=defines)
|
||||
pp.do_filter("substitution")
|
||||
with open(dst, "w") as output:
|
||||
with open(src, "r") as input:
|
||||
pp.processFile(input=input, output=output)
|
||||
|
||||
def add_dir_to_zip(zip, top, pathInZip, blacklist=()):
|
||||
zf = ZipFile(zip, "a")
|
||||
for dirpath, subdirs, files in os.walk(top):
|
||||
dir_relpath = os.path.relpath(dirpath, top)
|
||||
if dir_relpath.startswith(blacklist):
|
||||
continue
|
||||
zf.write(dirpath, os.path.join(pathInZip, dir_relpath))
|
||||
for filename in files:
|
||||
relpath = os.path.join(dir_relpath, filename)
|
||||
if relpath in blacklist:
|
||||
continue
|
||||
zf.write(os.path.join(dirpath, filename),
|
||||
os.path.join(pathInZip, relpath))
|
||||
zf.close()
|
||||
|
||||
def main(platform):
|
||||
build = MozbuildObject.from_environment()
|
||||
topsrcdir = build.topsrcdir
|
||||
distdir = build.distdir
|
||||
|
||||
srcdir = os.path.join(topsrcdir, "b2g", "simulator")
|
||||
|
||||
app_buildid = open(os.path.join(build.topobjdir, "config", "buildid")).read().strip()
|
||||
|
||||
# The simulator uses a shorter version string,
|
||||
# it only keeps the major version digits A.B
|
||||
# whereas MOZ_B2G_VERSION is A.B.C.D
|
||||
b2g_version = build.config_environment.defines["MOZ_B2G_VERSION"].replace('"', '')
|
||||
version = ".".join(str(n) for n in LooseVersion(b2g_version).version[0:2])
|
||||
|
||||
# Build a gaia profile specific to the simulator in order to:
|
||||
# - disable the FTU
|
||||
# - set custom prefs to enable devtools debugger server
|
||||
# - set custom settings to disable lockscreen and screen timeout
|
||||
# - only ship production apps
|
||||
gaia_path = build.config_environment.substs["GAIADIR"]
|
||||
builder = GaiaBuilder(build, gaia_path)
|
||||
builder.clean()
|
||||
env = {
|
||||
"NOFTU": "1",
|
||||
"GAIA_APP_TARGET": "production",
|
||||
"SETTINGS_PATH": os.path.join(srcdir, "custom-settings.json")
|
||||
}
|
||||
builder.profile(env)
|
||||
builder.override_prefs(os.path.join(srcdir, "custom-prefs.js"))
|
||||
|
||||
# Substitute version strings in the package manifest overload file
|
||||
manifest_overload = os.path.join(build.topobjdir, "b2g", "simulator", "package-overload.json")
|
||||
process_package_overload(os.path.join(srcdir, "package-overload.json.in"),
|
||||
manifest_overload,
|
||||
version,
|
||||
app_buildid)
|
||||
|
||||
# Build the simulator addon xpi
|
||||
xpi_name = XPI_NAME % {"version": version, "platform": platform}
|
||||
xpi_path = os.path.join(distdir, xpi_name)
|
||||
|
||||
update_path = "%s/%s" % (version, platform)
|
||||
update_link = UPDATE_LINK % {"update_path": update_path, "xpi_name": xpi_name}
|
||||
update_url = UPDATE_URL % {"update_path": update_path}
|
||||
subprocess.check_call([
|
||||
build.virtualenv_manager.python_path, os.path.join(topsrcdir, "addon-sdk", "source", "bin", "cfx"), "xpi", \
|
||||
"--pkgdir", srcdir, \
|
||||
"--manifest-overload", manifest_overload, \
|
||||
"--strip-sdk", \
|
||||
"--update-link", update_link, \
|
||||
"--update-url", update_url, \
|
||||
"--static-args", "{\"label\": \"Firefox OS %s\"}" % version, \
|
||||
"--output-file", xpi_path \
|
||||
])
|
||||
|
||||
# Ship b2g-desktop, but prevent its gaia profile to be shipped in the xpi
|
||||
add_dir_to_zip(xpi_path, os.path.join(distdir, "b2g"), "b2g", ("gaia"))
|
||||
# Then ship our own gaia profile
|
||||
add_dir_to_zip(xpi_path, os.path.join(gaia_path, "profile"), "profile")
|
||||
|
||||
if __name__ == '__main__':
|
||||
if 2 != len(sys.argv):
|
||||
print("""Usage:
|
||||
python {0} MOZ_PKG_PLATFORM
|
||||
""".format(sys.argv[0]))
|
||||
sys.exit(1)
|
||||
main(*sys.argv[1:])
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
user_pref("devtools.debugger.prompt-connection", false);
|
||||
user_pref("devtools.debugger.forbid-certified-apps", false);
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"debugger.remote-mode": "adb-devtools",
|
||||
"screen.timeout": 0,
|
||||
"lockscreen.enabled": false,
|
||||
"lockscreen.locked": false
|
||||
}
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 4.7 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 7.7 KiB |
|
@ -0,0 +1,52 @@
|
|||
/* 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/.
|
||||
*/
|
||||
|
||||
const { Cc, Ci, Cu } = require("chrome");
|
||||
|
||||
const { SimulatorProcess } = require("./simulator-process");
|
||||
const Promise = require("sdk/core/promise");
|
||||
const Self = require("sdk/self");
|
||||
const System = require("sdk/system");
|
||||
const { Simulator } = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
|
||||
|
||||
let process;
|
||||
|
||||
function launch({ port }) {
|
||||
// Close already opened simulation
|
||||
if (process) {
|
||||
return close().then(launch.bind(null, { port: port }));
|
||||
}
|
||||
|
||||
process = SimulatorProcess();
|
||||
process.remoteDebuggerPort = port;
|
||||
process.run();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (!process) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
let p = process;
|
||||
process = null;
|
||||
return p.kill();
|
||||
}
|
||||
|
||||
|
||||
// Load data generated at build time that
|
||||
// expose various information about the runtime we ship
|
||||
let appinfo = System.staticArgs;
|
||||
|
||||
Simulator.register(appinfo.label, {
|
||||
appinfo: appinfo,
|
||||
launch: launch,
|
||||
close: close
|
||||
});
|
||||
|
||||
require("sdk/system/unload").when(function () {
|
||||
Simulator.unregister(appinfo.label);
|
||||
close();
|
||||
});
|
|
@ -0,0 +1,182 @@
|
|||
/* 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 { Cc, Ci, Cu, ChromeWorker } = require("chrome");
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const { EventTarget } = require("sdk/event/target");
|
||||
const { emit, off } = require("sdk/event/core");
|
||||
const { Class } = require("sdk/core/heritage");
|
||||
const Environment = require("sdk/system/environment").env;
|
||||
const Runtime = require("sdk/system/runtime");
|
||||
const Self = require("sdk/self");
|
||||
const URL = require("sdk/url");
|
||||
const Subprocess = require("subprocess");
|
||||
const Promise = require("sdk/core/promise");
|
||||
|
||||
const { rootURI: ROOT_URI } = require('@loader/options');
|
||||
const PROFILE_URL = ROOT_URI + "profile/";
|
||||
const BIN_URL = ROOT_URI + "b2g/";
|
||||
|
||||
// Log subprocess error and debug messages to the console. This logs messages
|
||||
// for all consumers of the API. We trim the messages because they sometimes
|
||||
// have trailing newlines. And note that registerLogHandler actually registers
|
||||
// an error handler, despite its name.
|
||||
Subprocess.registerLogHandler(
|
||||
function(s) console.error("subprocess: " + s.trim())
|
||||
);
|
||||
Subprocess.registerDebugHandler(
|
||||
function(s) console.debug("subprocess: " + s.trim())
|
||||
);
|
||||
|
||||
exports.SimulatorProcess = Class({
|
||||
extends: EventTarget,
|
||||
initialize: function initialize(options) {
|
||||
EventTarget.prototype.initialize.call(this, options);
|
||||
|
||||
this.on("stdout", function onStdout(data) console.log(data.trim()));
|
||||
this.on("stderr", function onStderr(data) console.error(data.trim()));
|
||||
},
|
||||
|
||||
// check if b2g is running
|
||||
get isRunning() !!this.process,
|
||||
|
||||
/**
|
||||
* Start the process and connect the debugger client.
|
||||
*/
|
||||
run: function() {
|
||||
// kill before start if already running
|
||||
if (this.process != null) {
|
||||
this.process
|
||||
.kill()
|
||||
.then(this.run.bind(this));
|
||||
return;
|
||||
}
|
||||
|
||||
// resolve b2g binaries path (raise exception if not found)
|
||||
let b2gExecutable = this.b2gExecutable;
|
||||
|
||||
this.once("stdout", function () {
|
||||
if (Runtime.OS == "Darwin") {
|
||||
console.debug("WORKAROUND run osascript to show b2g-desktop window"+
|
||||
" on Runtime.OS=='Darwin'");
|
||||
// Escape double quotes and escape characters for use in AppleScript.
|
||||
let path = b2gExecutable.path
|
||||
.replace(/\\/g, "\\\\").replace(/\"/g, '\\"');
|
||||
|
||||
Subprocess.call({
|
||||
command: "/usr/bin/osascript",
|
||||
arguments: ["-e", 'tell application "' + path + '" to activate'],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let environment;
|
||||
if (Runtime.OS == "Linux") {
|
||||
environment = ["TMPDIR=" + Services.dirsvc.get("TmpD",Ci.nsIFile).path];
|
||||
if ("DISPLAY" in Environment) {
|
||||
environment.push("DISPLAY=" + Environment.DISPLAY);
|
||||
}
|
||||
}
|
||||
|
||||
// spawn a b2g instance
|
||||
this.process = Subprocess.call({
|
||||
command: b2gExecutable,
|
||||
arguments: this.b2gArguments,
|
||||
environment: environment,
|
||||
|
||||
// emit stdout event
|
||||
stdout: (function(data) {
|
||||
emit(this, "stdout", data);
|
||||
}).bind(this),
|
||||
|
||||
// emit stderr event
|
||||
stderr: (function(data) {
|
||||
emit(this, "stderr", data);
|
||||
}).bind(this),
|
||||
|
||||
// on b2g instance exit, reset tracked process, remoteDebuggerPort and
|
||||
// shuttingDown flag, then finally emit an exit event
|
||||
done: (function(result) {
|
||||
console.log(this.b2gFilename + " terminated with " + result.exitCode);
|
||||
this.process = null;
|
||||
emit(this, "exit", result.exitCode);
|
||||
}).bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
// request a b2g instance kill
|
||||
kill: function() {
|
||||
let deferred = Promise.defer();
|
||||
if (this.process) {
|
||||
this.once("exit", (exitCode) => {
|
||||
this.shuttingDown = false;
|
||||
deferred.resolve(exitCode);
|
||||
});
|
||||
if (!this.shuttingDown) {
|
||||
this.shuttingDown = true;
|
||||
emit(this, "kill", null);
|
||||
this.process.kill();
|
||||
}
|
||||
return deferred.promise;
|
||||
} else {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
},
|
||||
|
||||
// compute current b2g filename
|
||||
get b2gFilename() {
|
||||
return this._executable ? this._executableFilename : "B2G";
|
||||
},
|
||||
|
||||
// compute current b2g file handle
|
||||
get b2gExecutable() {
|
||||
if (this._executable) return this._executable;
|
||||
|
||||
let bin = URL.toFilename(BIN_URL);
|
||||
let executables = {
|
||||
WINNT: "b2g-bin.exe",
|
||||
Darwin: "Contents/MacOS/b2g-bin",
|
||||
Linux: "b2g-bin",
|
||||
};
|
||||
|
||||
console.log("bin url: "+bin+"/"+executables[Runtime.OS]);
|
||||
let path = bin + "/" + executables[Runtime.OS];
|
||||
|
||||
let executable = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||
executable.initWithPath(path);
|
||||
|
||||
if (!executable.exists()) {
|
||||
// B2G binaries not found
|
||||
throw Error("b2g-desktop Executable not found.");
|
||||
}
|
||||
|
||||
this._executable = executable;
|
||||
this._executableFilename = "b2g-bin";
|
||||
|
||||
return executable;
|
||||
},
|
||||
|
||||
// compute b2g CLI arguments
|
||||
get b2gArguments() {
|
||||
let args = [];
|
||||
|
||||
let profile = URL.toFilename(PROFILE_URL);
|
||||
args.push("-profile", profile);
|
||||
Cu.reportError(profile);
|
||||
|
||||
// NOTE: push dbgport option on the b2g-desktop commandline
|
||||
args.push("-dbgport", "" + this.remoteDebuggerPort);
|
||||
|
||||
// Ignore eventual zombie instances of b2g that are left over
|
||||
args.push("-no-remote");
|
||||
|
||||
return args;
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"id": "fxos_@SLASH_VERSION@_simulator@mozilla.org",
|
||||
"name": "fxos_@SLASH_VERSION@_simulator",
|
||||
"version": "@FULL_VERSION@",
|
||||
"fullName": "Firefox OS @NUM_VERSION@ Simulator",
|
||||
"description": "a Firefox OS @NUM_VERSION@ simulator"
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"id": "fxos_simulator@mozilla.org",
|
||||
"name": "fxos_simulator",
|
||||
"version": "1.0.dev",
|
||||
"fullName": "Firefox OS Simulator",
|
||||
"label": "Firefox OS",
|
||||
"description": "a Firefox OS simulator",
|
||||
"author": "Myk Melez (https://github.com/mykmelez)",
|
||||
"contributors": [
|
||||
"Alexandre Poirot (https://github.com/ochameau)",
|
||||
"Anant Narayanan (https://github.com/anantn)",
|
||||
"Brandon Kase (https://github.com/bkase)",
|
||||
"Breck Yunits (https://github.com/breck7)",
|
||||
"César Carruitero (https://github.com/ccarruitero)",
|
||||
"David Gomes (https://github.com/davidgomes)",
|
||||
"Fabrice Desré (https://github.com/fabricedesre)",
|
||||
"Fraser Tweedale (https://github.com/frasertweedale)",
|
||||
"Harald Kirschner (https://github.com/digitarald)",
|
||||
"Jérémie Patonnier (https://github.com/JeremiePat)",
|
||||
"J. Ryan Stinnett (https://github.com/jryans)",
|
||||
"Kan-Ru Chen (陳侃如) (https://github.com/kanru)",
|
||||
"Louis Stowasser (https://github.com/louisstow)",
|
||||
"Luca Greco (https://github.com/rpl)",
|
||||
"Matthew Claypotch (https://github.com/potch)",
|
||||
"Matthew Riley MacPherson (https://github.com/tofumatt)",
|
||||
"Nick Desaulniers (https://github.com/nickdesaulniers)",
|
||||
"Soumen Ganguly (https://github.com/SoumenG)",
|
||||
"Sudheesh Singanamalla (https://github.com/sudheesh001)",
|
||||
"Victor Bjelkholm (https://github.com/VictorBjelkholm)"
|
||||
],
|
||||
"permissions": {
|
||||
"private-browsing": true
|
||||
},
|
||||
"license": "MPL 2.0",
|
||||
"unpack": true,
|
||||
"dependencies": ["subprocess"]
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
<h2>What's that?</h2>
|
||||
Simply package enigmail hard work on providing IPC feature in mozilla platform.
|
||||
So we are able to launch child proccesses from javascript,
|
||||
and in our case, from addon-sdk libraries :)
|
||||
|
||||
<h2>Sample of code:</h2>
|
||||
This object allows to start a process, and read/write data to/from it
|
||||
using stdin/stdout/stderr streams.
|
||||
Usage example:
|
||||
|
||||
const subprocess = require("subprocess");
|
||||
var p = subprocess.call({
|
||||
command: '/bin/foo',
|
||||
arguments: ['-v', 'foo'],
|
||||
environment: [ "XYZ=abc", "MYVAR=def" ],
|
||||
charset: 'UTF-8',
|
||||
workdir: '/home/foo',
|
||||
//stdin: "some value to write to stdin\nfoobar",
|
||||
stdin: function(stdin) {
|
||||
stdin.write("some value to write to stdin\nfoobar");
|
||||
stdin.close();
|
||||
},
|
||||
stdout: function(data) {
|
||||
dump("got data on stdout:" + data + "\n");
|
||||
},
|
||||
stderr: function(data) {
|
||||
dump("got data on stderr:" + data + "\n");
|
||||
},
|
||||
done: function(result) {
|
||||
dump("process terminated with " + result.exitCode + "\n");
|
||||
},
|
||||
mergeStderr: false
|
||||
});
|
||||
p.wait(); // wait for the subprocess to terminate
|
||||
// this will block the main thread,
|
||||
// only do if you can wait that long
|
||||
|
||||
|
||||
Description of parameters:
|
||||
--------------------------
|
||||
Apart from <command>, all arguments are optional.
|
||||
|
||||
command: either a |nsIFile| object pointing to an executable file or a
|
||||
String containing the platform-dependent path to an executable
|
||||
file.
|
||||
|
||||
arguments: optional string array containing the arguments to the command.
|
||||
|
||||
environment: optional string array containing environment variables to pass
|
||||
to the command. The array elements must have the form
|
||||
"VAR=data". Please note that if environment is defined, it
|
||||
replaces any existing environment variables for the subprocess.
|
||||
|
||||
charset: Output is decoded with given charset and a string is returned.
|
||||
If charset is undefined, "UTF-8" is used as default.
|
||||
To get binary data, set this to null and the returned string
|
||||
is not decoded in any way.
|
||||
|
||||
workdir: optional; String containing the platform-dependent path to a
|
||||
directory to become the current working directory of the subprocess.
|
||||
|
||||
stdin: optional input data for the process to be passed on standard
|
||||
input. stdin can either be a string or a function.
|
||||
A |string| gets written to stdin and stdin gets closed;
|
||||
A |function| gets passed an object with write and close function.
|
||||
Please note that the write() function will return almost immediately;
|
||||
data is always written asynchronously on a separate thread.
|
||||
|
||||
stdout: an optional function that can receive output data from the
|
||||
process. The stdout-function is called asynchronously; it can be
|
||||
called mutliple times during the execution of a process.
|
||||
At a minimum at each occurance of \n or \r.
|
||||
Please note that null-characters might need to be escaped
|
||||
with something like 'data.replace(/\0/g, "\\0");'.
|
||||
|
||||
stderr: an optional function that can receive stderr data from the
|
||||
process. The stderr-function is called asynchronously; it can be
|
||||
called mutliple times during the execution of a process. Please
|
||||
note that null-characters might need to be escaped with
|
||||
something like 'data.replace(/\0/g, "\\0");'.
|
||||
(on windows it only gets called once right now)
|
||||
|
||||
done: optional function that is called when the process has terminated.
|
||||
The exit code from the process available via result.exitCode. If
|
||||
stdout is not defined, then the output from stdout is available
|
||||
via result.stdout. stderr data is in result.stderr
|
||||
|
||||
mergeStderr: optional boolean value. If true, stderr is merged with stdout;
|
||||
no data will be provided to stderr.
|
||||
|
||||
|
||||
Description of object returned by subprocess.call(...)
|
||||
------------------------------------------------------
|
||||
The object returned by subprocess.call offers a few methods that can be
|
||||
executed:
|
||||
|
||||
wait(): waits for the subprocess to terminate. It is not required to use
|
||||
wait; done will be called in any case when the subprocess terminated.
|
||||
|
||||
kill(hardKill): kill the subprocess. Any open pipes will be closed and
|
||||
done will be called.
|
||||
hardKill [ignored on Windows]:
|
||||
- false: signal the process terminate (SIGTERM)
|
||||
- true: kill the process (SIGKILL)
|
||||
|
||||
|
||||
Other methods in subprocess
|
||||
---------------------------
|
||||
|
||||
registerDebugHandler(functionRef): register a handler that is called to get
|
||||
debugging information
|
||||
registerLogHandler(functionRef): register a handler that is called to get error
|
||||
messages
|
||||
|
||||
example:
|
||||
subprocess.registerLogHandler( function(s) { dump(s); } );
|
||||
|
||||
|
||||
<h2>Credits:</h2>
|
||||
All enigmail team working on IPC component.
|
||||
The Initial Developer of this code is Jan Gerber.
|
||||
Portions created by Jan Gerber <j@mailb.org>,
|
||||
Patrick Brunschwig (author of almost all code) <patrick@mozilla-enigmail.org>,
|
||||
Ramalingam Saravanan (from enigmail team) <svn@xmlterm.org>
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,248 @@
|
|||
/* 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/.
|
||||
*/
|
||||
|
||||
/*
|
||||
* ChromeWorker Object subprocess.jsm on Unix-like systems (Linux, Mac OS X, ...)
|
||||
* to process stdin/stdout/stderr on separate threads.
|
||||
*
|
||||
*/
|
||||
|
||||
// Being a ChromeWorker object, implicitly uses the following:
|
||||
// Components.utils.import("resource://gre/modules/ctypes.jsm");
|
||||
|
||||
'use strict';
|
||||
|
||||
const BufferSize = 1024;
|
||||
|
||||
var libc = null;
|
||||
var libcFunc = {};
|
||||
|
||||
|
||||
/*
|
||||
struct pollfd {
|
||||
int fd; // file descriptor
|
||||
short events; // events to look for
|
||||
short revents; // events returned
|
||||
};
|
||||
*/
|
||||
|
||||
var pollfd = new ctypes.StructType("pollfd",
|
||||
[ {'fd': ctypes.int},
|
||||
{'events': ctypes.short},
|
||||
{'revents': ctypes.short}
|
||||
]);
|
||||
|
||||
var WriteBuffer = ctypes.uint8_t.array(BufferSize);
|
||||
var ReadBuffer = ctypes.char.array(BufferSize);
|
||||
|
||||
|
||||
const POLLIN = 0x0001;
|
||||
const POLLOUT = 0x0004;
|
||||
|
||||
const POLLERR = 0x0008; // some poll error occurred
|
||||
const POLLHUP = 0x0010; // file descriptor was "hung up"
|
||||
const POLLNVAL = 0x0020; // requested events "invalid"
|
||||
|
||||
const WNOHANG = 0x01;
|
||||
|
||||
const pid_t = ctypes.int32_t;
|
||||
|
||||
const INDEFINITE = -1;
|
||||
const NOWAIT = 0;
|
||||
const WAITTIME = 200 // wait time for poll() in ms
|
||||
|
||||
function initLibc(libName) {
|
||||
postMessage({msg: "debug", data: "initialising library with "+ libName});
|
||||
|
||||
libc = ctypes.open(libName);
|
||||
|
||||
libcFunc.pollFds = pollfd.array(1);
|
||||
|
||||
// int poll(struct pollfd fds[], nfds_t nfds, int timeout);
|
||||
libcFunc.poll = libc.declare("poll",
|
||||
ctypes.default_abi,
|
||||
ctypes.int,
|
||||
libcFunc.pollFds,
|
||||
ctypes.unsigned_int,
|
||||
ctypes.int);
|
||||
|
||||
//ssize_t write(int fd, const void *buf, size_t count);
|
||||
// NOTE: buf is declared as array of unsigned int8 instead of char to avoid
|
||||
// implicit charset conversion
|
||||
libcFunc.write = libc.declare("write",
|
||||
ctypes.default_abi,
|
||||
ctypes.int,
|
||||
ctypes.int,
|
||||
WriteBuffer,
|
||||
ctypes.int);
|
||||
|
||||
//int read(int fd, void *buf, size_t count);
|
||||
libcFunc.read = libc.declare("read",
|
||||
ctypes.default_abi,
|
||||
ctypes.int,
|
||||
ctypes.int,
|
||||
ReadBuffer,
|
||||
ctypes.int);
|
||||
|
||||
//int pipe(int pipefd[2]);
|
||||
libcFunc.pipefd = ctypes.int.array(2);
|
||||
|
||||
//int close(int fd);
|
||||
libcFunc.close = libc.declare("close",
|
||||
ctypes.default_abi,
|
||||
ctypes.int,
|
||||
ctypes.int);
|
||||
|
||||
//pid_t waitpid(pid_t pid, int *status, int options);
|
||||
libcFunc.waitpid = libc.declare("waitpid",
|
||||
ctypes.default_abi,
|
||||
pid_t,
|
||||
pid_t,
|
||||
ctypes.int.ptr,
|
||||
ctypes.int);
|
||||
}
|
||||
|
||||
function closePipe(pipe) {
|
||||
libcFunc.close(pipe);
|
||||
}
|
||||
|
||||
function writePipe(pipe, data) {
|
||||
|
||||
postMessage({msg: "debug", data: "trying to write to "+pipe});
|
||||
|
||||
let numChunks = Math.floor(data.length / BufferSize);
|
||||
let pData = new WriteBuffer();
|
||||
|
||||
for (var chunk = 0; chunk <= numChunks; chunk ++) {
|
||||
let numBytes = chunk < numChunks ? BufferSize : data.length - chunk * BufferSize;
|
||||
for (var i=0; i < numBytes; i++) {
|
||||
pData[i] = data.charCodeAt(chunk * BufferSize + i) % 256;
|
||||
}
|
||||
|
||||
let bytesWritten = libcFunc.write(pipe, pData, numBytes);
|
||||
if (bytesWritten != numBytes) {
|
||||
closePipe();
|
||||
libc.close();
|
||||
postMessage({ msg: "error", data: "error: wrote "+bytesWritten+" instead of "+numBytes+" bytes"});
|
||||
close();
|
||||
}
|
||||
}
|
||||
postMessage({msg: "info", data: "wrote "+data.length+" bytes of data"});
|
||||
}
|
||||
|
||||
|
||||
function readString(data, length, charset) {
|
||||
var string = '', bytes = [];
|
||||
for(var i = 0;i < length; i++) {
|
||||
if(data[i] == 0 && charset != "null") // stop on NULL character for non-binary data
|
||||
break;
|
||||
|
||||
bytes.push(data[i]);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function readPipe(pipe, charset, pid) {
|
||||
var p = new libcFunc.pollFds;
|
||||
p[0].fd = pipe;
|
||||
p[0].events = POLLIN | POLLERR | POLLHUP;
|
||||
p[0].revents = 0;
|
||||
var pollTimeout = WAITTIME;
|
||||
var exitCode = -1;
|
||||
var readCount = 0;
|
||||
var result, status = ctypes.int();
|
||||
result = 0;
|
||||
|
||||
|
||||
const i=0;
|
||||
while (true) {
|
||||
if (result == 0) {
|
||||
result = libcFunc.waitpid(pid, status.address(), WNOHANG);
|
||||
if (result > 0) {
|
||||
pollTimeout = NOWAIT;
|
||||
exitCode = parseInt(status.value);
|
||||
postMessage({msg: "debug", data: "waitpid signaled subprocess stop, exitcode="+status.value });
|
||||
}
|
||||
}
|
||||
var r = libcFunc.poll(p, 1, pollTimeout);
|
||||
if (r > 0) {
|
||||
if (p[i].revents & POLLIN) {
|
||||
postMessage({msg: "debug", data: "reading next chunk"});
|
||||
readCount = readPolledFd(p[i].fd, charset);
|
||||
if (readCount == 0) break;
|
||||
}
|
||||
|
||||
if (p[i].revents & POLLHUP) {
|
||||
postMessage({msg: "debug", data: "poll returned HUP"});
|
||||
break;
|
||||
}
|
||||
else if (p[i].revents & POLLERR) {
|
||||
postMessage({msg: "error", data: "poll returned error"});
|
||||
break;
|
||||
}
|
||||
else if (p[i].revents != POLLIN) {
|
||||
postMessage({msg: "error", data: "poll returned "+p[i]});
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
if (pollTimeout == 0 || r < 0) break;
|
||||
}
|
||||
|
||||
// continue reading until the buffer is empty
|
||||
while (readCount > 0) {
|
||||
readCount = readPolledFd(pipe, charset);
|
||||
}
|
||||
|
||||
libcFunc.close(pipe);
|
||||
postMessage({msg: "done", data: exitCode });
|
||||
libc.close();
|
||||
close();
|
||||
}
|
||||
|
||||
function readPolledFd(pipe, charset) {
|
||||
var line = new ReadBuffer();
|
||||
var r = libcFunc.read(pipe, line, BufferSize);
|
||||
|
||||
if (r > 0) {
|
||||
var c = readString(line, r, charset);
|
||||
postMessage({msg: "data", data: c, count: c.length});
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
onmessage = function (event) {
|
||||
switch (event.data.msg) {
|
||||
case "init":
|
||||
initLibc(event.data.libc);
|
||||
break;
|
||||
case "read":
|
||||
initLibc(event.data.libc);
|
||||
readPipe(event.data.pipe, event.data.charset, event.data.pid);
|
||||
break;
|
||||
case "write":
|
||||
// data contents:
|
||||
// msg: 'write'
|
||||
// data: the data (string) to write
|
||||
// pipe: ptr to pipe
|
||||
writePipe(event.data.pipe, event.data.data);
|
||||
postMessage({msg: "info", data: "WriteOK"});
|
||||
break;
|
||||
case "close":
|
||||
postMessage({msg: "debug", data: "closing stdin\n"});
|
||||
|
||||
closePipe(event.data.pipe);
|
||||
postMessage({msg: "info", data: "ClosedOK"});
|
||||
break;
|
||||
case "stop":
|
||||
libc.close(); // do not use libc after this point
|
||||
close();
|
||||
break;
|
||||
default:
|
||||
throw("error: Unknown command"+event.data.msg+"\n");
|
||||
}
|
||||
return;
|
||||
};
|
|
@ -0,0 +1,206 @@
|
|||
/* 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/.
|
||||
*/
|
||||
|
||||
/*
|
||||
* ChromeWorker Object subprocess.jsm on Windows to process stdin/stdout/stderr
|
||||
* on separate threads.
|
||||
*
|
||||
*/
|
||||
|
||||
// Being a ChromeWorker object, implicitly uses the following:
|
||||
// Components.utils.import("resource://gre/modules/ctypes.jsm");
|
||||
|
||||
'use strict';
|
||||
|
||||
const BufferSize = 1024;
|
||||
|
||||
const BOOL = ctypes.bool;
|
||||
const HANDLE = ctypes.size_t;
|
||||
const DWORD = ctypes.uint32_t;
|
||||
const LPDWORD = DWORD.ptr;
|
||||
const PVOID = ctypes.voidptr_t;
|
||||
const LPVOID = PVOID;
|
||||
|
||||
/*
|
||||
typedef struct _OVERLAPPED {
|
||||
ULONG_PTR Internal;
|
||||
ULONG_PTR InternalHigh;
|
||||
union {
|
||||
struct {
|
||||
DWORD Offset;
|
||||
DWORD OffsetHigh;
|
||||
};
|
||||
PVOID Pointer;
|
||||
};
|
||||
HANDLE hEvent;
|
||||
} OVERLAPPED, *LPOVERLAPPED;
|
||||
*/
|
||||
const OVERLAPPED = new ctypes.StructType("OVERLAPPED");
|
||||
|
||||
var ReadFileBuffer = ctypes.char.array(BufferSize);
|
||||
var WriteFileBuffer = ctypes.uint8_t.array(BufferSize);
|
||||
|
||||
var kernel32dll = null;
|
||||
var libFunc = {};
|
||||
|
||||
function initLib(libName) {
|
||||
if (ctypes.size_t.size == 8) {
|
||||
var WinABI = ctypes.default_abi;
|
||||
} else {
|
||||
var WinABI = ctypes.winapi_abi;
|
||||
}
|
||||
|
||||
kernel32dll = ctypes.open(libName);
|
||||
|
||||
/*
|
||||
BOOL WINAPI WriteFile(
|
||||
__in HANDLE hFile,
|
||||
__in LPCVOID lpBuffer,
|
||||
__in DWORD nNumberOfBytesToWrite,
|
||||
__out_opt LPDWORD lpNumberOfBytesWritten,
|
||||
__inout_opt LPOVERLAPPED lpOverlapped
|
||||
);
|
||||
|
||||
NOTE: lpBuffer is declared as array of unsigned int8 instead of char to avoid
|
||||
implicit charset conversion
|
||||
*/
|
||||
libFunc.WriteFile = kernel32dll.declare("WriteFile",
|
||||
WinABI,
|
||||
BOOL,
|
||||
HANDLE,
|
||||
WriteFileBuffer,
|
||||
DWORD,
|
||||
LPDWORD,
|
||||
OVERLAPPED.ptr
|
||||
);
|
||||
|
||||
/*
|
||||
BOOL WINAPI ReadFile(
|
||||
__in HANDLE hFile,
|
||||
__out LPVOID ReadFileBuffer,
|
||||
__in DWORD nNumberOfBytesToRead,
|
||||
__out_opt LPDWORD lpNumberOfBytesRead,
|
||||
__inout_opt LPOVERLAPPED lpOverlapped
|
||||
);
|
||||
*/
|
||||
libFunc.ReadFile = kernel32dll.declare("ReadFile",
|
||||
WinABI,
|
||||
BOOL,
|
||||
HANDLE,
|
||||
ReadFileBuffer,
|
||||
DWORD,
|
||||
LPDWORD,
|
||||
OVERLAPPED.ptr
|
||||
);
|
||||
|
||||
/*
|
||||
BOOL WINAPI CloseHandle(
|
||||
__in HANDLE hObject
|
||||
);
|
||||
*/
|
||||
libFunc.CloseHandle = kernel32dll.declare("CloseHandle",
|
||||
WinABI,
|
||||
BOOL,
|
||||
HANDLE
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function writePipe(pipe, data) {
|
||||
var bytesWritten = DWORD(0);
|
||||
|
||||
var pData = new WriteFileBuffer();
|
||||
|
||||
var numChunks = Math.floor(data.length / BufferSize);
|
||||
for (var chunk = 0; chunk <= numChunks; chunk ++) {
|
||||
var numBytes = chunk < numChunks ? BufferSize : data.length - chunk * BufferSize;
|
||||
for (var i=0; i < numBytes; i++) {
|
||||
pData[i] = data.charCodeAt(chunk * BufferSize + i) % 256;
|
||||
}
|
||||
|
||||
var r = libFunc.WriteFile(pipe, pData, numBytes, bytesWritten.address(), null);
|
||||
if (bytesWritten.value != numBytes)
|
||||
throw("error: wrote "+bytesWritten.value+" instead of "+numBytes+" bytes");
|
||||
}
|
||||
postMessage("wrote "+data.length+" bytes of data");
|
||||
}
|
||||
|
||||
function readString(data, length, charset) {
|
||||
var string = '', bytes = [];
|
||||
for(var i = 0;i < length; i++) {
|
||||
if(data[i] == 0 && charset != "null") // stop on NULL character for non-binary data
|
||||
break;
|
||||
|
||||
bytes.push(data[i]);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function readPipe(pipe, charset) {
|
||||
while (true) {
|
||||
var bytesRead = DWORD(0);
|
||||
var line = new ReadFileBuffer();
|
||||
var r = libFunc.ReadFile(pipe, line, BufferSize, bytesRead.address(), null);
|
||||
|
||||
if (!r) {
|
||||
// stop if we get an error (such as EOF reached)
|
||||
postMessage({msg: "info", data: "ReadFile failed"});
|
||||
break;
|
||||
}
|
||||
|
||||
if (bytesRead.value > 0) {
|
||||
var c = readString(line, bytesRead.value, charset);
|
||||
postMessage({msg: "data", data: c, count: c.length});
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
libFunc.CloseHandle(pipe);
|
||||
postMessage({msg: "done"});
|
||||
kernel32dll.close();
|
||||
close();
|
||||
}
|
||||
|
||||
onmessage = function (event) {
|
||||
let pipePtr;
|
||||
switch (event.data.msg) {
|
||||
case "init":
|
||||
initLib(event.data.libc);
|
||||
break;
|
||||
case "write":
|
||||
// data contents:
|
||||
// msg: 'write'
|
||||
// data: the data (string) to write
|
||||
// pipe: ptr to pipe
|
||||
pipePtr = HANDLE.ptr(event.data.pipe);
|
||||
writePipe(pipePtr.contents, event.data.data);
|
||||
postMessage("WriteOK");
|
||||
break;
|
||||
case "read":
|
||||
initLib(event.data.libc);
|
||||
pipePtr = HANDLE.ptr(event.data.pipe);
|
||||
readPipe(pipePtr.contents, event.data.charset);
|
||||
break;
|
||||
case "close":
|
||||
pipePtr = HANDLE.ptr(event.data.pipe);
|
||||
postMessage("closing stdin\n");
|
||||
|
||||
if (libFunc.CloseHandle(pipePtr.contents)) {
|
||||
postMessage("ClosedOK");
|
||||
}
|
||||
else
|
||||
postMessage("Could not close stdin handle");
|
||||
break;
|
||||
case "stop":
|
||||
kernel32dll.close();
|
||||
close();
|
||||
break;
|
||||
default:
|
||||
throw("error: Unknown command"+event.data.msg+"\n");
|
||||
}
|
||||
return;
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "subprocess",
|
||||
"license": "MPL 1.1/GPL 2.0/LGPL 2.1",
|
||||
"author": "Alexandre Poirot",
|
||||
"contributors": [
|
||||
"Jan Gerber (original creator) <j@mailb.org>",
|
||||
"Patrick Brunschwig (author of almost all code) <patrick@mozilla-enigmail.org>",
|
||||
"Ramalingam Saravanan (from enigmail team) <svn@xmlterm.org>"
|
||||
],
|
||||
"version": "0.1.1",
|
||||
"dependencies": [
|
||||
"api-utils"
|
||||
],
|
||||
"description": "Addon-sdk package for subprocess xpcom components from enigmail. Allow to run process, manipulate stdin/out and kill it."
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
/* 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/.
|
||||
*/
|
||||
|
||||
const { Cc, Ci } = require("chrome");
|
||||
const subprocess = require("subprocess");
|
||||
const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
|
||||
|
||||
// For now, only test on windows
|
||||
if (env.get('OS') && env.get('OS').match(/Windows/)) {
|
||||
|
||||
exports.testWindows = function (test) {
|
||||
test.waitUntilDone();
|
||||
let envTestValue = "OK";
|
||||
let gotStdout = false;
|
||||
|
||||
var p = subprocess.call({
|
||||
// Retrieve windows cmd.exe path from env
|
||||
command: env.get('ComSpec'),
|
||||
// In order to execute a simple "echo" function
|
||||
arguments: ['/C', 'echo %ENV_TEST%'], // ' & type CON' should display stdin, but doesn't work
|
||||
// Printing an environnement variable set here by the parent process
|
||||
environment: ['ENV_TEST='+envTestValue],
|
||||
|
||||
stdin: function(stdin) {
|
||||
// Win32 command line is not really made for stdin
|
||||
// So it doesn't seems to work as it's hard to retrieve stdin
|
||||
stdin.write("stdin");
|
||||
stdin.close();
|
||||
},
|
||||
stdout: function(data) {
|
||||
test.assert(!gotStdout,"don't get stdout twice");
|
||||
test.assertEqual(data,envTestValue+"\r\n","stdout contains the environment variable");
|
||||
gotStdout = true;
|
||||
},
|
||||
stderr: function(data) {
|
||||
test.fail("shouldn't get stderr");
|
||||
},
|
||||
done: function() {
|
||||
test.assert(gotStdout, "got stdout before finished");
|
||||
test.done();
|
||||
},
|
||||
mergeStderr: false
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
exports.testWindowsStderr = function (test) {
|
||||
test.waitUntilDone();
|
||||
let gotStderr = false;
|
||||
|
||||
var p = subprocess.call({
|
||||
command: env.get('ComSpec'),
|
||||
arguments: ['/C', 'nonexistent'],
|
||||
|
||||
stdout: function(data) {
|
||||
test.fail("shouldn't get stdout");
|
||||
},
|
||||
stderr: function(data) {
|
||||
test.assert(!gotStderr,"don't get stderr twice");
|
||||
test.assertEqual(
|
||||
data,
|
||||
"'nonexistent' is not recognized as an internal or external command,\r\n" +
|
||||
"operable program or batch file.\r\n",
|
||||
"stderr contains the error message"
|
||||
);
|
||||
gotStderr = true;
|
||||
},
|
||||
done: function() {
|
||||
test.assert(gotStderr, "got stderr before finished");
|
||||
test.done();
|
||||
},
|
||||
mergeStderr: false
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (env.get('USER') && env.get('SHELL')) {
|
||||
|
||||
exports.testUnix = function (test) {
|
||||
test.waitUntilDone();
|
||||
let envTestValue = "OK";
|
||||
let gotStdout = false;
|
||||
|
||||
var p = subprocess.call({
|
||||
command: '/bin/sh',
|
||||
// Print stdin and our env variable
|
||||
//arguments: ['-c', 'echo $@ $ENV_TEST'],
|
||||
environment: ['ENV_TEST='+envTestValue],
|
||||
|
||||
stdin: function(stdin) {
|
||||
stdin.write("echo $ENV_TEST");
|
||||
stdin.close();
|
||||
},
|
||||
stdout: function(data) {
|
||||
test.assert(!gotStdout,"don't get stdout twice");
|
||||
test.assertEqual(data,envTestValue+"\n","stdout contains the environment variable");
|
||||
gotStdout = true;
|
||||
},
|
||||
stderr: function(data) {
|
||||
test.fail("shouldn't get stderr");
|
||||
},
|
||||
done: function() {
|
||||
test.assert(gotStdout, "got stdout before finished");
|
||||
test.done();
|
||||
},
|
||||
mergeStderr: false
|
||||
});
|
||||
}
|
||||
|
||||
exports.testUnixStderr = function (test) {
|
||||
test.waitUntilDone();
|
||||
let gotStderr = false;
|
||||
|
||||
var p = subprocess.call({
|
||||
// Hope that we don't have to give absolute path on linux ...
|
||||
command: '/bin/sh',
|
||||
arguments: ['nonexistent'],
|
||||
|
||||
stdout: function(data) {
|
||||
test.fail("shouldn't get stdout");
|
||||
},
|
||||
stderr: function(data) {
|
||||
test.assert(!gotStderr,"don't get stderr twice");
|
||||
// There is two variant of error message
|
||||
if (data == "/bin/sh: 0: Can't open nonexistent\n")
|
||||
test.pass("stderr containes the expected error message");
|
||||
else
|
||||
test.assertEqual(data, "/bin/sh: nonexistent: No such file or directory\n",
|
||||
"stderr contains the error message");
|
||||
gotStderr = true;
|
||||
},
|
||||
done: function() {
|
||||
test.assert(gotStderr, "got stderr before finished");
|
||||
test.done();
|
||||
},
|
||||
mergeStderr: false
|
||||
});
|
||||
}
|
||||
|
||||
}
|
10
configure.in
10
configure.in
|
@ -165,11 +165,20 @@ if test -z "$PERL" -o "$PERL" = ":"; then
|
|||
AC_MSG_ERROR([perl not found in \$PATH])
|
||||
fi
|
||||
|
||||
if test -n "$GAIADIR" -a ! -d "$GAIADIR" ; then
|
||||
AC_MSG_ERROR([GAIADIR '$GAIADIR' isn't a valid directory])
|
||||
fi
|
||||
|
||||
AC_SUBST(GAIADIR)
|
||||
if test -n "$GAIADIR" ; then
|
||||
AC_DEFINE(PACKAGE_GAIA)
|
||||
fi
|
||||
|
||||
if test -n "$FXOS_SIMULATOR" -a -z "$GAIADIR" ; then
|
||||
AC_MSG_ERROR([FXOS_SIMULATOR=1 requires GAIADIR to be defined])
|
||||
fi
|
||||
AC_SUBST(FXOS_SIMULATOR)
|
||||
|
||||
MOZ_ARG_WITH_STRING(gonk,
|
||||
[ --with-gonk=DIR
|
||||
location of gonk dir],
|
||||
|
@ -4241,6 +4250,7 @@ AC_SUBST(MOZ_BUILD_APP)
|
|||
AC_SUBST(MOZ_PHOENIX)
|
||||
AC_SUBST(MOZ_XULRUNNER)
|
||||
AC_SUBST(MOZ_B2G)
|
||||
AC_SUBST(MOZ_B2G_VERSION)
|
||||
|
||||
AC_DEFINE_UNQUOTED(MOZ_BUILD_APP,$MOZ_BUILD_APP)
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче