You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

256 lines
8.4 KiB

/**
* Copyright 2018 Google Inc. 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.
*/
'use strict';
const fs = require('fs');
const path = require('path');
const execSync = require('child_process').execSync;
const execFileSync = require('child_process').execFileSync;
const puppeteer = require('puppeteer-core');
const newLineRegex = /\r?\n/;
function darwin(canary) {
const LSREGISTER = '/System/Library/Frameworks/CoreServices.framework' +
'/Versions/A/Frameworks/LaunchServices.framework' +
'/Versions/A/Support/lsregister';
const grepexpr = canary ? 'google chrome canary' : 'google chrome';
const result =
execSync(`${LSREGISTER} -dump | grep -i \'${grepexpr}\\?.app$\' | awk \'{$1=""; print $0}\'`);
const installations = new Set();
const paths = result.toString().split(newLineRegex).filter(a => a).map(a => a.trim());
paths.unshift(canary ? '/Applications/Google Chrome Canary.app' : '/Applications/Google Chrome.app');
for (const p of paths) {
if (p.startsWith('/Volumes'))
continue;
const inst = path.join(p, canary ? '/Contents/MacOS/Google Chrome Canary' : '/Contents/MacOS/Google Chrome');
if (canAccess(inst))
return inst;
}
}
/**
* Look for linux executables in 3 ways
* 1. Look into CHROME_PATH env variable
* 2. Look into the directories where .desktop are saved on gnome based distro's
* 3. Look for google-chrome-stable & google-chrome executables by using the which command
*/
function linux(canary) {
let installations = [];
// Look into the directories where .desktop are saved on gnome based distro's
const desktopInstallationFolders = [
path.join(require('os').homedir(), '.local/share/applications/'),
'/usr/share/applications/',
];
desktopInstallationFolders.forEach(folder => {
installations = installations.concat(findChromeExecutables(folder));
});
// Look for google-chrome(-stable) & chromium(-browser) executables by using the which command
const executables = [
'google-chrome-stable',
'google-chrome',
'chromium-browser',
'chromium',
];
executables.forEach(executable => {
try {
const chromePath =
execFileSync('which', [executable], {stdio: 'pipe'}).toString().split(newLineRegex)[0];
if (canAccess(chromePath))
installations.push(chromePath);
} catch (e) {
// Not installed.
}
});
if (!installations.length)
throw new Error('The environment variable CHROME_PATH must be set to executable of a build of Chromium version 54.0 or later.');
const priorities = [
{regex: /chrome-wrapper$/, weight: 51},
{regex: /google-chrome-stable$/, weight: 50},
{regex: /google-chrome$/, weight: 49},
{regex: /chromium-browser$/, weight: 48},
{regex: /chromium$/, weight: 47},
];
if (process.env.CHROME_PATH)
priorities.unshift({regex: new RegExp(`${process.env.CHROME_PATH}`), weight: 101});
return sort(uniq(installations.filter(Boolean)), priorities)[0];
}
function win32(canary) {
const suffix = canary ?
`${path.sep}Google${path.sep}Chrome SxS${path.sep}Application${path.sep}chrome.exe` :
`${path.sep}Google${path.sep}Chrome${path.sep}Application${path.sep}chrome.exe`;
const prefixes = [
process.env.LOCALAPPDATA, process.env.PROGRAMFILES, process.env['PROGRAMFILES(X86)']
].filter(Boolean);
let result;
prefixes.forEach(prefix => {
const chromePath = path.join(prefix, suffix);
if (canAccess(chromePath))
result = chromePath;
});
return result;
}
function sort(installations, priorities) {
const defaultPriority = 10;
return installations
// assign priorities
.map(inst => {
for (const pair of priorities) {
if (pair.regex.test(inst))
return {path: inst, weight: pair.weight};
}
return {path: inst, weight: defaultPriority};
})
// sort based on priorities
.sort((a, b) => (b.weight - a.weight))
// remove priority flag
.map(pair => pair.path);
}
function canAccess(file) {
if (!file)
return false;
try {
fs.accessSync(file);
return true;
} catch (e) {
return false;
}
}
function uniq(arr) {
return Array.from(new Set(arr));
}
function findChromeExecutables(folder) {
const argumentsRegex = /(^[^ ]+).*/; // Take everything up to the first space
const chromeExecRegex = '^Exec=\/.*\/(google-chrome|chrome|chromium)-.*';
const installations = [];
if (canAccess(folder)) {
// Output of the grep & print looks like:
// /opt/google/chrome/google-chrome --profile-directory
// /home/user/Downloads/chrome-linux/chrome-wrapper %U
let execPaths;
// Some systems do not support grep -R so fallback to -r.
// See https://github.com/GoogleChrome/chrome-launcher/issues/46 for more context.
try {
execPaths = execSync(`grep -ER "${chromeExecRegex}" ${folder} | awk -F '=' '{print $2}'`);
} catch (e) {
execPaths = execSync(`grep -Er "${chromeExecRegex}" ${folder} | awk -F '=' '{print $2}'`);
}
execPaths = execPaths.toString()
.split(newLineRegex)
.map(execPath => execPath.replace(argumentsRegex, '$1'));
execPaths.forEach(execPath => canAccess(execPath) && installations.push(execPath));
}
return installations;
}
/**
* @return {!Promise<?string>}
*/
async function downloadChromium(options, targetRevision) {
const browserFetcher = puppeteer.createBrowserFetcher({ path: options.localDataDir });
const revision = targetRevision || require('puppeteer-core/package.json').puppeteer.chromium_revision;
const revisionInfo = browserFetcher.revisionInfo(revision);
// Do nothing if the revision is already downloaded.
if (revisionInfo.local)
return revisionInfo;
// Override current environment proxy settings with npm configuration, if any.
try {
console.log(`Downloading Chromium r${revision}...`);
const newRevisionInfo = await browserFetcher.download(revisionInfo.revision);
console.log('Chromium downloaded to ' + newRevisionInfo.folderPath);
let localRevisions = await browserFetcher.localRevisions();
localRevisions = localRevisions.filter(revision => revision !== revisionInfo.revision);
// Remove previous chromium revisions.
const cleanupOldVersions = localRevisions.map(revision => browserFetcher.remove(revision));
await Promise.all(cleanupOldVersions);
return newRevisionInfo;
} catch (error) {
console.error(`ERROR: Failed to download Chromium r${revision}!`);
console.error(error);
return null;
}
}
async function findChrome(options) {
if (options.executablePath)
return { executablePath: options.executablePath, type: 'user' };
const config = new Set(options.channel || ['stable']);
let executablePath;
// Always prefer canary.
if (config.has('canary') || config.has('*')) {
if (process.platform === 'linux')
executablePath = linux(true);
else if (process.platform === 'win32')
executablePath = win32(true);
else if (process.platform === 'darwin')
executablePath = darwin(true);
if (executablePath)
return { executablePath, type: 'canary' };
}
// Then pick stable.
if (config.has('stable') || config.has('*')) {
if (process.platform === 'linux')
executablePath = linux();
else if (process.platform === 'win32')
executablePath = win32();
else if (process.platform === 'darwin')
executablePath = darwin();
if (executablePath)
return { executablePath, type: 'stable' };
}
// always prefer puppeteer revision of chromium
if (config.has('chromium') || config.has('*')) {
const revisionInfo = await downloadChromium(options);
return { executablePath: revisionInfo.executablePath, type: revisionInfo.revision };
}
for (const item of config) {
if (!item.startsWith('r'))
continue;
const revisionInfo = await downloadChromium(options, item.substring(1));
return { executablePath: revisionInfo.executablePath, type: revisionInfo.revision };
}
return {};
}
module.exports = findChrome;