@waynew/boa
Version:
Use Python modules seamlessly in Node.js
278 lines (254 loc) • 7.53 kB
JavaScript
;
const os = require('os');
const path = require('path');
const { execSync } = require('child_process');
const fs = require('fs');
// Constants
const CONDA_INSTALL_DIR = path.join(__dirname, '../.CONDA_INSTALL_DIR');
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 = "1",
/**
* 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-py39_4.9.2',
/**
* The conda index URI, if `BOA_TUNA` is specified, it will be set.
* @string
*/
BOA_CONDA_INDEX,
/**
* The python library version, for example 3.9m
* @string
*/
BOA_PYTHON_VERSION = '3.9',
/**
* 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 !== 'none') {
BOA_CONDA_REMOTE = 'https://mirrors.bfsu.edu.cn/anaconda/miniconda';
BOA_CONDA_INDEX = 'https://pypi.tuna.tsinghua.edu.cn/simple';
}
module.exports = {
/**
* Call to `os.platform()`.
*/
PLATFORM: os.platform(),
/**
* Call to `os.arch()`
*/
ARCH: os.arch(),
/**
* 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;
},
/**
* 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;
},
/**
* 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 bin, this is use to compile with.
*/
getPythonBinaryAbsPath() {
return path.join(this.getCondaPath(), 'bin');
},
/**
* 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 === '@package') {
const prefix = this.PLATFORM === 'darwin' ? '@loader_path' : '$$ORIGIN';
return `${prefix}/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);
},
};