Merge qemu-ts.ts and qemu.ts files

This commit is contained in:
Pagan Gazzard 2020-04-30 21:25:54 +01:00
parent 52bece7f17
commit cbcd7694a9
5 changed files with 70 additions and 92 deletions

View File

@ -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}`);

View File

@ -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<boolean> {
// 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<boolean> {
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;
}

View File

@ -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<boolean> {
// 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<boolean> {
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;
}

View File

@ -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}`,

View File

@ -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);
});
});