Bug 1469054 - Extract adb binary and related files into local profile directory from the extension. r?jdescottes draft
authorHiroyuki Ikezoe <hikezoe@mozilla.com>
Thu, 09 Aug 2018 14:27:55 +0900
changeset 827773 2c10e2ce3468ade4257b1159b55514f1e7e84c9c
parent 827540 17116905bc072c37d74226ccc46c93f0bd45d516
child 827774 17955c7c342140cef7b0bfe1b570664fd58a03f6
push id118583
push userhikezoe@mozilla.com
push dateThu, 09 Aug 2018 06:03:03 +0000
reviewersjdescottes
bugs1469054
milestone63.0a1
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
devtools/shared/adb/adb-binary.js
devtools/shared/adb/moz.build
devtools/shared/adb/test/.eslintrc.js
devtools/shared/adb/test/test_adb.js
devtools/shared/adb/test/xpcshell-head.js
devtools/shared/adb/test/xpcshell.ini
devtools/shared/moz.build
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',