From cbcd7694a9b4b5cb3a61711f051227059ffbd272 Mon Sep 17 00:00:00 2001 From: Pagan Gazzard Date: Thu, 30 Apr 2020 21:25:54 +0100 Subject: [PATCH] Merge qemu-ts.ts and qemu.ts files --- lib/utils/compose.js | 2 +- lib/utils/qemu-ts.ts | 84 ------------------------------------ lib/utils/qemu.ts | 73 ++++++++++++++++++++++++++++--- tests/commands/build.spec.ts | 1 - tests/utils/qemu.spec.ts | 2 +- 5 files changed, 70 insertions(+), 92 deletions(-) delete mode 100644 lib/utils/qemu-ts.ts diff --git a/lib/utils/compose.js b/lib/utils/compose.js index 67c996bc..8ed8ef77 100644 --- a/lib/utils/compose.js +++ b/lib/utils/compose.js @@ -232,7 +232,7 @@ export function buildProject( checkBuildSecretsRequirements, makeBuildTasks, } = require('./compose_ts'); - const qemu = require('./qemu-ts'); + const qemu = require('./qemu'); const { toPosixPath } = builder.PathUtils; logger.logInfo(`Building for ${arch}/${deviceType}`); diff --git a/lib/utils/qemu-ts.ts b/lib/utils/qemu-ts.ts deleted file mode 100644 index 67938074..00000000 --- a/lib/utils/qemu-ts.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * @license - * Copyright 2019 Balena Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { stripIndent } from 'common-tags'; -import Dockerode = require('dockerode'); - -import Logger = require('./logger'); -import { getQemuPath, installQemu } from './qemu'; - -export { QEMU_BIN_NAME } from './qemu'; -export * from './qemu'; - -export async function installQemuIfNeeded( - emulated: boolean, - logger: Logger, - arch: string, - docker: Dockerode, -): Promise { - // call platformNeedsQemu() regardless of whether emulation is required, - // because it logs useful information - const needsQemu = await platformNeedsQemu(docker, logger); - if (!emulated || !needsQemu) { - return false; - } - const fs = await import('mz/fs'); - if (!(await fs.exists(await getQemuPath(arch)))) { - logger.logInfo(`Installing qemu for ${arch} emulation...`); - await installQemu(arch); - } - return true; -} - -/** - * Check whether the Docker daemon (including balenaEngine) requires explicit - * QEMU emulation setup. Note that Docker Desktop (Windows and Mac), and also - * the older Docker for Mac, have built-in support for binfmt_misc, so they - * do not require explicit QEMU setup. References: - * - https://en.wikipedia.org/wiki/Binfmt_misc - * - https://docs.docker.com/docker-for-mac/multi-arch/ - * - https://www.ecliptik.com/Cross-Building-and-Running-Multi-Arch-Docker-Images/ - * - https://stackoverflow.com/questions/55388725/run-linux-arm-container-via-qemu-binfmt-misc-on-docker-lcow - * - * @param docker Dockerode instance - */ -async function platformNeedsQemu( - docker: Dockerode, - logger: Logger, -): Promise { - const dockerInfo = await docker.info(); - // Docker Desktop (Windows and Mac) with Docker Engine 19.03 reports: - // OperatingSystem: Docker Desktop - // OSType: linux - // Docker for Mac with Docker Engine 18.06 reports: - // OperatingSystem: Docker for Mac - // OSType: linux - // On Ubuntu (standard Docker installation): - // OperatingSystem: Ubuntu 18.04.2 LTS (containerized) - // OSType: linux - // https://stackoverflow.com/questions/38223965/how-can-i-detect-if-docker-for-mac-is-installed - const isDockerDesktop = /(?:Docker Desktop)|(?:Docker for Mac)/i.test( - dockerInfo.OperatingSystem, - ); - if (isDockerDesktop) { - logger.logInfo(stripIndent` - Docker Desktop detected (daemon architecture: "${dockerInfo.Architecture}") - Docker itself will determine and enable architecture emulation if required, - without balena-cli intervention and regardless of the --emulated option.`); - } - return !isDockerDesktop; -} diff --git a/lib/utils/qemu.ts b/lib/utils/qemu.ts index ea721799..af155ec1 100644 --- a/lib/utils/qemu.ts +++ b/lib/utils/qemu.ts @@ -15,8 +15,12 @@ * limitations under the License. */ -import * as Promise from 'bluebird'; +import * as Bluebird from 'bluebird'; +import { stripIndent } from 'common-tags'; +import Dockerode = require('dockerode'); import { getBalenaSdk } from './lazy'; +import Logger = require('./logger'); + export const QEMU_VERSION = 'v4.0.0+balena2'; export const QEMU_BIN_NAME = 'qemu-execve'; @@ -34,14 +38,14 @@ export function copyQemu(context: string, arch: string) { const binDir = path.join(context, '.balena'); const binPath = path.join(binDir, QEMU_BIN_NAME); - return Promise.resolve(fs.mkdir(binDir)) + return Bluebird.resolve(fs.mkdir(binDir)) .catch({ code: 'EEXIST' }, function() { // noop }) .then(() => getQemuPath(arch)) .then( qemu => - new Promise(function(resolve, reject) { + new Bluebird(function(resolve, reject) { const read = fs.createReadStream(qemu); const write = fs.createWriteStream(binPath); @@ -62,7 +66,7 @@ export const getQemuPath = function(arch: string) { const fs = require('mz/fs') as typeof import('mz/fs'); return balena.settings.get('binDirectory').then(binDir => - Promise.resolve(fs.mkdir(binDir)) + Bluebird.resolve(fs.mkdir(binDir)) .catch({ code: 'EEXIST' }, function() { // noop }) @@ -80,7 +84,7 @@ export function installQemu(arch: string) { return getQemuPath(arch).then( qemuPath => - new Promise(function(resolve, reject) { + new Bluebird(function(resolve, reject) { const installStream = fs.createWriteStream(qemuPath); const qemuArch = balenaArchToQemuArch(arch); @@ -127,3 +131,62 @@ const balenaArchToQemuArch = function(arch: string) { throw new Error(`Cannot install emulator for architecture ${arch}`); } }; + +export async function installQemuIfNeeded( + emulated: boolean, + logger: Logger, + arch: string, + docker: Dockerode, +): Promise { + // call platformNeedsQemu() regardless of whether emulation is required, + // because it logs useful information + const needsQemu = await platformNeedsQemu(docker, logger); + if (!emulated || !needsQemu) { + return false; + } + const fs = await import('mz/fs'); + if (!(await fs.exists(await getQemuPath(arch)))) { + logger.logInfo(`Installing qemu for ${arch} emulation...`); + await installQemu(arch); + } + return true; +} + +/** + * Check whether the Docker daemon (including balenaEngine) requires explicit + * QEMU emulation setup. Note that Docker Desktop (Windows and Mac), and also + * the older Docker for Mac, have built-in support for binfmt_misc, so they + * do not require explicit QEMU setup. References: + * - https://en.wikipedia.org/wiki/Binfmt_misc + * - https://docs.docker.com/docker-for-mac/multi-arch/ + * - https://www.ecliptik.com/Cross-Building-and-Running-Multi-Arch-Docker-Images/ + * - https://stackoverflow.com/questions/55388725/run-linux-arm-container-via-qemu-binfmt-misc-on-docker-lcow + * + * @param docker Dockerode instance + */ +async function platformNeedsQemu( + docker: Dockerode, + logger: Logger, +): Promise { + const dockerInfo = await docker.info(); + // Docker Desktop (Windows and Mac) with Docker Engine 19.03 reports: + // OperatingSystem: Docker Desktop + // OSType: linux + // Docker for Mac with Docker Engine 18.06 reports: + // OperatingSystem: Docker for Mac + // OSType: linux + // On Ubuntu (standard Docker installation): + // OperatingSystem: Ubuntu 18.04.2 LTS (containerized) + // OSType: linux + // https://stackoverflow.com/questions/38223965/how-can-i-detect-if-docker-for-mac-is-installed + const isDockerDesktop = /(?:Docker Desktop)|(?:Docker for Mac)/i.test( + dockerInfo.OperatingSystem, + ); + if (isDockerDesktop) { + logger.logInfo(stripIndent` + Docker Desktop detected (daemon architecture: "${dockerInfo.Architecture}") + Docker itself will determine and enable architecture emulation if required, + without balena-cli intervention and regardless of the --emulated option.`); + } + return !isDockerDesktop; +} diff --git a/tests/commands/build.spec.ts b/tests/commands/build.spec.ts index 93a3a6be..e38cba88 100644 --- a/tests/commands/build.spec.ts +++ b/tests/commands/build.spec.ts @@ -176,7 +176,6 @@ describe('balena build', function() { copyQemu: async () => '', }); mock.reRequire('../../build/utils/qemu'); - mock.reRequire('../../build/utils/qemu-ts'); docker.expectGetInfo({ OperatingSystem: 'balenaOS 2.44.0+rev1' }); await testDockerBuildStream({ commandLine: `build ${projectPath} --emulated --deviceType ${deviceType} --arch ${arch}`, diff --git a/tests/utils/qemu.spec.ts b/tests/utils/qemu.spec.ts index d68ddf87..99e62145 100644 --- a/tests/utils/qemu.spec.ts +++ b/tests/utils/qemu.spec.ts @@ -20,7 +20,7 @@ import { expect } from 'chai'; describe('resin-multibuild consistency', function() { it('should use the same values for selected constants', async () => { const { QEMU_BIN_NAME: MQEMU_BIN_NAME } = await import('resin-multibuild'); - const { QEMU_BIN_NAME } = await import('../../build/utils/qemu-ts'); + const { QEMU_BIN_NAME } = await import('../../build/utils/qemu'); expect(QEMU_BIN_NAME).to.equal(MQEMU_BIN_NAME); }); });