@pipcook/boa
Advanced tools
Comparing version 1.2.1-c9eced5-beta.0 to 1.2.1-fc7360e-beta.0
@@ -1,3 +0,1 @@ | ||
class Enumerate { | ||
@@ -4,0 +2,0 @@ constructor(T, wrap) { |
@@ -1,3 +0,1 @@ | ||
module.exports = () => { | ||
@@ -4,0 +2,0 @@ return function PythonCallable() { |
489
lib/index.js
@@ -1,37 +0,24 @@ | ||
const fs = require('fs'); | ||
'use strict'; | ||
const vm = require('vm'); | ||
const util = require('util'); | ||
const path = require('path'); | ||
const native = require('bindings')('boa'); | ||
const debug = require('debug')('boa'); | ||
const utils = require('./utils'); | ||
const DelegatorLoader = require('./delegator-loader'); | ||
// internal symbols | ||
const IterIdxForSeqSymbol = Symbol('The iteration index for sequence'); | ||
const PyGetAttrSymbol = Symbol('PYTHON_GETATTR_SYMBOL'); | ||
const PySetAttrSymbol = Symbol('PYTHON_SETATTR_SYMBOL'); | ||
const PyGetItemSymbol = Symbol('PYTHON_GETITEM_SYMBOL'); | ||
const PySetItemSymbol = Symbol('PYTHON_SETITEM_SYMBOL'); | ||
const { | ||
notEmpty, | ||
getIndent, | ||
removeIndent, | ||
asHandleObject, | ||
GetOwnershipSymbol, | ||
PyGetAttrSymbol, | ||
PySetAttrSymbol, | ||
PyGetItemSymbol, | ||
PySetItemSymbol, | ||
} = require('./utils'); | ||
const { SharedPythonObject } = require('./worker'); | ||
const { condaPath, pyInst, globals, builtins } = require('./factory'); | ||
const { wrap, _internalWrap } = require('./proxy'); | ||
// read the conda path from the .CONDA_INSTALL_DIR | ||
// eslint-disable-next-line no-sync | ||
const condaPath = fs.readFileSync(path.join(__dirname, '../.CONDA_INSTALL_DIR'), 'utf8'); | ||
if (!process.env.PYTHONHOME) { | ||
process.env.PYTHONHOME = condaPath; | ||
} | ||
// create the global-scoped instance | ||
let pyInst = global.__pipcook_boa_pyinst__; | ||
if (pyInst == null) { | ||
pyInst = new native.Python(process.argv.slice(1)); | ||
global.__pipcook_boa_pyinst__ = pyInst; | ||
} | ||
const importedNames = []; | ||
// FIXME(Yorkie): move to costa or daemon? | ||
const sharedModules = ['sys', 'torch']; | ||
const globals = pyInst.globals(); | ||
const builtins = pyInst.builtins(); | ||
const delegators = DelegatorLoader.load(); | ||
let defaultSysPath = []; | ||
@@ -42,14 +29,2 @@ | ||
function getTypeInfo(T) { | ||
const typeo = builtins.__getitem__('type').invoke(asHandleObject(T)); | ||
const tinfo = { module: null, name: null }; | ||
if (typeo.__hasattr__('__module__')) { | ||
tinfo.module = typeo.__getattr__('__module__').toString(); | ||
} | ||
if (typeo.__hasattr__('__name__')) { | ||
tinfo.name = typeo.__getattr__('__name__').toString(); | ||
} | ||
return tinfo; | ||
} | ||
function setenv(externalSearchPath) { | ||
@@ -85,74 +60,2 @@ const sys = pyInst.import('sys'); | ||
function dump(T) { | ||
return pyInst.import('json') | ||
.__getattr__('dumps') | ||
.invoke(asHandleObject(T), { | ||
// use str method to serialize those fields which cannot be serialized by default | ||
default: _internalWrap(builtins).str, | ||
[native.NODE_PYTHON_KWARGS_NAME]: true, | ||
}); | ||
} | ||
function getDelegator(type) { | ||
if (typeof type === 'string') { | ||
return delegators[type]; | ||
} | ||
const { module, name } = type; | ||
if (Object.prototype.hasOwnProperty.call(delegators, module)) { | ||
return delegators[module][name]; | ||
} | ||
return undefined; | ||
} | ||
// The function `wrap(T)` is used to return an object or value for using. | ||
// It depends on the type of `T` in Python world, usually it returns a | ||
// `Proxy` object that's based on `T`, when the type could be represented | ||
// as number/boolean/string/null, the return value would be converted to | ||
// corresponding JS primative. | ||
function wrap(T) { | ||
// if `T` is null or undefined, returning itself by default. | ||
if (T === null || T == undefined) { | ||
return T; | ||
} | ||
const type = getTypeInfo(T); | ||
debug(`start wrapping an object, and its type is "${type.module}.${type.name}"`); | ||
// if `type` is "NoneType", returning the null. | ||
if (type.module === 'builtins' && type.name === 'NoneType') { | ||
return null; | ||
} | ||
// FIXME(Yorkie): directly returning the primitive value on the | ||
// following conditions. | ||
if ([ | ||
/** python types convert to primitive values. */ | ||
'int', /** Number */ | ||
'int64', /** BigInt */ | ||
'float', /** Number */ | ||
'float64', /** BigDecimal(depends on new tc39 proposal) */ | ||
'bool', /** Boolean */ | ||
'str', /** String */ | ||
/** except for null and undefined */ | ||
].includes(type.name)) { | ||
return T.toPrimitive(); | ||
} | ||
let fn = getDelegator(T.isCallable() ? 'callee' : type); | ||
if (typeof fn !== 'function') { | ||
fn = getDelegator('default'); | ||
} | ||
// use internalWrap to generate proxy object with corresponding delegator. | ||
const wrapped = _internalWrap(T, fn(T, wrap)); | ||
T[native.NODE_PYTHON_WRAPPED_NAME] = wrapped; | ||
return wrapped; | ||
} | ||
function asHandleObject(T) { | ||
return { | ||
// namely shortcut for Python object. | ||
[native.NODE_PYTHON_HANDLE_NAME]: T | ||
}; | ||
} | ||
function asBytesObject(str) { | ||
@@ -165,305 +68,2 @@ return { | ||
function _internalWrap(T, src={}) { | ||
Object.defineProperties(src, { | ||
/** | ||
* @property native.NODE_PYTHON_WRAPPED_NAME | ||
* @private | ||
*/ | ||
[native.NODE_PYTHON_HANDLE_NAME]: { | ||
enumerable: true, | ||
writable: false, | ||
value: T, | ||
}, | ||
/** | ||
* @method native.NODE_PYTHON_JS_DISPATCH | ||
* @private | ||
*/ | ||
[native.NODE_PYTHON_JS_DISPATCH]: { | ||
enumerable: true, | ||
// FIXME(Yorkie): temporarily set `configurable` to false here. | ||
// See https://github.com/v8/v8/blob/7.9.317/src/objects/objects.cc#L1176-L1179 | ||
// | ||
// The proxy object's get trap handler is inconsistent with this descriptor when | ||
// the value is a function, which means the `inconsistent` to be true, then throwing | ||
// a `kProxyGetNonConfigurableData` error. | ||
// | ||
// In order to better solve, we need to define both `get` and `has` traps in the | ||
// proxy object, and move descriptors to the trap handler. | ||
configurable: true, | ||
writable: false, | ||
value: function(fn, isClassMethod, ...args) { | ||
if (isClassMethod) { | ||
return fn.apply(wrap(args[0]), args.slice(1).map(wrap)); | ||
} else { | ||
return fn.apply(this, args.map(wrap)); | ||
} | ||
}, | ||
}, | ||
/** | ||
* @method invoke | ||
* @param {object} args | ||
* @private | ||
*/ | ||
invoke: { | ||
enumerable: false, | ||
writable: false, | ||
value: args => { | ||
return T.invoke.apply(T, args); | ||
}, | ||
}, | ||
/** | ||
* @method toString | ||
* @public | ||
*/ | ||
toString: { | ||
configurable: true, | ||
enumerable: false, | ||
writable: false, | ||
value: () => T.toString(), | ||
}, | ||
/** | ||
* @method toJSON | ||
* @public | ||
*/ | ||
toJSON: { | ||
configurable: true, | ||
enumerable: false, | ||
writable: false, | ||
value: () => { | ||
const type = getTypeInfo(T); | ||
let str; | ||
if (type.module === 'numpy') { | ||
str = dump(T.__getattr__('tolist').invoke()); | ||
} else { | ||
str = dump(T); | ||
} | ||
// TODO(Yorkie): more performant way to serialize objects? | ||
return JSON.parse(wrap(str)); | ||
}, | ||
}, | ||
/** | ||
* Shortcut for slicing object. | ||
* @method slice | ||
* @public | ||
*/ | ||
slice: { | ||
configurable: true, | ||
enumerable: false, | ||
writable: false, | ||
value: (start, end, step) => { | ||
// slice(start, end, step) | ||
const slice = builtins.__getitem__('slice') | ||
.invoke(start, end, step); | ||
// use the slice object as the key for s[x:y:z] | ||
return wrap(T.__getitem__(asHandleObject(slice))); | ||
}, | ||
}, | ||
/** | ||
* This is used to cusom the console.log output by calling toString(). | ||
* @method util.inspect.custom | ||
* @public | ||
*/ | ||
[util.inspect.custom]: { | ||
configurable: true, | ||
enumerable: false, | ||
writable: false, | ||
value: () => T.toString(), | ||
}, | ||
/** | ||
* @method Symbol.toPrimitive | ||
* @param {string} hint | ||
* @public | ||
*/ | ||
// Forward compatible with newer `toPrimitive` spec | ||
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toPrimitive | ||
[Symbol.toPrimitive]: { | ||
configurable: true, | ||
enumerable: false, | ||
writable: false, | ||
value: () => T.toString(), | ||
}, | ||
/** | ||
* Implementation of ES iterator protocol, See: | ||
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols | ||
* | ||
* @method Symbol.iterator | ||
* @public | ||
*/ | ||
[Symbol.iterator]: { | ||
configurable: true, | ||
enumerable: false, | ||
writable: false, | ||
value: () => { | ||
if (T.isIterator()) { | ||
return { | ||
next: () => { | ||
const curr = T.next(); | ||
return { | ||
done: curr.done, | ||
value: wrap(curr.value), | ||
}; | ||
}, | ||
}; | ||
} | ||
if (T.isSequence()) { | ||
return { | ||
next: function next() { | ||
if (typeof this[IterIdxForSeqSymbol] === 'number') { | ||
this[IterIdxForSeqSymbol] += 1; | ||
} else { | ||
this[IterIdxForSeqSymbol] = 0; | ||
} | ||
const lengthOfSeq = builtins.__getitem__('len') | ||
.invoke(asHandleObject(T)).toPrimitive(); | ||
const index = this[IterIdxForSeqSymbol]; | ||
if (index >= lengthOfSeq) { | ||
return { done: true, value: undefined }; | ||
} | ||
return { | ||
done: false, | ||
value: wrap(T.__getitem__(index)), | ||
}; | ||
} | ||
}; | ||
} | ||
throw new TypeError('object is not iteratable or sequence.'); | ||
}, | ||
}, | ||
/** | ||
* @method __hash__ | ||
* @public | ||
*/ | ||
__hash__: { | ||
configurable: true, | ||
enumerable: true, | ||
writable: false, | ||
value: () => T.__hash__(), | ||
}, | ||
/** | ||
* @method [PyGetAttrSymbol] | ||
* @public | ||
*/ | ||
[PyGetAttrSymbol]: { | ||
configurable: true, | ||
enumerable: true, | ||
writable: false, | ||
value: k => wrap(T.__getattr__(k)), | ||
}, | ||
/** | ||
* @method [PySetAttrSymbol] | ||
* @public | ||
*/ | ||
[PySetAttrSymbol]: { | ||
configurable: true, | ||
enumerable: true, | ||
writable: false, | ||
value: (k, v) => T.__setattr__(k, v), | ||
}, | ||
/** | ||
* @method [PyGetItemSymbol] | ||
* @public | ||
*/ | ||
[PyGetItemSymbol]: { | ||
configurable: true, | ||
enumerable: true, | ||
writable: false, | ||
value: k => wrap(T.__getitem__(k)), | ||
}, | ||
/** | ||
* @method [PySetItemSymbol] | ||
* @public | ||
*/ | ||
[PySetItemSymbol]: { | ||
configurable: true, | ||
enumerable: true, | ||
writable: false, | ||
value: (k, v) => T.__setitem__(k, v), | ||
}, | ||
}); | ||
// Create the proxy object for handlers | ||
let newTarget; | ||
return (newTarget = new Proxy(src, { | ||
'get'(target, name) { | ||
debug(`get property on "${target.constructor.name}", ` + | ||
`name is "${name.toString()}"`); | ||
const { hasOwnProperty } = Object.prototype; | ||
const constructProto = target.constructor.prototype; | ||
if (hasOwnProperty.call(target, name) /* check for own property */ || | ||
hasOwnProperty.call(constructProto, name) /* check for inherited one-level */ | ||
) { | ||
const value = target[name]; | ||
debug(`found "${name.toString()}" from object own properties ` + | ||
`or one-level properties.`); | ||
if (typeof value === 'function') { | ||
// FIXME(Yorkie): make sure the function's this is correct. | ||
return value.bind(newTarget); | ||
} else { | ||
return value; | ||
} | ||
} | ||
/** Enter the Python world. */ | ||
if (typeof name === 'string') { | ||
if (/^[0-9]+$/.test(name)) { | ||
debug('name is detected to be an index.'); | ||
const n = parseInt(name, 10); | ||
return wrap(T.__getitem__(n)); | ||
} | ||
if (T.__hasattr__(name)) { | ||
debug(`found "${name}" as python attr`); | ||
return wrap(T.__getattr__(name)); | ||
} | ||
} | ||
try { | ||
const r = T.__getitem__(name); | ||
if (r != null) { | ||
debug(`found "${name.toString()}" as python item`); | ||
return wrap(r); | ||
} | ||
} catch (e) { | ||
debug(`accessing the item["${name.toString()}"] failed ` + | ||
`with ${e.message}`); | ||
} | ||
}, | ||
'set'(target, name, value) { | ||
if (typeof name === 'string') { | ||
if (/^[0-9]+$/.test(name)) { | ||
return T.__setitem__(parseInt(name, 10), value) !== -1; | ||
} | ||
if (T.__hasattr__(name)) { | ||
return T.__setattr__(name, value) !== -1; | ||
} | ||
} | ||
let r = T.__setitem__(name, value); | ||
if (r === -1) { | ||
r = T.__setattr__(name, value); | ||
} | ||
return r !== -1; | ||
}, | ||
'apply'(target, thisArg, argumentsList) { | ||
return wrap(target.invoke(argumentsList)); | ||
}, | ||
'construct'(target, argumentsList, newClass) { | ||
if (newClass.name === 'PythonCallable') { | ||
return wrap(target.invoke(argumentsList)); | ||
} | ||
if (!newClass.prototype.$pyclass) { | ||
const pyclass = T.createClass(newClass.name, target); | ||
Object.getOwnPropertyNames(newClass.prototype) | ||
.filter(name => name !== 'constructor' || name !== '__init__') | ||
.forEach(name => { | ||
pyclass.setClassMethod(name, newClass.prototype[name]); | ||
}); | ||
newClass.prototype.$pyclass = wrap(pyclass); | ||
} | ||
// return the instance | ||
return newClass.prototype.$pyclass.apply(null, argumentsList); | ||
}, | ||
})); | ||
} | ||
module.exports = { | ||
@@ -477,22 +77,5 @@ /** | ||
/** | ||
* Public symbols | ||
* @class SharedPythonObject | ||
*/ | ||
symbols: { | ||
/** | ||
* __getattr__ | ||
*/ | ||
PyGetAttrSymbol, | ||
/** | ||
* __setattr__ | ||
*/ | ||
PySetAttrSymbol, | ||
/** | ||
* __getitem__ | ||
*/ | ||
PyGetItemSymbol, | ||
/** | ||
* __setitem__ | ||
*/ | ||
PySetItemSymbol, | ||
}, | ||
SharedPythonObject, | ||
/* | ||
@@ -574,2 +157,6 @@ * Import a Python module. | ||
}, | ||
/** | ||
* Evaluate a Python expression. | ||
* @param {string} strs the Python exprs. | ||
*/ | ||
'eval': (strs, ...params) => { | ||
@@ -599,9 +186,35 @@ let src = ''; | ||
// for multiline executing. | ||
const lines = src.split('\n').filter(utils.notEmpty); | ||
const indent = utils.getIndent(lines); | ||
const lines = src.split('\n').filter(notEmpty); | ||
const indent = getIndent(lines); | ||
return wrap(pyInst.eval( | ||
lines.map(utils.removeIndent(indent)).join('\n'), | ||
lines.map(removeIndent(indent)).join('\n'), | ||
{ globals: env, locals: env } | ||
)); | ||
}, | ||
/** | ||
* Symbols | ||
*/ | ||
symbols: { | ||
/** | ||
* The symbol is used to get the ownership value on an object. | ||
*/ | ||
GetOwnershipSymbol, | ||
/** | ||
* __getattr__ | ||
*/ | ||
PyGetAttrSymbol, | ||
/** | ||
* __setattr__ | ||
*/ | ||
PySetAttrSymbol, | ||
/** | ||
* __getitem__ | ||
*/ | ||
PyGetItemSymbol, | ||
/** | ||
* __setitem__ | ||
*/ | ||
PySetItemSymbol, | ||
}, | ||
}; |
@@ -0,3 +1,11 @@ | ||
'use strict'; | ||
const { NODE_PYTHON_HANDLE_NAME } = require('bindings')('boa'); | ||
const GetOwnershipSymbol = Symbol('GET_OWNERSHIP'); | ||
const PyGetAttrSymbol = Symbol('PYTHON_GETATTR_SYMBOL'); | ||
const PySetAttrSymbol = Symbol('PYTHON_SETATTR_SYMBOL'); | ||
const PyGetItemSymbol = Symbol('PYTHON_GETITEM_SYMBOL'); | ||
const PySetItemSymbol = Symbol('PYTHON_SETITEM_SYMBOL'); | ||
function notEmpty(line) { | ||
@@ -23,4 +31,20 @@ return /^\s*$/.test(line) === false; | ||
exports.notEmpty = notEmpty; | ||
exports.getIndent = getIndent; | ||
exports.removeIndent = removeIndent; | ||
function asHandleObject(T) { | ||
return { | ||
// namely shortcut for Python object. | ||
[NODE_PYTHON_HANDLE_NAME]: T | ||
}; | ||
} | ||
module.exports = { | ||
notEmpty, | ||
getIndent, | ||
removeIndent, | ||
asHandleObject, | ||
// symbols | ||
GetOwnershipSymbol, | ||
PyGetAttrSymbol, | ||
PySetAttrSymbol, | ||
PyGetItemSymbol, | ||
PySetItemSymbol, | ||
}; |
{ | ||
"name": "@pipcook/boa", | ||
"version": "1.2.1-c9eced5-beta.0", | ||
"version": "1.2.1-fc7360e-beta.0", | ||
"description": "Use Python modules seamlessly in Node.js", | ||
@@ -8,8 +8,17 @@ "main": "lib/index.js", | ||
"clean": "sh tools/clean-python.sh && make -C ./pybind11/ clean", | ||
"preinstall": "node tools/check-dependence.js && make -C ./pybind11/ && node tools/install-python.js && node tools/install-requirements.js", | ||
"predeps": "tools/check-dependence.js", | ||
"deps": "make -C ./pybind11/ && tools/install-python.js", | ||
"preinstall": "npm run deps", | ||
"postinstall": "npm run build", | ||
"test": "tape ./tests/**/*.js", | ||
"lint": "eslint . -c .eslintrc.js --no-eslintrc && ./clang-format.py", | ||
"pretest": "npm run lint", | ||
"pretest:js": "bip install numpy", | ||
"test": "npm run test:js && npm run test:ts", | ||
"test:js": "tape ./tests/**/*.js", | ||
"test:ts": "ts-node ./node_modules/.bin/tape ./tests/**/*.ts", | ||
"cov": "nyc --reporter=text-summary npm run test", | ||
"cov:report": "nyc report -r=lcov", | ||
"lint": "npm run lint:js && npm run lint:clang", | ||
"lint:js": "eslint . -c .eslintrc.js --no-eslintrc", | ||
"lint:clang": "./clang-format.py", | ||
"build": "npm run compile", | ||
"pretest": "npm run lint", | ||
"codecov": "nyc report --reporter=lcovonly && codecov", | ||
@@ -43,6 +52,8 @@ "htmlcov": "nyc report --reporter=html", | ||
"devDependencies": { | ||
"@types/tape": "^4.13.0", | ||
"codecov": "^3.6.5", | ||
"eslint": "^6.7.2", | ||
"nyc": "^14.1.1", | ||
"tape": "^5.0.1" | ||
"nyc": "^15.1.0", | ||
"tape": "^5.0.1", | ||
"ts-node": "^8.6.2" | ||
}, | ||
@@ -52,3 +63,3 @@ "publishConfig": { | ||
}, | ||
"gitHead": "df6a138b57f1958f80504af07c1990c51784d2a3" | ||
"gitHead": "1d38a9b2cc96eb9df2e11696110677bd37211b28" | ||
} |
@@ -0,1 +1,3 @@ | ||
#!/usr/bin/env node | ||
'use strict'; | ||
@@ -16,3 +18,3 @@ const { run, PLATFORM } = require('./utils'); | ||
} else { | ||
throw new TypeError(`No support for your platform ${PLATFORM}`); | ||
throw new TypeError(`no support for your platform ${PLATFORM}`); | ||
} | ||
@@ -19,0 +21,0 @@ |
@@ -0,1 +1,3 @@ | ||
#!/usr/bin/env node | ||
'use strict'; | ||
@@ -2,0 +4,0 @@ |
@@ -0,45 +1,47 @@ | ||
#!/usr/bin/env node | ||
'use strict'; | ||
const { run, py, initAndGetCondaPath, PLATFORM, ARCH } = require('./utils'); | ||
const utils = require('./utils'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
let CONDA_DOWNLOAD_PREFIX = 'https://repo.anaconda.com/miniconda'; | ||
if (process.env.BOA_TUNA) { | ||
CONDA_DOWNLOAD_PREFIX = 'https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda'; | ||
const run = utils.run.bind(utils); | ||
const py = utils.py.bind(utils); | ||
if (!utils.shouldInstallConda()) { | ||
console.info('skip installing the python from conda.'); | ||
return process.exit(0); | ||
} | ||
if (process.env.BOA_CONDA_MIRROR) { | ||
CONDA_DOWNLOAD_PREFIX = process.env.BOA_CONDA_MIRROR; | ||
} | ||
const CONDA_LOCAL_PATH = initAndGetCondaPath(); | ||
let condaDownloadName = 'Miniconda3-4.7.12.1'; | ||
// download and install conda | ||
const remoteURL = utils.getCondaRemote(); | ||
const installDir = utils.resolveAndUpdateCondaPath(); | ||
const downloader = utils.getCondaDownloaderName(); | ||
if (PLATFORM === 'linux') { | ||
condaDownloadName += '-Linux'; | ||
} else if (PLATFORM === 'darwin') { | ||
condaDownloadName += '-MacOSX'; | ||
} else { | ||
throw new TypeError(`No support for your platform ${PLATFORM}`); | ||
// fetch the downloader file if that doesn't exist. | ||
if (!fs.existsSync(downloader)) { | ||
run('curl', `${remoteURL}/${downloader}`, '>', downloader); | ||
} | ||
if (ARCH === 'x64') { | ||
condaDownloadName += '-x86_64'; | ||
} else if (PLATFORM !== 'darwin') { | ||
condaDownloadName += '-x86'; | ||
// check if the python is installed correctly, we will skip the installation | ||
// when it's installed before. | ||
if (!utils.shouldPythonInstalledOn(installDir)) { | ||
// clean the install dir. | ||
run('rm', '-rf', installDir); | ||
// install | ||
run('sh', downloader, `-f -b -p ${installDir}`); | ||
} | ||
condaDownloadName = `${condaDownloadName}.sh`; | ||
if (!fs.existsSync(condaDownloadName)) { | ||
run(`curl ${CONDA_DOWNLOAD_PREFIX}/${condaDownloadName} > ${condaDownloadName}`); | ||
// cleanup the standard libs. | ||
if (utils.PLATFORM === 'darwin') { | ||
run('rm', '-rf', `${installDir}/lib/libc++*`); | ||
} else if (utils.PLATFORM === 'linux') { | ||
run('rm', '-rf', `${installDir}/lib/libstdc++.so*`); | ||
run('rm', '-rf', `${installDir}/lib/libgcc_s.so*`); | ||
} | ||
if (!fs.existsSync(path.join(CONDA_LOCAL_PATH, 'bin', 'python'))) { | ||
run('rm', `-rf ${CONDA_LOCAL_PATH}`); | ||
run('sh', `./${condaDownloadName}`, `-f -b -p ${CONDA_LOCAL_PATH}`); | ||
run('rm', `-rf ${CONDA_LOCAL_PATH}/lib/libstdc++.so*`); | ||
run('rm', `-rf ${CONDA_LOCAL_PATH}/lib/libgcc_s.so*`); | ||
} | ||
// dump info | ||
py(`${installDir}/bin/conda`, 'info -a'); | ||
// dump info | ||
py(`${CONDA_LOCAL_PATH}/bin/conda`, 'info -a'); | ||
// install python packages | ||
utils.installPythonPackages(); |
@@ -7,47 +7,265 @@ 'use strict'; | ||
const { execSync } = require('child_process'); | ||
const { BOA_CONDA_PREFIX, BOA_TUNA } = process.env; | ||
// Constants | ||
const CONDA_INSTALL_DIR = path.join(__dirname, '../.CONDA_INSTALL_DIR'); | ||
let { BOA_CONDA_INDEX } = process.env; | ||
const CONDA_INSTALL_NAME = '.miniconda'; | ||
// Environment variables | ||
let { | ||
/** | ||
* The prefix path for choosing the conda directory, possible values are: | ||
* - {\@package} represents a relative path to the boa installed package. | ||
* - {\@cwd} represents a relative path to the current working directory it depends on shell. | ||
* - {string} represents a absolute path to your installed conda path. | ||
* | ||
* Note that, if the variable is specified with relative path, we will throw an error. | ||
*/ | ||
BOA_CONDA_PREFIX = '@package', | ||
/** | ||
* The boolean if installing from tuna mirror. | ||
* @boolean | ||
*/ | ||
BOA_TUNA, | ||
/** | ||
* The boolean if installing the conda. | ||
*/ | ||
BOA_FORCE_INSTALL, | ||
/** | ||
* The conda remote URL, default is https://repo.anaconda.com/miniconda. | ||
* @string | ||
*/ | ||
BOA_CONDA_REMOTE = 'https://repo.anaconda.com/miniconda', | ||
BOA_CONDA_MIRROR, | ||
/** | ||
* The conda version to download from remote URL. | ||
* @string | ||
*/ | ||
BOA_CONDA_VERSION = 'Miniconda3-4.7.12.1', | ||
/** | ||
* The conda index URI, if `BOA_TUNA` is specified, it will be set. | ||
* @string | ||
*/ | ||
BOA_CONDA_INDEX, | ||
/** | ||
* The python library version, for example 3.7m | ||
* @string | ||
*/ | ||
BOA_PYTHON_VERSION = '3.7m', | ||
/** | ||
* Install the base packages: numpy/scikit/... | ||
*/ | ||
BOA_PACKAGE_BASE, | ||
/** | ||
* Install the cv packages: opencv | ||
*/ | ||
BOA_PACKAGE_CV, | ||
} = process.env; | ||
// Check for BOA_CONDA_PREFIX, throw an TypeError when it's not a relative path. | ||
if (!/\@(package|cwd)/.test(BOA_CONDA_PREFIX) && !path.isAbsolute(BOA_CONDA_PREFIX)) { | ||
throw new TypeError('BOA_CONDA_PREFIX is required to be an absolute path'); | ||
} | ||
// aliases | ||
if (typeof BOA_CONDA_MIRROR === 'string' && BOA_CONDA_MIRROR.length > 0) { | ||
BOA_CONDA_REMOTE = BOA_CONDA_MIRROR; | ||
} | ||
// Specify BOA_TUNA for simplifying the env setup. | ||
if (BOA_TUNA && !BOA_CONDA_INDEX) { | ||
if (BOA_TUNA) { | ||
BOA_CONDA_REMOTE = 'https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda'; | ||
BOA_CONDA_INDEX = 'https://pypi.tuna.tsinghua.edu.cn/simple'; | ||
} | ||
exports.run = (...args) => execSync.call(null, args.join(' '), { stdio: 'inherit' }); | ||
exports.PLATFORM = os.platform(); | ||
exports.ARCH = os.arch(); | ||
module.exports = { | ||
/** | ||
* Call to `os.platform()`. | ||
*/ | ||
PLATFORM: os.platform(), | ||
/** | ||
* Call to `os.arch()` | ||
*/ | ||
ARCH: os.arch(), | ||
exports.getCondaPath = () => fs.readFileSync(CONDA_INSTALL_DIR, 'utf8'); | ||
exports.printCondaPath = () => console.log(this.getCondaPath()); | ||
exports.initAndGetCondaPath = () => { | ||
let condaPath; | ||
if (BOA_CONDA_PREFIX === '@cwd' || !BOA_CONDA_PREFIX) { | ||
condaPath = path.join(process.cwd(), '.miniconda'); | ||
} else if (BOA_CONDA_PREFIX === '@package') { | ||
condaPath = path.join(__dirname, '../.miniconda'); | ||
} else { | ||
condaPath = path.join(BOA_CONDA_PREFIX, '.miniconda'); | ||
} | ||
fs.writeFileSync(CONDA_INSTALL_DIR, condaPath, 'utf8'); | ||
return condaPath; | ||
}; | ||
/** | ||
* Compute the state if the build status should install conda. | ||
*/ | ||
shouldInstallConda() { | ||
if ('@package' === BOA_CONDA_PREFIX) { | ||
return true; | ||
} | ||
let expectedPath = BOA_CONDA_PREFIX; | ||
if (/^\@cwd/.test(expectedPath)) { | ||
expectedPath = expectedPath.replace('@cwd', process.cwd()); | ||
} | ||
if (!fs.existsSync(expectedPath)) { | ||
console.warn(`found the path(${expectedPath}) not exists, conda installation has been switched on.`); | ||
return true; | ||
} | ||
return false; | ||
}, | ||
exports.py = (...args) => { | ||
const { run, getCondaPath } = exports; | ||
const CONDA_LOCAL_PATH = getCondaPath(); | ||
const Python = path.join(CONDA_LOCAL_PATH, 'bin/python'); | ||
const cmds = [Python].concat(args); | ||
return run(...cmds); | ||
}; | ||
/** | ||
* Resolves the conda path by configs, then update it to the CONDA_INSTALL_DIR dot file, it returns | ||
* the resolved value. | ||
*/ | ||
resolveAndUpdateCondaPath() { | ||
let resolvedPrefix; | ||
if (BOA_CONDA_PREFIX === '@package') { | ||
resolvedPrefix = path.join(__dirname, '..'); | ||
} else if (/^\@cwd/.test(BOA_CONDA_PREFIX)) { | ||
resolvedPrefix = (BOA_CONDA_PREFIX + '').replace('@cwd', process.cwd()); | ||
} else { | ||
resolvedPrefix = BOA_CONDA_PREFIX; | ||
} | ||
const str = path.join(resolvedPrefix, CONDA_INSTALL_NAME); | ||
fs.writeFileSync(CONDA_INSTALL_DIR, str, 'utf8'); | ||
return str; | ||
}, | ||
exports.pip = (...args) => { | ||
const { py, getCondaPath } = exports; | ||
const CONDA_LOCAL_PATH = getCondaPath(); | ||
const PIP = path.join(CONDA_LOCAL_PATH, 'bin/pip'); | ||
const cmds = [PIP].concat(args); | ||
if (BOA_CONDA_INDEX) { | ||
cmds.push(`-i ${BOA_CONDA_INDEX}`); | ||
} | ||
return py(...cmds); | ||
/** | ||
* Return the conda remote URL. | ||
*/ | ||
getCondaRemote() { | ||
return BOA_CONDA_REMOTE; | ||
}, | ||
/** | ||
* Get the conda directory absolute path. | ||
*/ | ||
getCondaPath() { | ||
if (!fs.existsSync(CONDA_INSTALL_DIR)) { | ||
throw new TypeError(`${CONDA_INSTALL_DIR} not found, please reinstall "@pipcook/boa".`); | ||
} | ||
const condaPath = fs.readFileSync(CONDA_INSTALL_DIR, 'utf8'); | ||
if (!condaPath || !fs.existsSync(condaPath)) { | ||
this.run('rm', '-rf', CONDA_INSTALL_DIR); | ||
throw new TypeError(`invalid CONDA_INSTALL_DIR file, please reinstall "@pipcook/boa".`); | ||
} | ||
return condaPath; | ||
}, | ||
/** | ||
* Return the complete conda URL to be downloaded the specific version. | ||
*/ | ||
getCondaDownloaderName() { | ||
let downloaderName = (BOA_CONDA_VERSION + ''); | ||
// matches for platforms: linux/macos | ||
if (this.PLATFORM === 'linux') { | ||
downloaderName += '-Linux'; | ||
} else if (this.PLATFORM === 'darwin') { | ||
downloaderName += '-MacOSX'; | ||
} else { | ||
throw new TypeError(`no support for platform ${PLATFORM}`); | ||
} | ||
// matches for archs: x64/x86/ppc64/? | ||
if (this.ARCH === 'x64') { | ||
downloaderName += '-x86_64'; | ||
} else if (this.ARCH === 'ppc64') { | ||
downloaderName += '-ppc64le'; | ||
} else { | ||
if (this.PLATFORM !== 'darwin') { | ||
downloaderName += '-x86'; | ||
} | ||
} | ||
return `${downloaderName}.sh`; | ||
}, | ||
/** | ||
* Get the absolute path of the python library, this is used to compile with. | ||
*/ | ||
getPythonLibraryAbsPath() { | ||
return path.join(this.getCondaPath(), 'lib'); | ||
}, | ||
/** | ||
* Get the runpath/rpath of the python library, this is used to load dynamically. | ||
*/ | ||
getPythonLibraryRunPath() { | ||
if (BOA_CONDA_PREFIX === '@packages') { | ||
const prefix = PLATFORM === 'darwin' ? '@loader_path' : '$$ORIGIN'; | ||
return `${prefix}/../../${CONDA_INSTALL_NAME}/lib`; | ||
} else { | ||
return this.getPythonLibraryAbsPath(); | ||
} | ||
}, | ||
/** | ||
* Returns if the python should be installed on the current prefix. To install the Python always, | ||
* set the `BOA_FORCE_INSTALL=1` to return false forcily. | ||
* @param {string} prefix | ||
*/ | ||
shouldPythonInstalledOn(prefix) { | ||
if (BOA_FORCE_INSTALL) { | ||
return false; | ||
} | ||
return fs.existsSync(path.join(prefix, 'bin/python')); | ||
}, | ||
/** | ||
* Get the Python version to be used by Boa. | ||
*/ | ||
getPythonVersion() { | ||
// TODO(Yorkie): fetch the default python version from conda or other sources. | ||
return BOA_PYTHON_VERSION; | ||
}, | ||
/** | ||
* Get the path of Python headers. | ||
*/ | ||
getPythonHeaderPath() { | ||
return `${this.getCondaPath()}/include/python${this.getPythonVersion()}`; | ||
}, | ||
/** | ||
* Install the Python packages by the BOA_PACKAGE_* variables. | ||
*/ | ||
installPythonPackages() { | ||
const packagesToInstall = []; | ||
if (BOA_PACKAGE_BASE) { | ||
packagesToInstall.push('numpy'); | ||
packagesToInstall.push('scikit-learn'); | ||
} | ||
if (BOA_PACKAGE_CV) { | ||
packagesToInstall.push('opencv-python'); | ||
} | ||
for (let pkg of packagesToInstall) { | ||
this.pip('install', pkg, '--default-timeout=1000'); | ||
} | ||
}, | ||
/** | ||
* Execute a shell command. | ||
* @param {...any} args | ||
*/ | ||
run(...args) { | ||
const cmd = args.join(' '); | ||
console.info(`sh "${cmd}"`); | ||
return execSync.call(null, cmd, { stdio: 'inherit' }); | ||
}, | ||
/** | ||
* Execute a python command. | ||
* @param {...any} args | ||
*/ | ||
py(...args) { | ||
const python = path.join(this.getCondaPath(), 'bin/python'); | ||
const cmds = [ python ].concat(args); | ||
return this.run(...cmds); | ||
}, | ||
/** | ||
* Execute a pip command. | ||
* @param {...any} args | ||
*/ | ||
pip(...args) { | ||
const pip = path.join(this.getCondaPath(), 'bin/pip'); | ||
const cmds = [ pip ].concat(args); | ||
if (BOA_CONDA_INDEX) { | ||
cmds.push(`-i ${BOA_CONDA_INDEX}`); | ||
} | ||
cmds.push('--timeout=5'); | ||
return this.py(...cmds); | ||
}, | ||
}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Install scripts
Supply chain riskInstall scripts are run when the package is installed. The majority of malware in npm is hidden in install scripts.
Found 1 instance in 1 package
Install scripts
Supply chain riskInstall scripts are run when the package is installed. The majority of malware in npm is hidden in install scripts.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
2194625
272
10127
11
6