feat(launcher): check dependencies before launch on Windows (#3240)
This commit is contained in:
parent
21eafbcdae
commit
cbfdca736c
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
# include sources from lib except for injected, but not map files
|
# include sources from lib except for injected, but not map files
|
||||||
!lib/**/*.js
|
!lib/**/*.js
|
||||||
|
# Include Windows dependency checker executable.
|
||||||
|
!bin/PrintDeps.exe
|
||||||
# Injected files are included via lib/generated, see src/injected/README.md
|
# Injected files are included via lib/generated, see src/injected/README.md
|
||||||
lib/injected/
|
lib/injected/
|
||||||
#types
|
#types
|
||||||
|
|
|
||||||
BIN
bin/PrintDeps.exe
Normal file
BIN
bin/PrintDeps.exe
Normal file
Binary file not shown.
|
|
@ -15,6 +15,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const os = require('os');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const rmSync = require('rimraf').sync;
|
const rmSync = require('rimraf').sync;
|
||||||
const ncp = require('ncp');
|
const ncp = require('ncp');
|
||||||
|
|
@ -27,7 +28,7 @@ const cpAsync = util.promisify(ncp);
|
||||||
const SCRIPT_NAME = path.basename(__filename);
|
const SCRIPT_NAME = path.basename(__filename);
|
||||||
const ROOT_PATH = path.join(__dirname, '..');
|
const ROOT_PATH = path.join(__dirname, '..');
|
||||||
|
|
||||||
const PLAYWRIGHT_CORE_FILES = ['lib', 'types', 'NOTICE', 'LICENSE', '.npmignore'];
|
const PLAYWRIGHT_CORE_FILES = ['bin', 'lib', 'types', 'NOTICE', 'LICENSE', '.npmignore'];
|
||||||
|
|
||||||
const PACKAGES = {
|
const PACKAGES = {
|
||||||
'playwright': {
|
'playwright': {
|
||||||
|
|
@ -157,7 +158,8 @@ if (!args.some(arg => arg === '--no-cleanup')) {
|
||||||
await writeToPackage('browsers.json', JSON.stringify(browsersJSON, null, 2));
|
await writeToPackage('browsers.json', JSON.stringify(browsersJSON, null, 2));
|
||||||
|
|
||||||
// 6. Run npm pack
|
// 6. Run npm pack
|
||||||
const {stdout, stderr, status} = spawnSync('npm', ['pack'], {cwd: packagePath, encoding: 'utf8'});
|
const shell = os.platform() === 'win32';
|
||||||
|
const {stdout, stderr, status} = spawnSync('npm', ['pack'], {cwd: packagePath, encoding: 'utf8', shell});
|
||||||
if (status !== 0) {
|
if (status !== 0) {
|
||||||
console.log(`ERROR: "npm pack" failed`);
|
console.log(`ERROR: "npm pack" failed`);
|
||||||
console.log(stderr);
|
console.log(stderr);
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,16 @@ export function linuxLddDirectories(browserPath: string, browser: BrowserDescrip
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function windowsExeAndDllDirectories(browserPath: string, browser: BrowserDescriptor): string[] {
|
||||||
|
if (browser.name === 'chromium')
|
||||||
|
return [path.join(browserPath, 'chrome-win')];
|
||||||
|
if (browser.name === 'firefox')
|
||||||
|
return [path.join(browserPath, 'firefox')];
|
||||||
|
if (browser.name === 'webkit')
|
||||||
|
return [browserPath];
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
export function executablePath(browserPath: string, browser: BrowserDescriptor): string | undefined {
|
export function executablePath(browserPath: string, browser: BrowserDescriptor): string | undefined {
|
||||||
let tokens: string[] | undefined;
|
let tokens: string[] | undefined;
|
||||||
if (browser.name === 'chromium') {
|
if (browser.name === 'chromium') {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import * as path from 'path';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import { getUbuntuVersion } from '../helper';
|
import { getUbuntuVersion } from '../helper';
|
||||||
import { linuxLddDirectories, BrowserDescriptor } from '../install/browserPaths.js';
|
import { linuxLddDirectories, windowsExeAndDllDirectories, BrowserDescriptor } from '../install/browserPaths.js';
|
||||||
|
|
||||||
const accessAsync = util.promisify(fs.access.bind(fs));
|
const accessAsync = util.promisify(fs.access.bind(fs));
|
||||||
const checkExecutable = (filePath: string) => accessAsync(filePath, fs.constants.X_OK).then(() => true).catch(e => false);
|
const checkExecutable = (filePath: string) => accessAsync(filePath, fs.constants.X_OK).then(() => true).catch(e => false);
|
||||||
|
|
@ -41,8 +41,65 @@ const DL_OPEN_LIBRARIES = {
|
||||||
|
|
||||||
async function validateDependencies(browserPath: string, browser: BrowserDescriptor) {
|
async function validateDependencies(browserPath: string, browser: BrowserDescriptor) {
|
||||||
// We currently only support Linux.
|
// We currently only support Linux.
|
||||||
if (os.platform() !== 'linux')
|
if (os.platform() === 'linux')
|
||||||
|
return await validateDependenciesLinux(browserPath, browser);
|
||||||
|
if (os.platform() === 'win32' && os.arch() === 'x64')
|
||||||
|
return await validateDependenciesWindows(browserPath, browser);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function validateDependenciesWindows(browserPath: string, browser: BrowserDescriptor) {
|
||||||
|
const directoryPaths = windowsExeAndDllDirectories(browserPath, browser);
|
||||||
|
const lddPaths: string[] = [];
|
||||||
|
for (const directoryPath of directoryPaths)
|
||||||
|
lddPaths.push(...(await executablesOrSharedLibraries(directoryPath)));
|
||||||
|
const allMissingDeps = await Promise.all(lddPaths.map(lddPath => missingFileDependenciesWindows(lddPath)));
|
||||||
|
const missingDeps: Set<string> = new Set();
|
||||||
|
for (const deps of allMissingDeps) {
|
||||||
|
for (const dep of deps)
|
||||||
|
missingDeps.add(dep);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!missingDeps.size)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
let isCrtMissing = false;
|
||||||
|
let isMediaFoundationMissing = false;
|
||||||
|
for (const dep of missingDeps) {
|
||||||
|
if (dep.startsWith('api-ms-win-crt'))
|
||||||
|
isCrtMissing = true;
|
||||||
|
else if (dep === 'mf.dll' || dep === 'mfplat.dll' || dep === 'msmpeg2vdec.dll')
|
||||||
|
isMediaFoundationMissing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const details = [];
|
||||||
|
|
||||||
|
if (isCrtMissing) {
|
||||||
|
details.push(
|
||||||
|
`Some of the Universal C Runtime files cannot be found on the system. You can fix`,
|
||||||
|
`that by installing Microsoft Visual C++ Redistributable for Visual Studio from:`,
|
||||||
|
`https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads`,
|
||||||
|
``);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMediaFoundationMissing) {
|
||||||
|
details.push(
|
||||||
|
`Some of the Media Foundation files cannot be found on the system. If you are`,
|
||||||
|
`on Windows Server try fixing this by running the following command in PowerShell`,
|
||||||
|
`as Administrator:`,
|
||||||
|
``,
|
||||||
|
` Install-WindowsFeature Server-Media-Foundation`,
|
||||||
|
``);
|
||||||
|
}
|
||||||
|
|
||||||
|
details.push(
|
||||||
|
`Full list of missing libraries:`,
|
||||||
|
` ${[...missingDeps].join('\n ')}`,
|
||||||
|
``);
|
||||||
|
|
||||||
|
throw new Error(`Host system is missing dependencies!\n\n${details.join('\n')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function validateDependenciesLinux(browserPath: string, browser: BrowserDescriptor) {
|
||||||
const directoryPaths = linuxLddDirectories(browserPath, browser);
|
const directoryPaths = linuxLddDirectories(browserPath, browser);
|
||||||
const lddPaths: string[] = [];
|
const lddPaths: string[] = [];
|
||||||
for (const directoryPath of directoryPaths)
|
for (const directoryPath of directoryPaths)
|
||||||
|
|
@ -100,6 +157,17 @@ async function validateDependencies(browserPath: string, browser: BrowserDescrip
|
||||||
throw new Error('Host system is missing dependencies!\n\n' + missingPackagesMessage + missingDependenciesMessage);
|
throw new Error('Host system is missing dependencies!\n\n' + missingPackagesMessage + missingDependenciesMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isSharedLib(basename: string) {
|
||||||
|
switch (os.platform()) {
|
||||||
|
case 'linux':
|
||||||
|
return basename.endsWith('.so') || basename.includes('.so.');
|
||||||
|
case 'win32':
|
||||||
|
return basename.endsWith('.dll');
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function executablesOrSharedLibraries(directoryPath: string): Promise<string[]> {
|
async function executablesOrSharedLibraries(directoryPath: string): Promise<string[]> {
|
||||||
const allPaths = (await readdirAsync(directoryPath)).map(file => path.resolve(directoryPath, file));
|
const allPaths = (await readdirAsync(directoryPath)).map(file => path.resolve(directoryPath, file));
|
||||||
const allStats = await Promise.all(allPaths.map(aPath => statAsync(aPath)));
|
const allStats = await Promise.all(allPaths.map(aPath => statAsync(aPath)));
|
||||||
|
|
@ -107,7 +175,7 @@ async function executablesOrSharedLibraries(directoryPath: string): Promise<stri
|
||||||
|
|
||||||
const executablersOrLibraries = (await Promise.all(filePaths.map(async filePath => {
|
const executablersOrLibraries = (await Promise.all(filePaths.map(async filePath => {
|
||||||
const basename = path.basename(filePath).toLowerCase();
|
const basename = path.basename(filePath).toLowerCase();
|
||||||
if (basename.endsWith('.so') || basename.includes('.so.'))
|
if (isSharedLib(basename))
|
||||||
return filePath;
|
return filePath;
|
||||||
if (await checkExecutable(filePath))
|
if (await checkExecutable(filePath))
|
||||||
return filePath;
|
return filePath;
|
||||||
|
|
@ -117,6 +185,21 @@ async function executablesOrSharedLibraries(directoryPath: string): Promise<stri
|
||||||
return executablersOrLibraries as string[];
|
return executablersOrLibraries as string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function missingFileDependenciesWindows(filePath: string): Promise<Array<string>> {
|
||||||
|
const dirname = path.dirname(filePath);
|
||||||
|
const {stdout, code} = await spawnAsync(path.join(__dirname, '../../bin/PrintDeps.exe'), [filePath], {
|
||||||
|
cwd: dirname,
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
LD_LIBRARY_PATH: process.env.LD_LIBRARY_PATH ? `${process.env.LD_LIBRARY_PATH}:${dirname}` : dirname,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (code !== 0)
|
||||||
|
return [];
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
async function missingFileDependencies(filePath: string): Promise<Array<string>> {
|
async function missingFileDependencies(filePath: string): Promise<Array<string>> {
|
||||||
const dirname = path.dirname(filePath);
|
const dirname = path.dirname(filePath);
|
||||||
const {stdout, code} = await spawnAsync('ldd', [filePath], {
|
const {stdout, code} = await spawnAsync('ldd', [filePath], {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue