Skip to content

Commit 5701037

Browse files
committed
Add python scripts
1 parent 52274f1 commit 5701037

3 files changed

Lines changed: 199 additions & 0 deletions

File tree

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
import os
5+
import subprocess
6+
from tempfile import mkdtemp
7+
8+
import extractor_version
9+
10+
11+
def _check_call(command):
12+
print('+ {}'.format(' '.join(command)), flush=True)
13+
subprocess.check_call(command, stdin=subprocess.DEVNULL)
14+
15+
16+
def _check_output(command):
17+
print('+ {}'.format(' '.join(command)), flush=True)
18+
out = subprocess.check_output(command, stdin=subprocess.DEVNULL)
19+
print(out, flush=True)
20+
sys.stderr.flush()
21+
return out
22+
23+
24+
def install_packages_with_poetry():
25+
try:
26+
_check_call(['poetry', 'install', '--no-root'])
27+
except subprocess.CalledProcessError:
28+
sys.exit('package installation with poetry failed, see error above')
29+
30+
# poetry is super annoying with `poetry run`, since it will put lots of output on
31+
# STDOUT if the current global python interpreter is not matching the one in the
32+
# virtualenv for the package, which was the case for using poetry for Python 2 when
33+
# default system interpreter was Python 3 :/
34+
35+
poetry_out = _check_output(['poetry', 'run', 'which', 'python'])
36+
python_executable_path = poetry_out.decode('utf-8').splitlines()[-1]
37+
38+
return python_executable_path
39+
40+
41+
def install_packages_with_pipenv():
42+
try:
43+
_check_call(['pipenv', 'install', '--keep-outdated', '--ignore-pipfile'])
44+
except subprocess.CalledProcessError:
45+
sys.exit('package installation with pipenv failed, see error above')
46+
47+
pipenv_out = _check_output(['pipenv', 'run', 'which', 'python'])
48+
python_executable_path = pipenv_out.decode('utf-8').splitlines()[-1]
49+
50+
return python_executable_path
51+
52+
53+
def install_requirements_txt_packages(version: int, requirements_txt_path: str):
54+
# create temporary directory ... that just lives "forever"
55+
venv_path = mkdtemp(prefix='codeql-action-python-autoinstall-')
56+
57+
# virtualenv is a bit nicer for setting up virtual environment, since it will provide
58+
# up-to-date versions of pip/setuptools/wheel which basic `python3 -m venv venv` won't
59+
60+
if version == 2:
61+
_check_call(['python2', '-m', 'virtualenv', venv_path])
62+
elif version == 3:
63+
_check_call(['python3', '-m', 'virtualenv', venv_path])
64+
65+
venv_pip = os.path.join(venv_path, 'bin', 'pip')
66+
try:
67+
_check_call([venv_pip, 'install', '-r', requirements_txt_path])
68+
except subprocess.CalledProcessError:
69+
sys.exit('package installation with pip failed, see error above')
70+
71+
venv_python = os.path.join(venv_path, 'bin', 'python')
72+
73+
return venv_python
74+
75+
76+
def install_packages() -> str:
77+
if os.path.exists('poetry.lock'):
78+
print('Found poetry.lock, will install packages with poetry', flush=True)
79+
return install_packages_with_poetry()
80+
81+
if os.path.exists('Pipfile') or os.path.exists('Pipfile.lock'):
82+
if os.path.exists('Pipfile.lock'):
83+
print('Found Pipfile.lock, will install packages with Pipenv', flush=True)
84+
else:
85+
print('Found Pipfile, will install packages with Pipenv', flush=True)
86+
return install_packages_with_pipenv()
87+
88+
version = extractor_version.get_extractor_version(sys.argv[1], quiet=False)
89+
90+
if os.path.exists('requirements.txt'):
91+
print('Found requirements.txt, will install packages with pip', flush=True)
92+
return install_requirements_txt_packages(version, 'requirements.txt')
93+
94+
print("was not able to install packages automatically", flush=True)
95+
96+
97+
if __name__ == "__main__":
98+
if len(sys.argv) != 2:
99+
sys.exit('Must provide base directory for codeql tool as only argument')
100+
101+
# The binaries for packages installed with `pip install --user` are not available on
102+
# PATH by default, so we need to manually add them.
103+
os.environ['PATH'] = os.path.expanduser('~/.local/bin') + os.pathsep + os.environ['PATH']
104+
105+
python_executable_path = install_packages()
106+
107+
if python_executable_path is not None:
108+
print("Setting CODEQL_PYTHON={}".format(python_executable_path))
109+
print("::set-env name=CODEQL_PYTHON::{}".format(python_executable_path))
110+
111+
# TODO:
112+
# - no packages
113+
# - poetry without version
114+
# - pipenv without version
115+
# - pipenv without lockfile

python-setup/extractor_version.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/usr/bin/env python
2+
3+
# A quick hack to get package installation for Code Scanning to work,
4+
# since it needs to know which version we're going to analyze the project as.
5+
6+
# This file needs to be placed next to `python_tracer.py`, so in
7+
# `<codeql-path>/python/tools/`
8+
9+
from __future__ import print_function, division
10+
11+
import os
12+
import sys
13+
from contextlib import contextmanager
14+
15+
16+
@contextmanager
17+
def suppress_stdout_stderr():
18+
# taken from
19+
# https://thesmithfam.org/blog/2012/10/25/temporarily-suppress-console-output-in-python/
20+
with open(os.devnull, "w") as devnull:
21+
old_stdout = sys.stdout
22+
old_stderr = sys.stderr
23+
sys.stdout = devnull
24+
sys.stderr = devnull
25+
try:
26+
yield
27+
finally:
28+
sys.stdout = old_stdout
29+
sys.stderr = old_stderr
30+
31+
32+
def get_extractor_version(codeql_base_dir: str, quiet: bool = True) -> int:
33+
34+
extractor_dir = os.path.join(codeql_base_dir, 'python', 'tools')
35+
sys.path = [extractor_dir] + sys.path
36+
37+
from python_tracer import getzipfilename
38+
39+
zippath = os.path.join(extractor_dir, getzipfilename())
40+
sys.path = [zippath] + sys.path
41+
import buildtools.discover
42+
43+
if quiet:
44+
with suppress_stdout_stderr():
45+
return buildtools.discover.get_version()
46+
else:
47+
return buildtools.discover.get_version()
48+
49+
50+
if __name__ == "__main__":
51+
codeql_base_dir = sys.argv[1]
52+
version = get_extractor_version(codeql_base_dir)
53+
print('{!r}'.format(version))

python-setup/install_tools.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/bin/sh
2+
set -x
3+
set -e
4+
5+
# The binaries for packages installed with `pip install --user` are not available on PATH
6+
# by default, so we fix up PATH to suppress warnings by pip. This also needs to be done by
7+
# any script that needs to access poetry/pipenv.
8+
#
9+
# Using `::add-path::` from the actions toolkit is not enough, since that only affects
10+
# subsequent actions in the current job, and not the current action.
11+
export PATH="$HOME/.local/bin:$PATH"
12+
13+
python2 -m pip install --user --upgrade pip setuptools wheel
14+
python3 -m pip install --user --upgrade pip setuptools wheel
15+
16+
# virtualenv is a bit nicer for setting up virtual environment, since it will provide up-to-date versions of
17+
# pip/setuptools/wheel which basic `python3 -m venv venv` won't
18+
python2 -m pip install --user virtualenv
19+
python3 -m pip install --user virtualenv
20+
21+
# venv is required for installation of poetry or pipenv (I forgot which)
22+
sudo apt-get install -y python3-venv
23+
24+
# We're install poetry with pip instead of the recommended way, since the recommended way
25+
# caused some problem since `poetry run` gives output like:
26+
#
27+
# /root/.poetry/lib/poetry/_vendor/py2.7/subprocess32.py:149: RuntimeWarning: The _posixsubprocess module is not being used. Child process reliability may suffer if your program uses threads.
28+
# "program uses threads.", RuntimeWarning)
29+
# LGTM_PYTHON_SETUP_VERSION=The currently activated Python version 2.7.18 is not supported by the project (^3.5). Trying to find and use a compatible version. Using python3 (3.8.2) 3
30+
31+
python3 -m pip install --user poetry pipenv

0 commit comments

Comments
 (0)