Skip to content

Commit 44d96a8

Browse files
committed
devops: re-factor list-dependencies script to output per-browser results
This refactors script to output per-browser package dependencies that could be easily copied over to the Github Action. References microsoft#2745
1 parent 101dd3b commit 44d96a8

File tree

4 files changed

+871
-97
lines changed

4 files changed

+871
-97
lines changed

src/server/validateDependencies.ts

+65-66
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,6 @@ const LIBRARY_TO_PACKAGE_NAME_UBUNTU_18_04: { [s: string]: string} = {
204204
'libpng16.so.16': 'libpng16-16',
205205
'libsecret-1.so.0': 'libsecret-1-0',
206206
'libsmime3.so': 'libnss3',
207-
'libssl3.so': 'libnss3',
208207
'libvpx.so.5': 'libvpx5',
209208
'libwayland-client.so.0': 'libwayland-client0',
210209
'libwayland-egl.so.1': 'libwayland-egl1',
@@ -221,85 +220,85 @@ const LIBRARY_TO_PACKAGE_NAME_UBUNTU_18_04: { [s: string]: string} = {
221220
};
222221

223222
const LIBRARY_TO_PACKAGE_NAME_UBUNTU_20_04: { [s: string]: string} = {
224-
'libglib-2.0.so.0': 'libglib2.0-0',
225-
'libX11.so.6': 'libx11-6',
226-
'libxcb.so.1': 'libxcb1',
227-
'libGL.so.1': 'libgl1',
228223
'libEGL.so.1': 'libegl1',
229-
'libnotify.so.4': 'libnotify4',
230-
'libgdk_pixbuf-2.0.so.0': 'libgdk-pixbuf2.0-0',
231-
'libgio-2.0.so.0': 'libglib2.0-0',
232-
'libgobject-2.0.so.0': 'libglib2.0-0',
233-
'libvpx.so.6': 'libvpx6',
234-
'libopus.so.0': 'libopus0',
235-
'libxml2.so.2': 'libxml2',
236-
'libicui18n.so.66': 'libicu66',
237-
'libicuuc.so.66': 'libicu66',
238-
'libxslt.so.1': 'libxslt1.1',
239-
'libwoff2dec.so.1.0.2': 'libwoff1',
224+
'libGL.so.1': 'libgl1',
225+
'libX11-xcb.so.1': 'libx11-xcb1',
226+
'libX11.so.6': 'libx11-6',
227+
'libXcomposite.so.1': 'libxcomposite1',
228+
'libXcursor.so.1': 'libxcursor1',
229+
'libXdamage.so.1': 'libxdamage1',
230+
'libXext.so.6': 'libxext6',
231+
'libXfixes.so.3': 'libxfixes3',
232+
'libXi.so.6': 'libxi6',
233+
'libXrandr.so.2': 'libxrandr2',
234+
'libXrender.so.1': 'libxrender1',
235+
'libXt.so.6': 'libxt6',
236+
'libXtst.so.6': 'libxtst6',
237+
'libasound.so.2': 'libasound2',
238+
'libatk-1.0.so.0': 'libatk1.0-0',
239+
'libatk-bridge-2.0.so.0': 'libatk-bridge2.0-0',
240+
'libatspi.so.0': 'libatspi2.0-0',
241+
'libcairo-gobject.so.2': 'libcairo-gobject2',
240242
'libcairo.so.2': 'libcairo2',
243+
'libcups.so.2': 'libcups2',
244+
'libdbus-1.so.3': 'libdbus-1-3',
245+
'libdbus-glib-1.so.2': 'libdbus-glib-1-2',
246+
'libdrm.so.2': 'libdrm2',
247+
'libenchant.so.1': 'libenchant1c2a',
248+
'libepoxy.so.0': 'libepoxy0',
241249
'libfontconfig.so.1': 'libfontconfig1',
242250
'libfreetype.so.6': 'libfreetype6',
243-
'libharfbuzz.so.0': 'libharfbuzz0b',
244-
'libharfbuzz-icu.so.0': 'libharfbuzz-icu0',
251+
'libgbm.so.1': 'libgbm1',
252+
'libgdk-3.so.0': 'libgtk-3-0',
253+
'libgdk-x11-2.0.so.0': 'libgtk2.0-0',
254+
'libgdk_pixbuf-2.0.so.0': 'libgdk-pixbuf2.0-0',
255+
'libgio-2.0.so.0': 'libglib2.0-0',
256+
'libglib-2.0.so.0': 'libglib2.0-0',
257+
'libgmodule-2.0.so.0': 'libglib2.0-0',
258+
'libgobject-2.0.so.0': 'libglib2.0-0',
245259
'libgstapp-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
260+
'libgstaudio-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
246261
'libgstbase-1.0.so.0': 'libgstreamer1.0-0',
247-
'libgstreamer-1.0.so.0': 'libgstreamer1.0-0',
262+
'libgstcodecparsers-1.0.so.0': 'libgstreamer-plugins-bad1.0-0',
263+
'libgstfft-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
264+
'libgstgl-1.0.so.0': 'libgstreamer-gl1.0-0',
248265
'libgstpbutils-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
249-
'libgstaudio-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
266+
'libgstreamer-1.0.so.0': 'libgstreamer1.0-0',
250267
'libgsttag-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
251268
'libgstvideo-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
252-
'libgstgl-1.0.so.0': 'libgstreamer-gl1.0-0',
253-
'libgstcodecparsers-1.0.so.0': 'libgstreamer-plugins-bad1.0-0',
254-
'libgstfft-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
269+
'libgthread-2.0.so.0': 'libglib2.0-0',
270+
'libgtk-3.so.0': 'libgtk-3-0',
271+
'libgtk-x11-2.0.so.0': 'libgtk2.0-0',
272+
'libharfbuzz-icu.so.0': 'libharfbuzz-icu0',
273+
'libharfbuzz.so.0': 'libharfbuzz0b',
274+
'libhyphen.so.0': 'libhyphen0',
275+
'libicui18n.so.66': 'libicu66',
276+
'libicuuc.so.66': 'libicu66',
255277
'libjpeg.so.8': 'libjpeg-turbo8',
256-
'libpng16.so.16': 'libpng16-16',
278+
'libnotify.so.4': 'libnotify4',
279+
'libnspr4.so': 'libnspr4',
280+
'libnss3.so': 'libnss3',
281+
'libnssutil3.so': 'libnss3',
257282
'libopenjp2.so.7': 'libopenjp2-7',
258-
'libwebpdemux.so.2': 'libwebpdemux2',
259-
'libwebp.so.6': 'libwebp6',
260-
'libsoup-2.4.so.1': 'libsoup2.4-1',
261-
'libenchant.so.1': 'libenchant1c2a',
262-
'libgmodule-2.0.so.0': 'libglib2.0-0',
263-
'libsecret-1.so.0': 'libsecret-1-0',
264-
'libhyphen.so.0': 'libhyphen0',
265-
'libXcomposite.so.1': 'libxcomposite1',
266-
'libXdamage.so.1': 'libxdamage1',
267-
'libwayland-server.so.0': 'libwayland-server0',
268-
'libwayland-egl.so.1': 'libwayland-egl1',
269-
'libwayland-client.so.0': 'libwayland-client0',
270-
'libgtk-3.so.0': 'libgtk-3-0',
271-
'libgdk-3.so.0': 'libgtk-3-0',
283+
'libopus.so.0': 'libopus0',
272284
'libpango-1.0.so.0': 'libpango-1.0-0',
273-
'libatk-1.0.so.0': 'libatk1.0-0',
274-
'libxkbcommon.so.0': 'libxkbcommon0',
275-
'libepoxy.so.0': 'libepoxy0',
276-
'libatk-bridge-2.0.so.0': 'libatk-bridge2.0-0',
277-
'libX11-xcb.so.1': 'libx11-xcb1',
278-
'libXcursor.so.1': 'libxcursor1',
279-
'libXext.so.6': 'libxext6',
280-
'libXfixes.so.3': 'libxfixes3',
281-
'libXi.so.6': 'libxi6',
282-
'libXrender.so.1': 'libxrender1',
283-
'libdbus-glib-1.so.2': 'libdbus-glib-1-2',
284-
'libdbus-1.so.3': 'libdbus-1-3',
285285
'libpangocairo-1.0.so.0': 'libpangocairo-1.0-0',
286-
'libcairo-gobject.so.2': 'libcairo-gobject2',
287-
'libxcb-shm.so.0': 'libxcb-shm0',
288286
'libpangoft2-1.0.so.0': 'libpangoft2-1.0-0',
289-
'libXt.so.6': 'libxt6',
290-
'libgthread-2.0.so.0': 'libglib2.0-0',
291-
'libgtk-x11-2.0.so.0': 'libgtk2.0-0',
292-
'libgdk-x11-2.0.so.0': 'libgtk2.0-0',
293-
'libnss3.so': 'libnss3',
294-
'libnssutil3.so': 'libnss3',
287+
'libpng16.so.16': 'libpng16-16',
288+
'libsecret-1.so.0': 'libsecret-1-0',
295289
'libsmime3.so': 'libnss3',
296-
'libnspr4.so': 'libnspr4',
290+
'libsoup-2.4.so.1': 'libsoup2.4-1',
291+
'libvpx.so.6': 'libvpx6',
292+
'libwayland-client.so.0': 'libwayland-client0',
293+
'libwayland-egl.so.1': 'libwayland-egl1',
294+
'libwayland-server.so.0': 'libwayland-server0',
295+
'libwebp.so.6': 'libwebp6',
296+
'libwebpdemux.so.2': 'libwebpdemux2',
297+
'libwoff2dec.so.1.0.2': 'libwoff1',
297298
'libxcb-dri3.so.0': 'libxcb-dri3-0',
298-
'libXtst.so.6': 'libxtst6',
299-
'libcups.so.2': 'libcups2',
300-
'libdrm.so.2': 'libdrm2',
301-
'libXrandr.so.2': 'libxrandr2',
302-
'libgbm.so.1': 'libgbm1',
303-
'libasound.so.2': 'libasound2',
304-
'libatspi.so.0': 'libatspi2.0-0',
299+
'libxcb-shm.so.0': 'libxcb-shm0',
300+
'libxcb.so.1': 'libxcb1',
301+
'libxkbcommon.so.0': 'libxkbcommon0',
302+
'libxml2.so.2': 'libxml2',
303+
'libxslt.so.1': 'libxslt1.1',
305304
};

utils/linux-browser-dependencies/inside_docker/list_dependencies.js

+166-31
Original file line numberDiff line numberDiff line change
@@ -6,47 +6,157 @@ const path = require('path');
66
const {spawn} = require('child_process');
77
const browserPaths = require('playwright/lib/install/browserPaths.js');
88

9+
const readdirAsync = util.promisify(fs.readdir.bind(fs));
10+
const readFileAsync = util.promisify(fs.readFile.bind(fs));
11+
912
(async () => {
13+
console.log('Working on:', await getDistributionName());
14+
console.log('Started at:', currentTime());
1015
const allBrowsersPath = browserPaths.browsersPath();
11-
const {stdout} = await runCommand('find', [allBrowsersPath, '-executable', '-type', 'f']);
12-
// lddPaths - files we want to run LDD against.
13-
const lddPaths = stdout.trim().split('\n').map(f => f.trim()).filter(filePath => !filePath.toLowerCase().endsWith('.sh'));
14-
// List of all shared libraries missing.
15-
const missingDeps = new Set();
16-
// Multimap: reverse-mapping from shared library to requiring file.
17-
const depsToLddPaths = new Map();
18-
await Promise.all(lddPaths.map(async lddPath => {
19-
const deps = await missingFileDependencies(lddPath);
20-
for (const dep of deps) {
21-
missingDeps.add(dep);
22-
let depsToLdd = depsToLddPaths.get(dep);
23-
if (!depsToLdd) {
24-
depsToLdd = new Set();
25-
depsToLddPaths.set(dep, depsToLdd);
26-
}
27-
depsToLdd.add(lddPath);
28-
}
16+
const browserDescriptors = (await readdirAsync(allBrowsersPath)).filter(dir => !dir.startsWith('.')).map(dir => ({
17+
name: dir,
18+
path: path.join(allBrowsersPath, dir),
19+
// All files that we will try to inspect for missing dependencies.
20+
filePaths: [],
21+
// All libraries that are missing for the browser.
22+
missingLibraries: new Set(),
23+
// All packages required for the browser.
24+
requiredPackages: new Set(),
25+
// Libraries that we didn't find a package.
26+
unresolvedLibraries: new Set(),
2927
}));
30-
console.log(`==== MISSING DEPENDENCIES: ${missingDeps.size} ====`);
31-
console.log([...missingDeps].sort().join('\n'));
3228

33-
console.log('{');
34-
for (const dep of missingDeps) {
35-
const packages = await findPackages(dep);
36-
if (packages.length === 0) {
37-
console.log(` // UNRESOLVED: ${dep} `);
38-
const depsToLdd = depsToLddPaths.get(dep);
39-
for (const filePath of depsToLdd)
40-
console.log(` // - required by ${filePath}`);
29+
// Collect all missing libraries for all browsers.
30+
const allMissingLibraries = new Set();
31+
for (const descriptor of browserDescriptors) {
32+
const {stdout} = await runCommand('find', [descriptor.path, '-executable', '-type', 'f']);
33+
descriptor.filePaths = stdout.trim().split('\n').map(f => f.trim()).filter(filePath => !filePath.toLowerCase().endsWith('.sh'));
34+
await Promise.all(descriptor.filePaths.map(async filePath => {
35+
const missingLibraries = await missingFileDependencies(filePath);
36+
for (const library of missingLibraries) {
37+
descriptor.missingLibraries.add(library);
38+
allMissingLibraries.add(library);
39+
}
40+
}));
41+
}
42+
43+
const libraryToPackage = new Map();
44+
const ambiguityLibraries = new Map();
45+
46+
// Map missing libraries to packages that could be installed to fulfill the dependency.
47+
console.log(`Finding packages for ${allMissingLibraries.size} missing libraries...`);
48+
49+
for (let i = 0, array = [...allMissingLibraries].sort(); i < allMissingLibraries.size; ++i) {
50+
const library = array[i];
51+
const packages = await findPackages(library);
52+
53+
const progress = `${i + 1}/${allMissingLibraries.size}`;
54+
console.log(`${progress.padStart(7)}: ${library} => ${JSON.stringify(packages)}`);
55+
56+
if (!packages.length) {
57+
const browsersWithMissingLibrary = browserDescriptors.filter(d => d.missingLibraries.has(library)).map(d => d.name).join(', ');
58+
const PADDING = ''.padStart(7) + ' ';
59+
console.log(PADDING + `ERROR: failed to resolve '${library}' required by ${browsersWithMissingLibrary}`);
4160
} else if (packages.length === 1) {
42-
console.log(` "${dep}": "${packages[0]}",`);
61+
libraryToPackage.set(library, packages[0]);
4362
} else {
44-
console.log(` "${dep}": ${JSON.stringify(packages)},`);
63+
ambiguityLibraries.set(library, packages);
4564
}
4665
}
66+
67+
console.log('');
68+
console.log(`Picking packages for ${ambiguityLibraries.size} libraries that have multiple package candidates`);
69+
// Pick packages to install to fulfill missing libraries.
70+
//
71+
// This is a 2-step process:
72+
// 1. Pick easy libraries by filtering out debug, test and dev packages.
73+
// 2. After that, pick packages that we already picked before.
74+
75+
// Step 1: pick libraries that are easy to pick.
76+
const totalAmbiguityLibraries = ambiguityLibraries.size;
77+
for (const [library, packages] of ambiguityLibraries) {
78+
const package = pickPackage(library, packages);
79+
if (!package)
80+
continue;
81+
libraryToPackage.set(library, package);
82+
ambiguityLibraries.delete(library);
83+
const progress = `${totalAmbiguityLibraries - ambiguityLibraries.size}/${totalAmbiguityLibraries}`;
84+
console.log(`${progress.padStart(7)}: ${library} => ${package}`);
85+
console.log(''.padStart(9) + `(note) packages are ${JSON.stringify(packages)}`);
86+
}
87+
// 2nd pass - prefer packages that we already picked.
88+
const allUsedPackages = new Set(libraryToPackage.values());
89+
for (const [library, packages] of ambiguityLibraries) {
90+
const package = packages.find(package => allUsedPackages.has(package));
91+
if (!package)
92+
continue;
93+
libraryToPackage.set(library, package);
94+
ambiguityLibraries.delete(library);
95+
const progress = `${totalAmbiguityLibraries - ambiguityLibraries.size}/${totalAmbiguityLibraries}`;
96+
console.log(`${progress.padStart(7)}: ${library} => ${package}`);
97+
console.log(''.padStart(9) + `(note) packages are ${JSON.stringify(packages)}`);
98+
}
99+
100+
// Report all ambiguities that were failed to resolve.
101+
for (const [library, packages] of ambiguityLibraries) {
102+
ambiguityLibraries.delete(library);
103+
const progress = `${totalAmbiguityLibraries - ambiguityLibraries.size}/${totalAmbiguityLibraries}`;
104+
console.log(`${progress.padStart(7)}: FAILED ${library} => ???`);
105+
console.log(''.padStart(9) + `(note) packages are ${JSON.stringify(packages)}`);
106+
}
107+
108+
// For each browser build a list of packages to install.
109+
for (const descriptor of browserDescriptors) {
110+
for (const library of descriptor.missingLibraries) {
111+
const package = libraryToPackage.get(library);
112+
if (package)
113+
descriptor.requiredPackages.add(package);
114+
else
115+
descriptor.unresolvedLibraries.add(library);
116+
}
117+
}
118+
119+
// Formatting results.
120+
console.log('');
121+
console.log(`----- Library to package name mapping -----`);
122+
console.log('{');
123+
for (const [library, package] of libraryToPackage)
124+
console.log(` "${library}": "${package}",`);
47125
console.log('}');
126+
127+
// Packages and unresolved libraries for every browser
128+
for (const descriptor of browserDescriptors) {
129+
console.log('');
130+
console.log(`======= ${descriptor.name}: required packages =======`);
131+
const requiredPackages = [...descriptor.requiredPackages].sort();
132+
console.log(JSON.stringify(requiredPackages, null, 2));
133+
console.log('');
134+
console.log(`------- ${descriptor.name}: unresolved libraries -------`);
135+
const unresolvedLibraries = [...descriptor.unresolvedLibraries].sort();
136+
console.log(JSON.stringify(unresolvedLibraries, null, 2));
137+
}
138+
139+
const status = browserDescriptors.some(d => d.unresolvedLibraries.size) ? 'FAILED' : 'SUCCESS';
140+
console.log(`
141+
====================
142+
${status}
143+
====================
144+
`);
48145
})();
49146

147+
function pickPackage(library, packages) {
148+
// Step 1: try to filter out debug, test and dev packages.
149+
packages = packages.filter(package => !package.endsWith('-dbg') && !package.endsWith('-test') && !package.endsWith('-dev') && !package.endsWith('-mesa'));
150+
if (packages.length === 1)
151+
return packages[0];
152+
// Step 2: use library name to filter packages with the same name.
153+
const prefix = library.split(/[-.]/).shift().toLowerCase();
154+
packages = packages.filter(package => package.toLowerCase().startsWith(prefix));
155+
if (packages.length === 1)
156+
return packages[0];
157+
return null;
158+
}
159+
50160
async function findPackages(libraryName) {
51161
const {stdout} = await runCommand('apt-file', ['search', libraryName]);
52162
if (!stdout.trim())
@@ -56,7 +166,9 @@ async function findPackages(libraryName) {
56166
}
57167

58168
async function fileDependencies(filePath) {
59-
const {stdout} = await lddAsync(filePath);
169+
const {stdout, code} = await lddAsync(filePath);
170+
if (code !== 0)
171+
return [];
60172
const deps = stdout.split('\n').map(line => {
61173
line = line.trim();
62174
const missing = line.includes('not found');
@@ -100,3 +212,26 @@ function runCommand(command, args, options = {}) {
100212
});
101213
});
102214
}
215+
216+
async function getDistributionName() {
217+
const osReleaseText = await readFileAsync('/etc/os-release', 'utf8');
218+
const fields = new Map();
219+
for (const line of osReleaseText.split('\n')) {
220+
const tokens = line.split('=');
221+
const name = tokens.shift();
222+
let value = tokens.join('=').trim();
223+
if (value.startsWith('"') && value.endsWith('"'))
224+
value = value.substring(1, value.length - 1);
225+
if (!name)
226+
continue;
227+
fields.set(name.toLowerCase(), value);
228+
}
229+
return fields.get('pretty_name') || '';
230+
}
231+
232+
function currentTime() {
233+
const date = new Date();
234+
const dateTimeFormat = new Intl.DateTimeFormat('en', { year: 'numeric', month: 'short', day: '2-digit' });
235+
const [{ value: month },,{ value: day },,{ value: year }] = dateTimeFormat .formatToParts(date );
236+
return `${month} ${day}, ${year}`;
237+
}

0 commit comments

Comments
 (0)