playwright/src/server/validateDependencies.ts

89 lines
3.5 KiB
TypeScript
Raw Normal View History

/**
* Copyright (c) Microsoft Corporation.
*
* 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 * as fs from 'fs';
import * as util from 'util';
import * as path from 'path';
import * as os from 'os';
import {spawn} from 'child_process';
import {linuxLddDirectories, BrowserDescriptor} from '../install/browserPaths.js';
const accessAsync = util.promisify(fs.access.bind(fs));
const checkExecutable = (filePath: string) => accessAsync(filePath, fs.constants.X_OK).then(() => true).catch(e => false);
const statAsync = util.promisify(fs.stat.bind(fs));
const readdirAsync = util.promisify(fs.readdir.bind(fs));
export async function validateDependencies(browserPath: string, browser: BrowserDescriptor) {
// We currently only support Linux.
if (os.platform() !== 'linux')
return;
const directoryPaths = linuxLddDirectories(browserPath, browser);
const lddPaths: string[] = [];
for (const directoryPath of directoryPaths)
lddPaths.push(...(await executablesOrSharedLibraries(directoryPath)));
const allMissingDeps = await Promise.all(lddPaths.map(lddPath => missingFileDependencies(lddPath)));
const missingDeps = new Set();
for (const deps of allMissingDeps) {
for (const dep of deps)
missingDeps.add(dep);
}
if (!missingDeps.size)
return;
const deps = [...missingDeps].sort().map(dep => ' ' + dep).join('\n');
throw new Error('Host system is missing the following dependencies to run browser\n' + deps);
}
async function executablesOrSharedLibraries(directoryPath: string): Promise<string[]> {
const allPaths = (await readdirAsync(directoryPath)).map(file => path.resolve(directoryPath, file));
const allStats = await Promise.all(allPaths.map(aPath => statAsync(aPath)));
const filePaths = allPaths.filter((aPath, index) => allStats[index].isFile());
const executablersOrLibraries = (await Promise.all(filePaths.map(async filePath => {
const basename = path.basename(filePath).toLowerCase();
if (basename.endsWith('.so') || basename.includes('.so.'))
return filePath;
if (await checkExecutable(filePath))
return filePath;
return false;
}))).filter(Boolean);
return executablersOrLibraries as string[];
}
async function missingFileDependencies(filePath: string): Promise<Array<string>> {
const {stdout} = await lddAsync(filePath);
const missingDeps = stdout.split('\n').map(line => line.trim()).filter(line => line.endsWith('not found') && line.includes('=>')).map(line => line.split('=>')[0].trim());
return missingDeps;
}
function lddAsync(filePath: string): Promise<{stdout: string, stderr: string, code: number}> {
const dirname = path.dirname(filePath);
const ldd = spawn('ldd', [filePath], {
cwd: dirname,
env: {
...process.env,
LD_LIBRARY_PATH: dirname,
},
});
return new Promise(resolve => {
let stdout = '';
let stderr = '';
ldd.stdout.on('data', data => stdout += data);
ldd.stderr.on('data', data => stderr += data);
ldd.on('close', code => resolve({stdout, stderr, code}));
});
}