"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.installDependenciesLinux = installDependenciesLinux;
exports.installDependenciesWindows = installDependenciesWindows;
exports.transformCommandsForRoot = transformCommandsForRoot;
exports.validateDependenciesLinux = validateDependenciesLinux;
exports.validateDependenciesWindows = validateDependenciesWindows;
exports.writeDockerVersion = writeDockerVersion;

var _fs = _interopRequireDefault(require("fs"));

var _path = _interopRequireDefault(require("path"));

var os = _interopRequireWildcard(require("os"));

var _child_process = _interopRequireDefault(require("child_process"));

var utils = _interopRequireWildcard(require("../../utils"));

var _spawnAsync = require("../../utils/spawnAsync");

var _hostPlatform = require("../../utils/hostPlatform");

var _ = require(".");

var _nativeDeps = require("./nativeDeps");

var _ubuntuVersion = require("../../utils/ubuntuVersion");

var _userAgent = require("../../common/userAgent");

function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }

function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/**
 * Copyright (c) Microsoft Corporation.
 *
 * 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.
 */
const BIN_DIRECTORY = _path.default.join(__dirname, '..', '..', '..', 'bin');

const packageJSON = require('../../../package.json');

const dockerVersionFilePath = '/ms-playwright/.docker-info';

async function writeDockerVersion(dockerImageNameTemplate) {
  await _fs.default.promises.mkdir(_path.default.dirname(dockerVersionFilePath), {
    recursive: true
  });
  await _fs.default.promises.writeFile(dockerVersionFilePath, JSON.stringify({
    driverVersion: packageJSON.version,
    dockerImageName: dockerImageNameTemplate.replace('%version%', packageJSON.version)
  }, null, 2), 'utf8'); // Make sure version file is globally accessible.

  await _fs.default.promises.chmod(dockerVersionFilePath, 0o777);
}

async function readDockerVersion() {
  return await _fs.default.promises.readFile(dockerVersionFilePath, 'utf8').then(text => JSON.parse(text)).catch(e => null);
}

const checkExecutable = filePath => _fs.default.promises.access(filePath, _fs.default.constants.X_OK).then(() => true).catch(e => false);

function isSupportedWindowsVersion() {
  if (os.platform() !== 'win32' || os.arch() !== 'x64') return false;
  const [major, minor] = os.release().split('.').map(token => parseInt(token, 10)); // This is based on: https://stackoverflow.com/questions/42524606/how-to-get-windows-version-using-node-js/44916050#44916050
  // The table with versions is taken from: https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoexw#remarks
  // Windows 7 is not supported and is encoded as `6.1`.

  return major > 6 || major === 6 && minor > 1;
}

async function installDependenciesWindows(targets, dryRun) {
  if (targets.has('chromium')) {
    const command = 'powershell.exe';
    const args = ['-ExecutionPolicy', 'Bypass', '-File', _path.default.join(BIN_DIRECTORY, 'install_media_pack.ps1')];

    if (dryRun) {
      console.log(`${command} ${quoteProcessArgs(args).join(' ')}`); // eslint-disable-line no-console

      return;
    }

    const {
      code
    } = await (0, _spawnAsync.spawnAsync)(command, args, {
      cwd: BIN_DIRECTORY,
      stdio: 'inherit'
    });
    if (code !== 0) throw new Error('Failed to install windows dependencies!');
  }
}

async function installDependenciesLinux(targets, dryRun) {
  if ((await (0, _ubuntuVersion.getUbuntuVersion)()) === '') throw new Error(`Unsupported Linux distribution, only Ubuntu is supported!`);
  const libraries = [];

  for (const target of targets) {
    const info = _nativeDeps.deps[_hostPlatform.hostPlatform];

    if (!info) {
      console.warn('Cannot install dependencies for this linux distribution!'); // eslint-disable-line no-console

      return;
    }

    libraries.push(...info[target]);
  }

  const uniqueLibraries = Array.from(new Set(libraries));
  if (!dryRun) console.log('Installing Ubuntu dependencies...'); // eslint-disable-line no-console

  const commands = [];
  commands.push('apt-get update');
  commands.push(['apt-get', 'install', '-y', '--no-install-recommends', ...uniqueLibraries].join(' '));
  const {
    command,
    args,
    elevatedPermissions
  } = await transformCommandsForRoot(commands);

  if (dryRun) {
    console.log(`${command} ${quoteProcessArgs(args).join(' ')}`); // eslint-disable-line no-console

    return;
  }

  if (elevatedPermissions) console.log('Switching to root user to install dependencies...'); // eslint-disable-line no-console

  const child = _child_process.default.spawn(command, args, {
    stdio: 'inherit'
  });

  await new Promise((resolve, reject) => {
    child.on('exit', code => code === 0 ? resolve() : reject(new Error(`Installation process exited with code: ${code}`)));
    child.on('error', reject);
  });
}

async function validateDependenciesWindows(windowsExeAndDllDirectories) {
  const directoryPaths = windowsExeAndDllDirectories;
  const lddPaths = [];

  for (const directoryPath of directoryPaths) lddPaths.push(...(await executablesOrSharedLibraries(directoryPath)));

  const allMissingDeps = await Promise.all(lddPaths.map(lddPath => missingFileDependenciesWindows(lddPath)));
  const missingDeps = 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') || dep === 'vcruntime140.dll' || dep === 'vcruntime140_1.dll' || dep === 'msvcp140.dll') isCrtMissing = true;else if (dep === 'mf.dll' || dep === 'mfplat.dll' || dep === 'msmpeg2vdec.dll' || dep === 'evr.dll' || dep === 'avrt.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`, ``, `For Windows N editions visit:`, `https://support.microsoft.com/en-us/help/3145500/media-feature-pack-list-for-windows-n-editions`, ``);
  }

  details.push(`Full list of missing libraries:`, `    ${[...missingDeps].join('\n    ')}`, ``);
  const message = `Host system is missing dependencies!\n\n${details.join('\n')}`;

  if (isSupportedWindowsVersion()) {
    throw new Error(message);
  } else {
    // eslint-disable-next-line no-console
    console.warn(`WARNING: running on unsupported windows version!`); // eslint-disable-next-line no-console

    console.warn(message);
  }
}

async function validateDependenciesLinux(sdkLanguage, linuxLddDirectories, dlOpenLibraries) {
  var _deps$hostPlatform;

  const directoryPaths = linuxLddDirectories;
  const lddPaths = [];

  for (const directoryPath of directoryPaths) lddPaths.push(...(await executablesOrSharedLibraries(directoryPath)));

  const missingDepsPerFile = await Promise.all(lddPaths.map(lddPath => missingFileDependencies(lddPath, directoryPaths)));
  const missingDeps = new Set();

  for (const deps of missingDepsPerFile) {
    for (const dep of deps) missingDeps.add(dep);
  }

  for (const dep of await missingDLOPENLibraries(dlOpenLibraries)) missingDeps.add(dep);

  if (!missingDeps.size) return;
  const allMissingDeps = new Set(missingDeps); // Check Ubuntu version.

  const missingPackages = new Set();
  const libraryToPackageNameMapping = { ...(((_deps$hostPlatform = _nativeDeps.deps[_hostPlatform.hostPlatform]) === null || _deps$hostPlatform === void 0 ? void 0 : _deps$hostPlatform.lib2package) || {}),
    ...MANUAL_LIBRARY_TO_PACKAGE_NAME_UBUNTU
  }; // Translate missing dependencies to package names to install with apt.

  for (const missingDep of missingDeps) {
    const packageName = libraryToPackageNameMapping[missingDep];

    if (packageName) {
      missingPackages.add(packageName);
      missingDeps.delete(missingDep);
    }
  }

  const maybeSudo = process.getuid() !== 0 && os.platform() !== 'win32' ? 'sudo ' : '';
  const dockerInfo = await readDockerVersion();
  const errorLines = [`Host system is missing dependencies to run browsers.`]; // Ignore patch versions when comparing docker container version and Playwright version:
  // we **NEVER** roll browsers in patch releases, so native dependencies do not change.

  if (dockerInfo && !dockerInfo.driverVersion.startsWith((0, _userAgent.getPlaywrightVersion)(true
  /* majorMinorOnly */
  ) + '.')) {
    // We are running in a docker container with unmatching version.
    // In this case, we know how to install dependencies in it.
    const pwVersion = (0, _userAgent.getPlaywrightVersion)();
    const requiredDockerImage = dockerInfo.dockerImageName.replace(dockerInfo.driverVersion, pwVersion);
    errorLines.push(...[`This is most likely due to docker image version not matching Playwright version:`, `- Playwright: ${pwVersion}`, `-     Docker: ${dockerInfo.driverVersion}`, ``, `Either:`, `- (recommended) use docker image "${requiredDockerImage}"`, `- (alternative 1) run the following command inside docker to install missing dependencies:`, ``, `    ${maybeSudo}${(0, _.buildPlaywrightCLICommand)(sdkLanguage, 'install-deps')}`, ``, `- (alternative 2) use Aptitude inside docker:`, ``, `    ${maybeSudo}apt-get install ${[...missingPackages].join('\\\n        ')}`, ``, `<3 Playwright Team`]);
  } else if (missingPackages.size && !missingDeps.size) {
    // Only known dependencies are missing for browsers.
    // Suggest installation with a Playwright CLI.
    errorLines.push(...[`Please install them with the following command:`, ``, `    ${maybeSudo}${(0, _.buildPlaywrightCLICommand)(sdkLanguage, 'install-deps')}`, ``, `Alternatively, use Aptitude:`, `    ${maybeSudo}apt-get install ${[...missingPackages].join('\\\n        ')}`, ``, `<3 Playwright Team`]);
  } else {
    // Unhappy path: we either run on unknown distribution, or we failed to resolve all missing
    // libraries to package names.
    // Print missing libraries only:
    errorLines.push(...[`Missing libraries:`, ...[...allMissingDeps].map(dep => '    ' + dep)]);
  }

  throw new Error('\n' + utils.wrapInASCIIBox(errorLines.join('\n'), 1));
}

function isSharedLib(basename) {
  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) {
  const allPaths = (await _fs.default.promises.readdir(directoryPath)).map(file => _path.default.resolve(directoryPath, file));
  const allStats = await Promise.all(allPaths.map(aPath => _fs.default.promises.stat(aPath)));
  const filePaths = allPaths.filter((aPath, index) => allStats[index].isFile());
  const executablersOrLibraries = (await Promise.all(filePaths.map(async filePath => {
    const basename = _path.default.basename(filePath).toLowerCase();

    if (isSharedLib(basename)) return filePath;
    if (await checkExecutable(filePath)) return filePath;
    return false;
  }))).filter(Boolean);
  return executablersOrLibraries;
}

async function missingFileDependenciesWindows(filePath) {
  const executable = _path.default.join(__dirname, '..', '..', '..', 'bin', 'PrintDeps.exe');

  const dirname = _path.default.dirname(filePath);

  const {
    stdout,
    code
  } = await (0, _spawnAsync.spawnAsync)(executable, [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().toLowerCase());
  return missingDeps;
}

async function missingFileDependencies(filePath, extraLDPaths) {
  const dirname = _path.default.dirname(filePath);

  let LD_LIBRARY_PATH = extraLDPaths.join(':');
  if (process.env.LD_LIBRARY_PATH) LD_LIBRARY_PATH = `${process.env.LD_LIBRARY_PATH}:${LD_LIBRARY_PATH}`;
  const {
    stdout,
    code
  } = await (0, _spawnAsync.spawnAsync)('ldd', [filePath], {
    cwd: dirname,
    env: { ...process.env,
      LD_LIBRARY_PATH
    }
  });
  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 missingDLOPENLibraries(libraries) {
  if (!libraries.length) return []; // NOTE: Using full-qualified path to `ldconfig` since `/sbin` is not part of the
  // default PATH in CRON.
  // @see https://github.com/microsoft/playwright/issues/3397

  const {
    stdout,
    code,
    error
  } = await (0, _spawnAsync.spawnAsync)('/sbin/ldconfig', ['-p'], {});
  if (code !== 0 || error) return [];

  const isLibraryAvailable = library => stdout.toLowerCase().includes(library.toLowerCase());

  return libraries.filter(library => !isLibraryAvailable(library));
}

const MANUAL_LIBRARY_TO_PACKAGE_NAME_UBUNTU = {
  // libgstlibav.so (the only actual library provided by gstreamer1.0-libav) is not
  // in the ldconfig cache, so we detect the actual library required for playing h.264
  // and if it's missing recommend installing missing gstreamer lib.
  // gstreamer1.0-libav -> libavcodec57 -> libx264-152
  'libx264.so': 'gstreamer1.0-libav'
};

function quoteProcessArgs(args) {
  return args.map(arg => {
    if (arg.includes(' ')) return `"${arg}"`;
    return arg;
  });
}

async function transformCommandsForRoot(commands) {
  const isRoot = process.getuid() === 0;
  if (isRoot) return {
    command: 'sh',
    args: ['-c', `${commands.join('&& ')}`],
    elevatedPermissions: false
  };
  const sudoExists = await (0, _spawnAsync.spawnAsync)('which', ['sudo']);
  if (sudoExists.code === 0) return {
    command: 'sudo',
    args: ['--', 'sh', '-c', `${commands.join('&& ')}`],
    elevatedPermissions: true
  };
  return {
    command: 'su',
    args: ['root', '-c', `${commands.join('&& ')}`],
    elevatedPermissions: true
  };
}