2020-04-29 17:19:21 -07:00
|
|
|
/**
|
|
|
|
* Copyright Microsoft Corporation. All rights reserved.
|
|
|
|
*
|
|
|
|
* 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 crypto from 'crypto';
|
|
|
|
import { getFromENV, logPolitely } from '../helper';
|
|
|
|
import * as fs from 'fs';
|
|
|
|
import * as path from 'path';
|
|
|
|
import * as util from 'util';
|
|
|
|
import * as removeFolder from 'rimraf';
|
2020-07-01 15:22:29 -07:00
|
|
|
import * as browserPaths from './browserPaths';
|
|
|
|
import * as browserFetcher from './browserFetcher';
|
2020-04-29 17:19:21 -07:00
|
|
|
|
|
|
|
const fsMkdirAsync = util.promisify(fs.mkdir.bind(fs));
|
|
|
|
const fsReaddirAsync = util.promisify(fs.readdir.bind(fs));
|
|
|
|
const fsReadFileAsync = util.promisify(fs.readFile.bind(fs));
|
2020-07-17 16:39:27 -07:00
|
|
|
const fsExistsAsync = (filePath: string) => fsReadFileAsync(filePath).then(() => true).catch(e => false);
|
2020-04-29 17:19:21 -07:00
|
|
|
const fsUnlinkAsync = util.promisify(fs.unlink.bind(fs));
|
|
|
|
const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs));
|
2020-06-10 18:49:03 -07:00
|
|
|
const removeFolderAsync = util.promisify(removeFolder);
|
2020-04-29 17:19:21 -07:00
|
|
|
|
|
|
|
export async function installBrowsersWithProgressBar(packagePath: string) {
|
|
|
|
const browsersPath = browserPaths.browsersPath(packagePath);
|
|
|
|
const linksDir = path.join(browsersPath, '.links');
|
|
|
|
|
|
|
|
if (getFromENV('PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD')) {
|
|
|
|
logPolitely('Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set');
|
|
|
|
return false;
|
|
|
|
}
|
2020-04-29 18:59:20 -07:00
|
|
|
await fsMkdirAsync(linksDir, { recursive: true });
|
2020-04-29 17:19:21 -07:00
|
|
|
await fsWriteFileAsync(path.join(linksDir, sha1(packagePath)), packagePath);
|
2020-05-04 09:34:59 -07:00
|
|
|
await validateCache(packagePath, browsersPath, linksDir);
|
2020-04-29 17:19:21 -07:00
|
|
|
}
|
|
|
|
|
2020-05-04 09:34:59 -07:00
|
|
|
async function validateCache(packagePath: string, browsersPath: string, linksDir: string) {
|
2020-07-17 16:39:27 -07:00
|
|
|
// 1. Collect used downloads and package descriptors.
|
|
|
|
const usedBrowserPaths: Set<string> = new Set();
|
2020-04-29 17:19:21 -07:00
|
|
|
for (const fileName of await fsReaddirAsync(linksDir)) {
|
|
|
|
const linkPath = path.join(linksDir, fileName);
|
2020-05-28 22:36:08 -07:00
|
|
|
let linkTarget = '';
|
2020-04-29 17:19:21 -07:00
|
|
|
try {
|
2020-05-28 22:36:08 -07:00
|
|
|
linkTarget = (await fsReadFileAsync(linkPath)).toString();
|
2020-07-24 16:36:00 -07:00
|
|
|
const browsersToDownload = await readBrowsersToDownload(linkTarget);
|
|
|
|
for (const browser of browsersToDownload) {
|
2020-07-17 16:39:27 -07:00
|
|
|
const usedBrowserPath = browserPaths.browserDirectory(browsersPath, browser);
|
|
|
|
const browserRevision = parseInt(browser.revision, 10);
|
|
|
|
// Old browser installations don't have marker file.
|
|
|
|
const shouldHaveMarkerFile = (browser.name === 'chromium' && browserRevision >= 786218) ||
|
|
|
|
(browser.name === 'firefox' && browserRevision >= 1128) ||
|
|
|
|
(browser.name === 'webkit' && browserRevision >= 1307);
|
|
|
|
if (!shouldHaveMarkerFile || (await fsExistsAsync(browserPaths.markerFilePath(browsersPath, browser))))
|
|
|
|
usedBrowserPaths.add(usedBrowserPath);
|
|
|
|
}
|
2020-04-29 17:19:21 -07:00
|
|
|
} catch (e) {
|
2020-05-28 22:36:08 -07:00
|
|
|
if (linkTarget)
|
|
|
|
logPolitely('Failed to process descriptor at ' + linkTarget);
|
2020-04-29 17:19:21 -07:00
|
|
|
await fsUnlinkAsync(linkPath).catch(e => {});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-10 18:49:03 -07:00
|
|
|
// 2. Delete all unused browsers.
|
2020-04-29 17:19:21 -07:00
|
|
|
let downloadedBrowsers = (await fsReaddirAsync(browsersPath)).map(file => path.join(browsersPath, file));
|
|
|
|
downloadedBrowsers = downloadedBrowsers.filter(file => browserPaths.isBrowserDirectory(file));
|
|
|
|
const directories = new Set<string>(downloadedBrowsers);
|
2020-07-17 16:39:27 -07:00
|
|
|
for (const browserPath of usedBrowserPaths)
|
|
|
|
directories.delete(browserPath);
|
2020-04-29 17:19:21 -07:00
|
|
|
for (const directory of directories) {
|
|
|
|
logPolitely('Removing unused browser at ' + directory);
|
2020-06-10 18:49:03 -07:00
|
|
|
await removeFolderAsync(directory).catch(e => {});
|
2020-04-29 17:19:21 -07:00
|
|
|
}
|
|
|
|
|
2020-06-10 18:49:03 -07:00
|
|
|
// 3. Install missing browsers for this package.
|
2020-07-24 16:36:00 -07:00
|
|
|
const myBrowsersToDownload = await readBrowsersToDownload(packagePath);
|
|
|
|
for (const browser of myBrowsersToDownload) {
|
|
|
|
await browserFetcher.downloadBrowserWithProgressBar(browsersPath, browser);
|
2020-07-17 16:39:27 -07:00
|
|
|
await fsWriteFileAsync(browserPaths.markerFilePath(browsersPath, browser), '');
|
2020-04-29 17:19:21 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-24 16:36:00 -07:00
|
|
|
async function readBrowsersToDownload(packagePath: string) {
|
|
|
|
const browsers = JSON.parse((await fsReadFileAsync(path.join(packagePath, 'browsers.json'))).toString())['browsers'] as browserPaths.BrowserDescriptor[];
|
|
|
|
// Older versions do not have "download" field. We assume they need all browsers
|
|
|
|
// from the list. So we want to skip all browsers that are explicitly marked as "download: false".
|
|
|
|
return browsers.filter(browser => browser.download !== false);
|
|
|
|
}
|
|
|
|
|
2020-04-29 17:19:21 -07:00
|
|
|
function sha1(data: string): string {
|
|
|
|
const sum = crypto.createHash('sha1');
|
|
|
|
sum.update(data);
|
|
|
|
return sum.digest('hex');
|
|
|
|
}
|