hexo/node_modules/buildcheck/lib/index.js

739 lines
18 KiB
JavaScript

'use strict';
// TODO: take `compilerParams`, headers into account in cache for all cached
// results
// TODO: debug output
const { spawnSync } = require('child_process');
const { unlinkSync, writeFileSync } = require('fs');
const { tmpdir } = require('os');
const { win32: path } = require('path');
const { inspect } = require('util');
const isWindows = (process.platform === 'win32');
const findVS = require('./findvs.js');
const RE_HEADER_DECORATED = /^(?:"(.+)")|(?:<(.+)>)$/;
const genWinTmpFilenames = (() => {
let instance = 1;
return () => {
const base =
path.resolve(tmpdir(), `_buildcheck-${process.pid}-${instance++}`);
return {
input: `${base}.in.tmp`,
object: `${base}.out.obj`,
output: `${base}.out.tmp`,
};
};
})();
function getKind(prop) {
const spawnOpts = {
encoding: 'utf8',
stdio: 'pipe',
windowsHide: true,
};
const lang = (prop === '_cc' ? 'c' : 'c++');
if (isWindows) {
spawnOpts.stdio = [ 'ignore', 'pipe', 'pipe' ];
writeFileSync(this._tmpInFile, [
'_MSC_VER',
].join(' '));
const result = spawnSync(
this[prop],
[ '-EP', `-T${lang === 'c' ? 'c' : 'p'}`, this._tmpInFile],
spawnOpts
);
unlinkSync(this._tmpInFile);
if (result.status === 0) {
const values = result.stdout.trim().split(' ');
if (values.length === 1 && /^\d+$/.test(values[0])) {
this[`${prop}Kind`] = 'msvc';
this[`${prop}Version`] = values[0];
this[`${prop}SpawnOpts`] = spawnOpts;
this._debug(
`>>> Detected MSVC ${values[0]} for ${lang.toUpperCase()} language`
);
return;
}
}
} else {
const result = spawnSync(
this[prop],
[ '-E', '-P', '-x', lang, '-' ],
{
...spawnOpts,
input: [
'__clang__',
'__GNUC__',
'__GNUC_MINOR__',
'__GNUC_PATCHLEVEL__',
'__clang_major__',
'__clang_minor__',
'__clang_patchlevel__',
].join(' '),
}
);
if (result.status === 0) {
const values = result.stdout.trim().split(' ');
if (values.length === 7) {
let kind;
let version;
if (values[0] === '1') {
kind = 'clang';
version = values.slice(4).map((v) => +v);
} else {
kind = 'gnu';
version = values.slice(1, 4).map((v) => +v);
}
let good = true;
for (const part of version) {
if (!isFinite(part) || part < 0) {
good = false;
break;
}
}
if (good) {
this[`${prop}Kind`] = kind;
this[`${prop}Version`] = version;
this[`${prop}SpawnOpts`] = spawnOpts;
const verStr = version.join('.');
this._debug(
`>>> Detected ${kind} ${verStr} for ${lang.toUpperCase()} language`
);
return;
}
}
}
}
throw new Error('Unable to detect compiler type');
}
class BuildEnvironment {
constructor(cfg) {
if (typeof cfg !== 'object' || cfg === null)
cfg = {};
this._debug = (typeof cfg.debug === 'function' ? cfg.debug : () => {});
let cc;
let cxx;
if (isWindows) {
const versions = findVS();
this._debug(
`>>> Detected MSVS installations: ${inspect(versions, false, 10)}`
);
if (versions.length === 0)
throw new Error('Unable to detect compiler type');
let selected_msvs;
if (cfg.msvs_version
&& (typeof cfg.msvs_version === 'string'
|| typeof cfg.msvs_version === 'number')) {
this._debug(`>>> Explicit MSVS requested: ${cfg.msvs_version}`);
// Try to select compiler by year
const msvs_version = cfg.msvs_version.toString();
for (const vs of versions) {
if (vs.year.toString() === msvs_version) {
selected_msvs = vs;
break;
}
}
if (selected_msvs === undefined)
throw new Error(`Unable to find MSVS with year '${msvs_version}'`);
} else {
selected_msvs = versions[0]; // Use newest
}
this._debug(`>>> Using MSVS: ${selected_msvs.year}`);
cc = selected_msvs.cl;
cxx = cc;
this._includePaths = selected_msvs.includePaths;
this._libPaths = selected_msvs.libPaths;
// Add (newest) SDK paths if we have them
for (const sdk of selected_msvs.sdks) {
this._debug(`>>> Using Windows SDK: ${sdk.fullVersion}`);
this._includePaths = this._includePaths.concat(sdk.includePaths);
this._libPaths = this._libPaths.concat(sdk.libPaths);
break;
}
const { input, object, output } = genWinTmpFilenames();
this._tmpInFile = input;
this._tmpObjFile = object;
this._tmpOutFile = output;
} else {
cc = ((typeof cfg.compilerC === 'string' && cfg.compilerC)
|| process.env.CC
|| 'cc');
cxx = ((typeof cfg.compilerCXX === 'string' && cfg.compilerCXX)
|| process.env.CXX
|| 'c++');
this._debug(`>>> Using C compiler: ${cc}`);
this._debug(`>>> Using C++ compiler: ${cxx}`);
}
this._cc = cc;
this._ccKind = undefined;
this._ccVersion = undefined;
this._ccSpawnOpts = undefined;
this._cxx = cxx;
this._cxxKind = undefined;
this._cxxVersion = undefined;
this._cxxSpawnOpts = undefined;
if (cfg.cache !== false) {
this._cache = new Map(Object.entries({
c: new Map(),
cxx: new Map(),
}));
} else {
this._cache = null;
}
}
checkDeclared(type, symbolName, opts) {
validateType(type);
if (typeof symbolName !== 'string' || !symbolName)
throw new Error(`Invalid symbol name: ${inspect(symbolName)}`);
const cached = getCachedValue(type, this._cache, 'declared', symbolName);
if (cached !== undefined) {
this._debug(
`>>> Checking if '${symbolName}' is declared... ${cached} (cached)`
);
return cached;
}
if (typeof opts !== 'object' || opts === null)
opts = {};
const { headers, searchLibs } = opts;
const headersList = renderHeaders(Array.isArray(headers)
? headers
: getDefaultHeaders(this, type));
const declName = symbolName.replace(/ *\(.*/, '');
const declUse = symbolName.replace(/\(/, '((')
.replace(/\)/, ') 0)')
.replace(/,/g, ') 0, (');
const libs = [
'',
...(Array.isArray(searchLibs) ? searchLibs : [])
];
for (let lib of libs) {
if (typeof lib !== 'string')
continue;
lib = lib.trim();
const code = `
${headersList}
int
main ()
{
#ifndef ${declName}
#ifdef __cplusplus
(void) ${declUse};
#else
(void) ${declName};
#endif
#endif
;
return 0;
}`;
const compilerParams = (lib ? [`-l${lib}`] : []);
const result = this.tryCompile(type, code, compilerParams);
this._debug(
`>>> Checking if '${symbolName}' is declared `
+ `(using ${lib ? `'${lib}'` : 'no'} library)... ${result === true}`
);
if (result !== true) {
this._debug('... check failed with compiler output:');
this._debug(result.output);
}
if (result === true) {
setCachedValue(
type,
this._cache,
'declared',
symbolName,
(result === true)
);
return true;
}
}
return false;
}
checkFeature(name) {
const cached = getCachedValue('features', this._cache, null, name);
if (cached !== undefined) {
if (typeof cached === 'object'
&& cached !== null
&& typeof cached.val !== undefined) {
return cached.val;
}
return cached;
}
const feature = features.get(name);
if (feature === undefined)
throw new Error(`Invalid feature: ${name}`);
let result = feature(this);
if (result === undefined)
result = null;
setCachedValue('features', this._cache, null, name, result);
if (typeof result === 'object'
&& result !== null
&& typeof result.val !== undefined) {
return result.val;
}
return result;
}
checkFunction(type, funcName, opts) {
validateType(type);
if (typeof funcName !== 'string' || !funcName)
throw new Error(`Invalid function name: ${inspect(funcName)}`);
const cached = getCachedValue(type, this._cache, 'functions', funcName);
if (cached !== undefined) {
this._debug(
`>>> Checking if function '${funcName}' exists... true (cached)`
);
return true;
}
if (typeof opts !== 'object' || opts === null)
opts = {};
const { searchLibs } = opts;
const libs = [
'',
...(Array.isArray(searchLibs) ? searchLibs : [])
];
for (let lib of libs) {
if (typeof lib !== 'string')
continue;
lib = lib.trim();
const code = `
/* Define ${funcName} to an innocuous variant, in case <limits.h> declares
${funcName}.
For example, HP-UX 11i <limits.h> declares gettimeofday. */
#define ${funcName} innocuous_${funcName}
/* System header to define __stub macros and hopefully few prototypes,
which can conflict with char ${funcName} (); below. */
#include <limits.h>
#undef ${funcName}
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
#ifdef __cplusplus
extern "C"
#endif
char ${funcName} ();
/* The GNU C library defines this for functions which it implements
to always fail with ENOSYS. Some functions are actually named
something starting with __ and the normal name is an alias. */
#if defined __stub_${funcName} || defined __stub___${funcName}
choke me
#endif
int
main ()
{
return ${funcName} ();
;
return 0;
}`;
const compilerParams = (lib ? [`-l${lib}`] : []);
const result = this.tryCompile(type, code, compilerParams);
this._debug(
`>>> Checking if function '${funcName}' exists `
+ `(using ${lib ? `'${lib}'` : 'no'} library)... ${result === true}`
);
if (result !== true) {
this._debug('... check failed with compiler output:');
this._debug(result.output);
}
if (result === true) {
setCachedValue(
type,
this._cache,
'functions',
funcName,
compilerParams
);
return true;
}
}
return false;
}
checkHeader(type, header) {
validateType(type);
const cached = getCachedValue(
type,
this._cache,
'headers',
normalizeHeader(header)
);
if (cached !== undefined) {
this._debug(
`>>> Checking if header '${header}' exists... ${cached} (cached)`
);
return cached;
}
const headersList = renderHeaders([header]);
const code = `
${headersList}
int
main ()
{
return 0;
}`;
const result = this.tryCompile(type, code);
setCachedValue(
type,
this._cache,
'headers',
normalizeHeader(header),
(result === true)
);
this._debug(
`>>> Checking if header '${header}' exists... ${result === true}`
);
if (result !== true) {
this._debug('... check failed with compiler output:');
this._debug(result.output);
}
return (result === true);
}
defines(type, rendered) {
if (this._cache === null)
return [];
const defines = new Map();
let types;
if (!['c', 'c++'].includes(type))
types = ['c', 'c++'];
else
types = [type];
for (const t of types) {
const typeCache = this._cache.get(t);
if (!typeCache)
continue;
for (const [subtype, entries] of typeCache) {
for (let name of entries.keys()) {
if (subtype === 'headers')
name = name.replace(RE_HEADER_DECORATED, '$1$2');
defines.set(makeDefine(name, rendered), 1);
}
}
}
{
const featuresCache = this._cache.get('features');
if (featuresCache) {
for (const result of featuresCache.values()) {
if (typeof result === 'object'
&& result !== null
&& Array.isArray(result.defines)) {
for (const define of result.defines)
defines.set(makeDefine(define, rendered), 1);
}
}
}
}
return Array.from(defines.keys());
}
libs(type) {
if (this._cache === null)
return [];
const libs = new Map();
let types;
if (!['c', 'c++'].includes(type))
types = ['c', 'c++'];
else
types = [type];
for (const t of types) {
const typeCache = this._cache.get(t);
if (!typeCache)
continue;
const functionsCache = typeCache.get('functions');
if (!functionsCache)
continue;
for (const compilerParams of functionsCache.values()) {
for (const param of compilerParams)
libs.set(param, 1);
}
}
{
const featuresCache = this._cache.get('features');
if (featuresCache) {
for (const result of featuresCache.values()) {
if (typeof result === 'object'
&& result !== null
&& Array.isArray(result.libs)) {
for (const lib of result.libs)
libs.set(lib, 1);
}
}
}
}
return Array.from(libs.keys());
}
tryCompile(type, code, compilerParams) {
validateType(type);
if (typeof code !== 'string')
throw new TypeError('Invalid code argument');
type = (type === 'c' ? 'c' : 'c++');
const prop = (type === 'c' ? '_cc' : '_cxx');
if (this[`${prop}Kind`] === undefined)
getKind.call(this, prop);
if (!Array.isArray(compilerParams))
compilerParams = [];
let result;
if (this[`${prop}Kind`] === 'msvc') {
const cmpOpts = [`-Fo${this._tmpObjFile}`];
for (const includePath of this._includePaths)
cmpOpts.push('-I', includePath);
const lnkOpts = [];
for (const libPath of this._libPaths)
lnkOpts.push(`-LIBPATH:${libPath}`);
for (const opt of compilerParams) {
let m;
if (m = /^[-/]l(.+)$/.exec(opt))
lnkOpts.push(m[1]);
else
cmpOpts.push(opt);
}
try {
writeFileSync(this._tmpInFile, code);
const args = [
...cmpOpts,
`-T${prop === '_cc' ? 'c' : 'p'}`,
this._tmpInFile,
'-link',
`-out:${this._tmpOutFile}`,
...lnkOpts,
];
result = spawnSync(
this[prop],
args,
this[`${prop}SpawnOpts`]
);
unlinkSync(this._tmpInFile);
// Overwrite stderr with stdout because MSVC seems to print
// errors to stdout instead for some reason
result.stderr = result.stdout;
} catch (ex) {
// We had trouble writing or deleting a temp file, fake
// the result
result = { status: Infinity, stderr: ex.stack };
}
try { unlinkSync(this._tmpObjFile); } catch {}
try { unlinkSync(this._tmpOutFile); } catch {}
} else {
result = spawnSync(
this[prop],
[
'-x', type,
'-o', '/dev/null',
'-',
...compilerParams,
],
{
...this[`${prop}SpawnOpts`],
input: code,
}
);
}
if (result.status === 0)
return true;
const err = new Error('Compilation failed');
err.output = result.stderr;
return err;
}
}
function validateType(type) {
if (!['c', 'c++'].includes(type))
throw new Error('Invalid type argument');
}
function getCachedValue(type, cache, subtype, key) {
if (cache === null)
return;
const typeCache = cache.get(type);
if (!typeCache)
return;
const subtypeCache = (typeof subtype !== 'string'
? typeCache
: typeCache.get(subtype));
if (!subtypeCache)
return;
return subtypeCache.get(key);
}
function setCachedValue(type, cache, subtype, key, value) {
if (cache === null)
return;
let typeCache = cache.get(type);
if (!typeCache)
cache.set(type, typeCache = new Map());
let subtypeCache = (typeof subtype !== 'string'
? typeCache
: typeCache.get(subtype));
if (!subtypeCache)
typeCache.set(subtype, subtypeCache = new Map());
subtypeCache.set(key, value);
}
function renderHeaders(headers) {
let ret = '';
if (Array.isArray(headers)) {
for (const header of headers) {
if (typeof header !== 'string' || !header)
throw new Error(`Invalid header: ${inspect(header)}`);
ret += `#include ${normalizeHeader(header)}\n`;
}
}
return ret;
}
function normalizeHeader(header) {
if (!RE_HEADER_DECORATED.test(header))
header = `<${header}>`;
return header;
}
const DEFAULT_HEADERS_POSIX = [
'stdio.h',
'sys/types.h',
'sys/stat.h',
'stdlib.h',
'stddef.h',
'memory.h',
'string.h',
'strings.h',
'inttypes.h',
'stdint.h',
'unistd.h'
];
const DEFAULT_HEADERS_MSVC = [
'windows.h',
];
function getDefaultHeaders(be, type) {
const prop = (type === 'c' ? '_cc' : '_cxx');
if (be[`${prop}Kind`] === undefined)
getKind.call(be, prop);
let headers;
if (be[`${prop}Kind`] === 'msvc')
headers = DEFAULT_HEADERS_MSVC;
else
headers = DEFAULT_HEADERS_POSIX;
return headers.filter((hdr) => be.checkHeader(type, hdr));
}
const features = new Map(Object.entries({
'strerror_r': (be) => {
const defines = [];
let returnsCharPtr = false;
const declared = be.checkDeclared('c', 'strerror_r');
if (declared) {
const code = `
${renderHeaders(getDefaultHeaders(be, 'c'))}
int
main ()
{
char buf[100];
char x = *strerror_r (0, buf, sizeof buf);
char *p = strerror_r (0, buf, sizeof buf);
return !p || x;
;
return 0;
}`;
returnsCharPtr = (be.tryCompile('c', code) === true);
if (returnsCharPtr)
defines.push('STRERROR_R_CHAR_P');
}
return {
defines,
val: { declared, returnsCharPtr }
};
},
}));
function makeDefine(name, rendered) {
name = name.replace(/[*]/g, 'P')
.replace(/[^_A-Za-z0-9]/g, '_')
.toUpperCase();
return (rendered ? `HAVE_${name}=1` : name);
}
module.exports = BuildEnvironment;