renovate/lib/manager/pip_setup/extract.ts

137 lines
4 KiB
TypeScript
Raw Normal View History

import * as datasourcePypi from '../../datasource/pypi';
2020-05-01 16:03:48 +00:00
import { logger } from '../../logger';
import { SkipReason } from '../../types';
2020-05-01 16:03:48 +00:00
import { exec } from '../../util/exec';
import { BinarySource } from '../../util/exec/common';
import { isSkipComment } from '../../util/ignore';
import { ExtractConfig, PackageDependency, PackageFile } from '../common';
import { dependencyPattern } from '../pip_requirements/extract';
import { PythonSetup, copyExtractFile, parseReport } from './util';
2018-11-15 17:42:01 +00:00
export const pythonVersions = ['python', 'python3', 'python3.8'];
let pythonAlias: string | null = null;
2018-11-15 17:42:01 +00:00
export function resetModule(): void {
pythonAlias = null;
}
export function parsePythonVersion(str: string): number[] {
const arr = str.split(' ')[1].split('.');
return [parseInt(arr[0], 10), parseInt(arr[1], 10)];
}
export async function getPythonAlias(): Promise<string> {
if (pythonAlias) {
return pythonAlias;
}
pythonAlias = pythonVersions[0]; // fallback to 'python'
for (const pythonVersion of pythonVersions) {
try {
const { stdout, stderr } = await exec(`${pythonVersion} --version`);
const version = parsePythonVersion(stdout || stderr);
if (version[0] >= 3 && version[1] >= 7) {
pythonAlias = pythonVersion;
}
} catch (err) {
logger.debug(`${pythonVersion} alias not found`);
}
}
return pythonAlias;
}
export async function extractSetupFile(
_content: string,
packageFile: string,
config: ExtractConfig
): Promise<PythonSetup> {
2018-11-15 17:42:01 +00:00
const cwd = config.localDir;
let cmd = 'python';
const extractPy = await copyExtractFile();
const args = [`"${extractPy}"`, `"${packageFile}"`];
if (config.binarySource !== BinarySource.Docker) {
logger.debug('Running python via global command');
cmd = await getPythonAlias();
2018-11-15 17:42:01 +00:00
}
logger.debug({ cmd, args }, 'python command');
const res = await exec(`${cmd} ${args.join(' ')}`, {
cwd,
timeout: 30000,
docker: {
image: 'renovate/pip',
},
});
if (res.stderr) {
const stderr = res.stderr
.replace(/.*\n\s*import imp/, '')
.trim()
.replace('fatal: No names found, cannot describe anything.', '');
if (stderr.length) {
logger.warn({ stdout: res.stdout, stderr }, 'Error in read setup file');
}
2018-11-15 17:42:01 +00:00
}
return parseReport();
2018-11-15 17:42:01 +00:00
}
export async function extractPackageFile(
content: string,
packageFile: string,
config: ExtractConfig
): Promise<PackageFile | null> {
2018-11-15 17:42:01 +00:00
logger.debug('pip_setup.extractPackageFile()');
let setup: PythonSetup;
2018-11-15 17:42:01 +00:00
try {
setup = await extractSetupFile(content, packageFile, config);
} catch (err) {
logger.warn({ err, content, packageFile }, 'Failed to read setup.py file');
2018-11-15 17:42:01 +00:00
return null;
}
const requires: string[] = [];
2018-11-15 17:42:01 +00:00
if (setup.install_requires) {
requires.push(...setup.install_requires);
}
if (setup.extras_require) {
for (const req of Object.values(setup.extras_require)) {
requires.push(...req);
}
}
const regex = new RegExp(`^${dependencyPattern}`);
const lines = content.split('\n');
const deps = requires
.map((req) => {
const lineNumber = lines.findIndex((l) => l.includes(req));
2018-11-15 17:42:01 +00:00
if (lineNumber === -1) {
return null;
}
const rawline = lines[lineNumber];
let dep: PackageDependency = {};
const [, comment] = rawline.split('#').map((part) => part.trim());
2018-11-15 17:42:01 +00:00
if (isSkipComment(comment)) {
dep.skipReason = SkipReason.Ignored;
2018-11-15 17:42:01 +00:00
}
regex.lastIndex = 0;
const matches = regex.exec(req);
if (!matches) {
return null;
}
const [, depName, , currentValue] = matches;
dep = {
...dep,
depName,
currentValue,
2019-07-29 06:07:34 +00:00
managerData: { lineNumber },
datasource: datasourcePypi.id,
2018-11-15 17:42:01 +00:00
};
return dep;
})
.filter(Boolean)
.sort((a, b) =>
2019-07-29 06:07:34 +00:00
a.managerData.lineNumber === b.managerData.lineNumber
2019-08-28 04:46:48 +00:00
? a.depName.localeCompare(b.depName)
2019-07-29 06:07:34 +00:00
: a.managerData.lineNumber - b.managerData.lineNumber
);
2018-11-15 17:42:01 +00:00
if (!deps.length) {
return null;
}
return { deps };
}