Skip to content

Commit

Permalink
feat(launcher): check dependencies before launch on Windows (#3240)
Browse files Browse the repository at this point in the history
  • Loading branch information
yury-s authored Jul 31, 2020
1 parent 21eafbc commit cbfdca7
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 5 deletions.
2 changes: 2 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

# include sources from lib except for injected, but not map files
!lib/**/*.js
# Include Windows dependency checker executable.
!bin/PrintDeps.exe
# Injected files are included via lib/generated, see src/injected/README.md
lib/injected/
#types
Expand Down
Binary file added bin/PrintDeps.exe
Binary file not shown.
6 changes: 4 additions & 2 deletions packages/build_package.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* limitations under the License.
*/
const fs = require('fs');
const os = require('os');
const path = require('path');
const rmSync = require('rimraf').sync;
const ncp = require('ncp');
Expand All @@ -27,7 +28,7 @@ const cpAsync = util.promisify(ncp);
const SCRIPT_NAME = path.basename(__filename);
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 = {
'playwright': {
Expand Down Expand Up @@ -157,7 +158,8 @@ if (!args.some(arg => arg === '--no-cleanup')) {
await writeToPackage('browsers.json', JSON.stringify(browsersJSON, null, 2));

// 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) {
console.log(`ERROR: "npm pack" failed`);
console.log(stderr);
Expand Down
10 changes: 10 additions & 0 deletions src/install/browserPaths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ export function linuxLddDirectories(browserPath: string, browser: BrowserDescrip
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 {
let tokens: string[] | undefined;
if (browser.name === 'chromium') {
Expand Down
89 changes: 86 additions & 3 deletions src/server/validateDependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import * as path from 'path';
import * as os from 'os';
import { spawn } from 'child_process';
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 checkExecutable = (filePath: string) => accessAsync(filePath, fs.constants.X_OK).then(() => true).catch(e => false);
Expand All @@ -41,8 +41,65 @@ const DL_OPEN_LIBRARIES = {

async function validateDependencies(browserPath: string, browser: BrowserDescriptor) {
// 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;

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 lddPaths: string[] = [];
for (const directoryPath of directoryPaths)
Expand Down Expand Up @@ -100,14 +157,25 @@ async function validateDependencies(browserPath: string, browser: BrowserDescrip
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[]> {
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.'))
if (isSharedLib(basename))
return filePath;
if (await checkExecutable(filePath))
return filePath;
Expand All @@ -117,6 +185,21 @@ async function executablesOrSharedLibraries(directoryPath: string): Promise<stri
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>> {
const dirname = path.dirname(filePath);
const {stdout, code} = await spawnAsync('ldd', [filePath], {
Expand Down

0 comments on commit cbfdca7

Please sign in to comment.