Skip to content

Commit cbfdca7

Browse files
authored
feat(launcher): check dependencies before launch on Windows (#3240)
1 parent 21eafbc commit cbfdca7

File tree

5 files changed

+102
-5
lines changed

5 files changed

+102
-5
lines changed

.npmignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
# include sources from lib except for injected, but not map files
99
!lib/**/*.js
10+
# Include Windows dependency checker executable.
11+
!bin/PrintDeps.exe
1012
# Injected files are included via lib/generated, see src/injected/README.md
1113
lib/injected/
1214
#types

bin/PrintDeps.exe

232 KB
Binary file not shown.

packages/build_package.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* limitations under the License.
1616
*/
1717
const fs = require('fs');
18+
const os = require('os');
1819
const path = require('path');
1920
const rmSync = require('rimraf').sync;
2021
const ncp = require('ncp');
@@ -27,7 +28,7 @@ const cpAsync = util.promisify(ncp);
2728
const SCRIPT_NAME = path.basename(__filename);
2829
const ROOT_PATH = path.join(__dirname, '..');
2930

30-
const PLAYWRIGHT_CORE_FILES = ['lib', 'types', 'NOTICE', 'LICENSE', '.npmignore'];
31+
const PLAYWRIGHT_CORE_FILES = ['bin', 'lib', 'types', 'NOTICE', 'LICENSE', '.npmignore'];
3132

3233
const PACKAGES = {
3334
'playwright': {
@@ -157,7 +158,8 @@ if (!args.some(arg => arg === '--no-cleanup')) {
157158
await writeToPackage('browsers.json', JSON.stringify(browsersJSON, null, 2));
158159

159160
// 6. Run npm pack
160-
const {stdout, stderr, status} = spawnSync('npm', ['pack'], {cwd: packagePath, encoding: 'utf8'});
161+
const shell = os.platform() === 'win32';
162+
const {stdout, stderr, status} = spawnSync('npm', ['pack'], {cwd: packagePath, encoding: 'utf8', shell});
161163
if (status !== 0) {
162164
console.log(`ERROR: "npm pack" failed`);
163165
console.log(stderr);

src/install/browserPaths.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ export function linuxLddDirectories(browserPath: string, browser: BrowserDescrip
6161
return [];
6262
}
6363

64+
export function windowsExeAndDllDirectories(browserPath: string, browser: BrowserDescriptor): string[] {
65+
if (browser.name === 'chromium')
66+
return [path.join(browserPath, 'chrome-win')];
67+
if (browser.name === 'firefox')
68+
return [path.join(browserPath, 'firefox')];
69+
if (browser.name === 'webkit')
70+
return [browserPath];
71+
return [];
72+
}
73+
6474
export function executablePath(browserPath: string, browser: BrowserDescriptor): string | undefined {
6575
let tokens: string[] | undefined;
6676
if (browser.name === 'chromium') {

src/server/validateDependencies.ts

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import * as path from 'path';
1919
import * as os from 'os';
2020
import { spawn } from 'child_process';
2121
import { getUbuntuVersion } from '../helper';
22-
import { linuxLddDirectories, BrowserDescriptor } from '../install/browserPaths.js';
22+
import { linuxLddDirectories, windowsExeAndDllDirectories, BrowserDescriptor } from '../install/browserPaths.js';
2323

2424
const accessAsync = util.promisify(fs.access.bind(fs));
2525
const checkExecutable = (filePath: string) => accessAsync(filePath, fs.constants.X_OK).then(() => true).catch(e => false);
@@ -41,8 +41,65 @@ const DL_OPEN_LIBRARIES = {
4141

4242
async function validateDependencies(browserPath: string, browser: BrowserDescriptor) {
4343
// We currently only support Linux.
44-
if (os.platform() !== 'linux')
44+
if (os.platform() === 'linux')
45+
return await validateDependenciesLinux(browserPath, browser);
46+
if (os.platform() === 'win32' && os.arch() === 'x64')
47+
return await validateDependenciesWindows(browserPath, browser);
48+
}
49+
50+
async function validateDependenciesWindows(browserPath: string, browser: BrowserDescriptor) {
51+
const directoryPaths = windowsExeAndDllDirectories(browserPath, browser);
52+
const lddPaths: string[] = [];
53+
for (const directoryPath of directoryPaths)
54+
lddPaths.push(...(await executablesOrSharedLibraries(directoryPath)));
55+
const allMissingDeps = await Promise.all(lddPaths.map(lddPath => missingFileDependenciesWindows(lddPath)));
56+
const missingDeps: Set<string> = new Set();
57+
for (const deps of allMissingDeps) {
58+
for (const dep of deps)
59+
missingDeps.add(dep);
60+
}
61+
62+
if (!missingDeps.size)
4563
return;
64+
65+
let isCrtMissing = false;
66+
let isMediaFoundationMissing = false;
67+
for (const dep of missingDeps) {
68+
if (dep.startsWith('api-ms-win-crt'))
69+
isCrtMissing = true;
70+
else if (dep === 'mf.dll' || dep === 'mfplat.dll' || dep === 'msmpeg2vdec.dll')
71+
isMediaFoundationMissing = true;
72+
}
73+
74+
const details = [];
75+
76+
if (isCrtMissing) {
77+
details.push(
78+
`Some of the Universal C Runtime files cannot be found on the system. You can fix`,
79+
`that by installing Microsoft Visual C++ Redistributable for Visual Studio from:`,
80+
`https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads`,
81+
``);
82+
}
83+
84+
if (isMediaFoundationMissing) {
85+
details.push(
86+
`Some of the Media Foundation files cannot be found on the system. If you are`,
87+
`on Windows Server try fixing this by running the following command in PowerShell`,
88+
`as Administrator:`,
89+
``,
90+
` Install-WindowsFeature Server-Media-Foundation`,
91+
``);
92+
}
93+
94+
details.push(
95+
`Full list of missing libraries:`,
96+
` ${[...missingDeps].join('\n ')}`,
97+
``);
98+
99+
throw new Error(`Host system is missing dependencies!\n\n${details.join('\n')}`);
100+
}
101+
102+
async function validateDependenciesLinux(browserPath: string, browser: BrowserDescriptor) {
46103
const directoryPaths = linuxLddDirectories(browserPath, browser);
47104
const lddPaths: string[] = [];
48105
for (const directoryPath of directoryPaths)
@@ -100,14 +157,25 @@ async function validateDependencies(browserPath: string, browser: BrowserDescrip
100157
throw new Error('Host system is missing dependencies!\n\n' + missingPackagesMessage + missingDependenciesMessage);
101158
}
102159

160+
function isSharedLib(basename: string) {
161+
switch (os.platform()) {
162+
case 'linux':
163+
return basename.endsWith('.so') || basename.includes('.so.');
164+
case 'win32':
165+
return basename.endsWith('.dll');
166+
default:
167+
return false;
168+
}
169+
}
170+
103171
async function executablesOrSharedLibraries(directoryPath: string): Promise<string[]> {
104172
const allPaths = (await readdirAsync(directoryPath)).map(file => path.resolve(directoryPath, file));
105173
const allStats = await Promise.all(allPaths.map(aPath => statAsync(aPath)));
106174
const filePaths = allPaths.filter((aPath, index) => allStats[index].isFile());
107175

108176
const executablersOrLibraries = (await Promise.all(filePaths.map(async filePath => {
109177
const basename = path.basename(filePath).toLowerCase();
110-
if (basename.endsWith('.so') || basename.includes('.so.'))
178+
if (isSharedLib(basename))
111179
return filePath;
112180
if (await checkExecutable(filePath))
113181
return filePath;
@@ -117,6 +185,21 @@ async function executablesOrSharedLibraries(directoryPath: string): Promise<stri
117185
return executablersOrLibraries as string[];
118186
}
119187

188+
async function missingFileDependenciesWindows(filePath: string): Promise<Array<string>> {
189+
const dirname = path.dirname(filePath);
190+
const {stdout, code} = await spawnAsync(path.join(__dirname, '../../bin/PrintDeps.exe'), [filePath], {
191+
cwd: dirname,
192+
env: {
193+
...process.env,
194+
LD_LIBRARY_PATH: process.env.LD_LIBRARY_PATH ? `${process.env.LD_LIBRARY_PATH}:${dirname}` : dirname,
195+
},
196+
});
197+
if (code !== 0)
198+
return [];
199+
const missingDeps = stdout.split('\n').map(line => line.trim()).filter(line => line.endsWith('not found') && line.includes('=>')).map(line => line.split('=>')[0].trim());
200+
return missingDeps;
201+
}
202+
120203
async function missingFileDependencies(filePath: string): Promise<Array<string>> {
121204
const dirname = path.dirname(filePath);
122205
const {stdout, code} = await spawnAsync('ldd', [filePath], {

0 commit comments

Comments
 (0)