Bug 1469054 - Extract adb binary and related files into local profile directory from the extension. r?jdescottes
The new devtools-adb-extension should have adb.json on the top of the extension
directory, and devtools fetches the adb.json and parses it then copies the blobs
into local profile directory.
NOTE: Though we are going to use the same adb.json for all platforms, we will
pack only the corresponding blobs for each platform. E.g. the extension for
Linux64 will have only linux64/adb.
The adb.json should look like this;
{
"Linux": {
"x86": [
"linux/adb"
],
"x86_64": [
"linux64/adb"
]
},
"Darwin": {
"x86_64": [
"mac64/adb"
]
},
"WINNT": {
"x86": [
"win32/adb.exe",
"win32/AdbWinApi.dll",
"win32/AdbWinUsbApi.dll"
],
"x86_64": [
"win32/adb.exe",
"win32/AdbWinApi.dll",
"win32/AdbWinUsbApi.dll"
]
}
}
unpackFile() here is mostly the same as in binary-manager.js in the adbhelper
addon.
MozReview-Commit-ID: 7aprfaaeTAT
new file mode 100644
--- /dev/null
+++ b/devtools/shared/adb/adb-binary.js
@@ -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/. */
+
+"use strict";
+
+loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm");
+loader.lazyImporter(this, "ExtensionParent", "resource://gre/modules/ExtensionParent.jsm");
+loader.lazyRequireGetter(this, "Services");
+loader.lazyRequireGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm", true);
+loader.lazyRequireGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm", true);
+loader.lazyGetter(this, "UNPACKED_ROOT_PATH", () => {
+ return OS.Path.join(OS.Constants.Path.localProfileDir, "adb");
+});
+
+// FIXME: Bug 1481691 - Read the extension ID from a pref.
+const EXTENSION_ID = "adb@mozilla.org";
+
+async function getAdbInfo(adbUri) {
+ return new Promise(resolve => {
+ NetUtil.asyncFetch({
+ uri: adbUri,
+ loadUsingSystemPrincipal: true
+ }, (input) => {
+ try {
+ const string = NetUtil.readInputStreamToString(input, input.available());
+ resolve(JSON.parse(string));
+ } catch (e) {
+ console.log(`Could not read adb.json in the extension: ${e}`);
+ resolve(null);
+ }
+ });
+ });
+}
+
+/**
+ * Unpack file from the extension.
+ * Uses NetUtil to read and write, since it's required for reading.
+ *
+ * @param {string} file
+ * The path name of the file in the extension, such as "win32/adb.exe".
+ * This has to have a '/' in the path string.
+ */
+async function unpackFile(file) {
+ const policy = ExtensionParent.WebExtensionPolicy.getByID(EXTENSION_ID);
+ if (!policy) {
+ return;
+ }
+
+ // Assumes that destination dir already exists.
+ const filePath = OS.Path.join(UNPACKED_ROOT_PATH, file.split("/")[1]);
+ await new Promise((resolve, reject) => {
+ NetUtil.asyncFetch({
+ uri: policy.getURL(file),
+ loadUsingSystemPrincipal: true
+ }, (input) => {
+ try {
+ // Since we have to use NetUtil to read, probably it's okay to use for
+ // writing, rather than bouncing to OS.File...?
+ const outputFile = new FileUtils.File(filePath);
+ const output = FileUtils.openAtomicFileOutputStream(outputFile);
+ NetUtil.asyncCopy(input, output, resolve);
+ } catch (e) {
+ console.log(`Could not unpack file ${file} in the extension: ${e}`);
+ reject(e);
+ }
+ });
+ });
+ // Mark binaries as executable.
+ await OS.File.setPermissions(filePath, { unixMode: 0o744 });
+}
+
+/**
+ * Extract files in the extension into local profile directory and returns
+ * the path for the adb binary.
+ */
+async function extractBinary() {
+ const policy = ExtensionParent.WebExtensionPolicy.getByID(EXTENSION_ID);
+ if (!policy) {
+ return null;
+ }
+ const uri = policy.getURL("adb.json");
+
+ const adbInfo = await getAdbInfo(uri);
+ if (!adbInfo) {
+ return null;
+ }
+
+ let filesForAdb;
+ try {
+ // The adbInfo is an object looks like this;
+ //
+ // {
+ // "Linux": {
+ // "x86": [
+ // "linux/adb"
+ // ],
+ // "x86_64": [
+ // "linux64/adb"
+ // ]
+ // },
+ // ...
+ filesForAdb =
+ // XPCOMABI looks this; x86_64-gcc3, so drop the compiler name.
+ adbInfo[Services.appinfo.OS][Services.appinfo.XPCOMABI.split("-")[0]];
+ } catch (e) {
+ return null;
+ }
+
+ await OS.File.makeDir(UNPACKED_ROOT_PATH);
+
+ for (const file of filesForAdb) {
+ try {
+ await unpackFile(file);
+ } catch (e) {
+ return null;
+ }
+ }
+
+ let adbBinaryPath = OS.Path.join(UNPACKED_ROOT_PATH, "adb");
+
+ if (Services.appinfo.OS === "WINNT") {
+ adbBinaryPath += ".exe";
+ }
+ return adbBinaryPath;
+}
+
+/**
+ * Get a file object for the adb binary from the 'adb@mozilla.org' extension
+ * which has been already installed.
+ *
+ * @return {nsIFile}
+ * File object for the binary.
+ */
+async function getFileForBinary() {
+ const path = await extractBinary();
+ if (!path) {
+ return null;
+ }
+ console.log(`Binary path: ${path}`);
+ return new FileUtils.File(path);
+}
+exports.getFileForBinary = getFileForBinary;
new file mode 100644
--- /dev/null
+++ b/devtools/shared/adb/moz.build
@@ -0,0 +1,12 @@
+# 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/.
+
+DevToolsModules(
+ 'adb-binary.js',
+)
+
+with Files('**'):
+ BUG_COMPONENT = ('DevTools', 'about:debugging')
+
+XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
new file mode 100644
--- /dev/null
+++ b/devtools/shared/adb/test/.eslintrc.js
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+ "extends": "../../../.eslintrc.xpcshell.js",
+};
new file mode 100644
--- /dev/null
+++ b/devtools/shared/adb/test/test_adb.js
@@ -0,0 +1,124 @@
+"use strict";
+
+const { ExtensionTestUtils } = ChromeUtils.import("resource://testing-common/ExtensionXPCShellUtils.jsm", {});
+const { getFileForBinary } = require("devtools/shared/adb/adb-binary");
+
+const ADB_JSON = {
+ "Linux": {
+ "x86": [
+ "linux/adb"
+ ],
+ "x86_64": [
+ "linux64/adb"
+ ]
+ },
+ "Darwin": {
+ "x86_64": [
+ "mac64/adb"
+ ]
+ },
+ "WINNT": {
+ "x86": [
+ "win32/adb.exe",
+ "win32/AdbWinApi.dll",
+ "win32/AdbWinUsbApi.dll"
+ ],
+ "x86_64": [
+ "win32/adb.exe",
+ "win32/AdbWinApi.dll",
+ "win32/AdbWinUsbApi.dll"
+ ]
+ }
+};
+
+ExtensionTestUtils.init(this);
+
+add_task(async function setup() {
+ // Prepare the profile directory where the adb extension will be installed.
+ do_get_profile();
+});
+
+add_task(async function testNoAdbExtension() {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ version: "1.0",
+ applications: {
+ gecko: { id: "not-adb@mozilla.org" }
+ }
+ },
+ });
+
+ await extension.startup();
+
+ const adbBinary = await getFileForBinary();
+ equal(adbBinary, null);
+
+ await extension.unload();
+});
+
+add_task(async function testNoAdbJSON() {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ version: "1.0",
+ applications: {
+ gecko: { id: "adb@mozilla.org" }
+ }
+ },
+ });
+
+ await extension.startup();
+
+ const adbBinary = await getFileForBinary();
+ equal(adbBinary, null);
+
+ await extension.unload();
+});
+
+add_task(async function testNoTargetBinaries() {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ version: "1.0",
+ applications: {
+ gecko: { id: "adb@mozilla.org" }
+ }
+ },
+ files: {
+ "adb.json": JSON.stringify(ADB_JSON),
+ },
+ });
+
+ await extension.startup();
+
+ const adbBinary = await getFileForBinary();
+ equal(adbBinary, null);
+
+ await extension.unload();
+});
+
+add_task(async function testExtract() {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ version: "1.0",
+ applications: {
+ gecko: { id: "adb@mozilla.org" }
+ }
+ },
+ files: {
+ "adb.json": JSON.stringify(ADB_JSON),
+ "linux/adb": "adb",
+ "linux64/adb": "adb",
+ "mac64/adb": "adb",
+ "win32/adb.exe": "adb.exe",
+ "win32/AdbWinApi.dll": "AdbWinApi.dll",
+ "win32/AdbWinUsbApi.dll": "AdbWinUsbApi.dll"
+ },
+ });
+
+ await extension.startup();
+
+ const adbBinary = await getFileForBinary();
+ ok(await adbBinary.exists);
+
+ await extension.unload();
+});
+
new file mode 100644
--- /dev/null
+++ b/devtools/shared/adb/test/xpcshell-head.js
@@ -0,0 +1,8 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+
+const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
new file mode 100644
--- /dev/null
+++ b/devtools/shared/adb/test/xpcshell.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+tags = devtools
+head = xpcshell-head.js
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+[test_adb.js]
+run-sequentially = An extension having the same id is installed/uninstalled in different tests
--- a/devtools/shared/moz.build
+++ b/devtools/shared/moz.build
@@ -3,16 +3,17 @@
# 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/.
include('../templates.mozbuild')
DIRS += [
'acorn',
+ 'adb',
'apps',
'client',
'css',
'discovery',
'fronts',
'gcli',
'heapsnapshot',
'inspector',