-
Notifications
You must be signed in to change notification settings - Fork 7
/
version.ts
166 lines (141 loc) · 4.98 KB
/
version.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import { IntrospectFn } from "../../../types.ts";
// The version declaratiom can diverge for each platform
export type PythonVersion = {
gitlab: string;
github: string;
};
// Find the latest stable version here:
// https://www.python.org/downloads/
// GitHub Actions determines the latest minor version
const LATEST = "3";
const WARN_USING_LATEST =
`Couldn't detect the Python version, using the latest available`;
const ERR_UNDETECTABLE_TITLE =
"Couldn't detect which Python version this project uses.";
const ERR_UNDETECTABLE_INSTRUCTIONS = `
To fix this issue, consider one of the following suggestions:
1. Adopt Pipenv
Pipenv is a tool, which is maintaned by the Python Packaging Authority, that
manages project dependencies, a local virtualenv, split dependencies between
development and production, and declares what is the Python version used in
the project.
See https://pipenv.pypa.io/
2. Adopt Poetry
Poetry is a popular alternative to Pipenv, it solves similar problems and helps
to build and publish Python packages. It also declares what Python version a
project is using.
See https://python-poetry.org/
3. Create a .python-version file
The .python-version file is used by pyenv to choose a specific Python version
for a project.
Its the easiest option, all you have to do is create a .python-version text
file with a version inside, like "3.9".
See https://github.com/pyenv/pyenv
`;
/** Search a setup.py file. If the project uses Python, it has a key
* with the Python version
* As an example:
* @see https://docs.python.org/pt-br/3.6/distutils/introduction.html
*/
const setup: IntrospectFn<string | Error> = async (context) => {
for await (const file of context.files.each("**/setup.py")) {
const setupText = await context.files.readText(file.path);
const setupVersion = Array.from(
setupText.matchAll(/python_requires="(..(?<Version>.*))"/gm),
(match) => !match.groups ? null : match.groups.Version,
)[0];
if (setupVersion) {
return setupVersion;
}
}
return Error("Can't find python version at .setup.py");
};
/**
* Search for application specific `.python-version` file from pyenv
*
* @see https://github.com/pyenv/pyenv/#choosing-the-python-version
*/
const pyenv: IntrospectFn<string | Error> = async (context) => {
for await (const file of context.files.each("**/.python-version")) {
return await context.files.readText(file.path);
}
return Error("Can't find python version at .python-version");
};
/**
* Search a Pipfile file, that have a key with the Python version, as managed
* by pipenv
*
* @see https://pipenv.pypa.io/en/latest/basics/#specifying-versions-of-python
*/
const pipfile: IntrospectFn<string | Error> = async (context) => {
for await (const file of context.files.each("**/Pipfile")) {
const pipfile = await context.files.readToml(file.path);
const version = pipfile?.requires?.python_version;
if (version) return version;
}
return Error("Can't find python version at Pipfile");
};
/**
* Search a pyproject.toml file. If the project uses Poetry, it has a key
* with the Python version
*
* @see https://python-poetry.org/docs/pyproject/#dependencies-and-dev-dependencies
*/
const poetry: IntrospectFn<string | Error> = async (context) => {
for await (const file of context.files.each("**/pyproject.toml")) {
const pyproject = await context.files.readToml(file.path);
const versionRaw: string | null = pyproject?.tool?.poetry?.dependencies
?.python;
if (versionRaw) {
// FIXME this simply removes caret and tilde from version specification
// to convert something like "^3.8" to "3.8". The correct behavior
// would be to convert it to a range with 3.8, 3.9 and 3.10
return versionRaw.replace(/[\^~]/, "");
}
}
return Error("Can't find python version at pyproject.toml");
};
/**
* Searches for the project Python version in multiple places, such as:
* - .python-version (from pyenv)
* - Pipfile (from Pipenv)
* - pyproject.toml (used by Poetry too)
* - setup.py (from Setup)
*
* If it fails to find a version definition anywhere, the next step depends
* wheter Pipelinit is running in the strict mode. It emits an error if running
* in the strict mode, otherwise it emits an warning and fallback to the latest
* stable version.
*/
export const introspect: IntrospectFn<PythonVersion | undefined> = async (
context,
) => {
const logger = context.getLogger("python");
const promises = await Promise.all([
pyenv(context),
pipfile(context),
poetry(context),
setup(context),
]);
for (const promiseResult of promises) {
if (typeof promiseResult === "string") {
return {
github: promiseResult,
gitlab: promiseResult,
};
} else {
logger.debug(promiseResult.message);
}
}
if (!context.strict) {
logger.warning(WARN_USING_LATEST);
return {
github: LATEST,
gitlab: "latest",
};
}
context.errors.add({
title: ERR_UNDETECTABLE_TITLE,
message: ERR_UNDETECTABLE_INSTRUCTIONS,
});
};