text: // Copyright (c) 2020, Compiler Explorer Authors
  // All rights reserved.
  //
  // Redistribution and use in source and binary forms, with or without
  // modification, are permitted provided that the following conditions are met:
  //
  //     * Redistributions of source code must retain the above copyright notice,
  //       this list of conditions and the following disclaimer.
  //     * Redistributions in binary form must reproduce the above copyright
  //       notice, this list of conditions and the following disclaimer in the
  //       documentation and/or other materials provided with the distribution.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
  // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  // POSSIBILITY OF SUCH DAMAGE.
  
  import path from 'path';
  import zlib from 'zlib';
  
  import fs from 'fs-extra';
  import request from 'request';
  import tar from 'tar-stream';
  import _ from 'underscore';
  
  import { logger } from '../logger';
  
  import { BuildEnvSetupBase } from './base';
  
  export class BuildEnvSetupCeConanDirect extends BuildEnvSetupBase {
      static get key() { return 'ceconan'; }
  
      constructor(compilerInfo, env, execCompilerCachedFunc) {
          super(compilerInfo, env, execCompilerCachedFunc);
  
          this.host = compilerInfo.buildenvsetup.props('host', false);
          this.onlyonstaticliblink = compilerInfo.buildenvsetup.props('onlyonstaticliblink', false);
  
          if (env.debug) request.debug = true;
      }
  
      async getAllPossibleBuilds(libid, version) {
          return new Promise((resolve, reject) => {
              const encLibid = encodeURIComponent(libid);
              const encVersion = encodeURIComponent(version);
              const url = `${this.host}/v1/conans/${encLibid}/${encVersion}/${encLibid}/${encVersion}/search`;
              const settings = {
                  method: 'GET',
                  json: true,
              };
  
              request(url, settings, (err, res, body) => {
                  if (res.statusCode === 404) {
                      reject(`Not found (${url})`);
                  } else if (err) {
                      reject(err);
                  } else {
                      resolve(body);
                  }
              });
          });
      }
  
      async getPackageUrl(libid, version, hash) {
          return new Promise((resolve, reject) => {
              const encLibid = encodeURIComponent(libid);
              const encVersion = encodeURIComponent(version);
              const libUrl = `${this.host}/v1/conans/${encLibid}/${encVersion}/${encLibid}/${encVersion}`;
              const url = `${libUrl}/packages/${hash}/download_urls`;
  
              const settings = {
                  method: 'GET',
                  json: true,
              };
  
              request(url, settings, (err, res, body) => {
                  if (err) {
                      reject(err);
                      return;
                  }
  
                  resolve(body['conan_package.tgz']);
              });
          });
      }
  
      async downloadAndExtractPackage(libId, version, downloadPath, packageUrl) {
          return new Promise((resolve) => {
              const startTime = process.hrtime.bigint();
              const extract = tar.extract();
              const gunzip = zlib.createGunzip();
  
              extract.on('entry', (header, stream, next) => {
                  const filename = path.basename(header.name);
                  const filestream = fs.createWriteStream(path.join(downloadPath, filename));
                  stream.pipe(filestream);
                  stream.on('end', next);
                  stream.resume();
              });
  
              extract.on('finish', () => {
                  const endTime = process.hrtime.bigint();
                  resolve({
                      step: `Download of ${libId} ${version}`,
                      packageUrl: packageUrl,
                      time: ((endTime - startTime) / BigInt(1000000)).toString(),
                  });
              });
  
              gunzip.pipe(extract);
  
              const settings = {
                  method: 'GET',
                  encoding: null,
              };
  
              request(packageUrl, settings).pipe(gunzip);
          });
      }
  
      async getConanBuildProperties(key) {
          const arch = this.getTarget(key);
          const libcxx = this.getLibcxx(key);
          const stdver = '';
          const flagcollection = '';
  
          return {
              os: 'Linux',
              build_type: 'Debug',
              compiler: this.compilerTypeOrGCC,
              'compiler.version': this.compiler.id,
              'compiler.libcxx': libcxx,
              arch: arch,
              stdver: stdver,
              flagcollection: flagcollection,
          };
      }
  
      async findMatchingHash(buildProperties, possibleBuilds) {
          return _.findKey(possibleBuilds, (elem) => {
              return _.all(buildProperties, (val, key) => {
                  return val === elem.settings[key];
              });
          });
      }
  
      async download(key, dirPath, libraryDetails) {
          const allDownloads = [];
          const allLibraryBuilds = [];
  
          _.each(libraryDetails, (details, libId) => {
              if (this.hasBinariesToLink(details)) {
                  const lookupversion = details.lookupversion ? details.lookupversion : details.version;
                  allLibraryBuilds.push({
                      id: libId,
                      version: details.version,
                      lookupversion: details.lookupversion,
                      possibleBuilds: this.getAllPossibleBuilds(libId, lookupversion).catch(() => false),
                  });
              }
          });
  
          const buildProperties = await this.getConanBuildProperties(key);
  
          for (const libVerBuilds of allLibraryBuilds) {
              const lookupversion = libVerBuilds.lookupversion ? libVerBuilds.lookupversion : libVerBuilds.version;
              const libVer = `${libVerBuilds.id}/${lookupversion}`;
              const possibleBuilds = await libVerBuilds.possibleBuilds;
              if (possibleBuilds) {
                  const hash = await this.findMatchingHash(buildProperties, possibleBuilds);
                  if (hash) {
                      logger.debug(`Found conan hash ${hash} for ${libVer}`);
                      allDownloads.push(
                          this.getPackageUrl(libVerBuilds.id, lookupversion, hash).then((downloadUrl) => {
                              return this.downloadAndExtractPackage(libVerBuilds.id, lookupversion, dirPath, downloadUrl);
                          }),
                      );
                  } else {
                      logger.info(`No build found for ${libVer} matching ${JSON.stringify(buildProperties)}`);
                  }
              } else {
                  logger.info(`Library ${libVer} not available`);
              }
          }
  
          return Promise.all(allDownloads);
      }
  
      async setup(key, dirPath, libraryDetails) {
          if (this.host && (!this.onlyonstaticliblink || this.hasAtLeastOneBinaryToLink(libraryDetails))) {
              return this.download(key, dirPath, libraryDetails);
          } else {
              return Promise.resolve();
          }
      }
  
      hasBinariesToLink(details) {
          return (details.libpath.length === 0) && ((details.staticliblink.length > 0) || (details.liblink.length > 0));
      }
  
      hasAtLeastOneBinaryToLink(libraryDetails) {
          return _.some(libraryDetails, (details) => this.hasBinariesToLink(details));
      }
  }
  