mirror of
https://github.com/github/codeql.git
synced 2025-12-17 09:13:20 +01:00
137 lines
4.1 KiB
Python
137 lines
4.1 KiB
Python
import copy
|
|
import tempfile
|
|
import re
|
|
from packaging.requirements import Requirement
|
|
from packaging.version import Version
|
|
from packaging.specifiers import SpecifierSet
|
|
|
|
IGNORED_REQUIREMENTS = re.compile("^(-e\\s+)?(git|svn|hg)(?:\\+.*)?://.*$")
|
|
|
|
def parse(lines):
|
|
'Parse a list of requirement strings into a list of `Requirement`s'
|
|
res = []
|
|
#Process
|
|
for line in lines:
|
|
if '#' in line:
|
|
line, _ = line.split('#', 1)
|
|
if not line:
|
|
continue
|
|
if IGNORED_REQUIREMENTS.match(line):
|
|
continue
|
|
try:
|
|
req = Requirement(line)
|
|
except:
|
|
print("Cannot parse requirements line '%s'" % line)
|
|
else:
|
|
res.append(req)
|
|
return res
|
|
|
|
def parse_file(filename):
|
|
with open(filename, 'r') as fd:
|
|
return parse(fd.read().splitlines())
|
|
|
|
def save_to_file(reqs):
|
|
'Takes a list of requirements, saves them to a temporary file and returns the filename'
|
|
with tempfile.NamedTemporaryFile(prefix="semmle-requirements", suffix=".txt", mode="w", delete=False) as fd:
|
|
for req in reqs:
|
|
if req.url is None:
|
|
fd.write(str(req))
|
|
else:
|
|
fd.write(req.url)
|
|
fd.write("\n")
|
|
return fd.name
|
|
|
|
def clean(reqs):
|
|
'Look for self-contradictory specifier groups and remove the necessary specifier parts to make them consistent'
|
|
result = []
|
|
for req in reqs:
|
|
specs = req.specifier
|
|
cleaned_specs = _clean_specs(specs)
|
|
req.specifier = cleaned_specs
|
|
result.append(Requirement(str(req)))
|
|
req.specifier = specs
|
|
return result
|
|
|
|
def _clean_specs(specs):
|
|
ok = SpecifierSet()
|
|
#Choose a deterministic order such that >= comes before <=.
|
|
for spec in sorted(iter(specs), key=str, reverse=True):
|
|
for ok_spec in ok:
|
|
if not _compatible_specifier(ok_spec, spec):
|
|
break
|
|
else:
|
|
ok &= SpecifierSet(str(spec))
|
|
return ok
|
|
|
|
def restrict(reqs):
|
|
'''Restrict versions to "compatible" versions.
|
|
For example restrict >=1.2 to all versions >= 1.2 that have 1 as the major version number.
|
|
>=N... becomes >=N...,==N.* and >N... requirements becomes >N..,==N.*
|
|
'''
|
|
#First of all clean the requirements
|
|
reqs = clean(reqs)
|
|
result = []
|
|
for req in reqs:
|
|
specs = req.specifier
|
|
req.specifier = _restrict_specs(specs)
|
|
result.append(Requirement(str(req)))
|
|
req.specifier = specs
|
|
return result
|
|
|
|
def _restrict_specs(specs):
|
|
restricted = copy.deepcopy(specs)
|
|
#Iteration order doesn't really matter here so we choose the
|
|
#same as for clean, just to be consistent
|
|
for spec in sorted(iter(specs), key=str, reverse=True):
|
|
if spec.operator in ('>', '>='):
|
|
base_version = spec.version.split(".", 1)[0]
|
|
restricted &= SpecifierSet('==' + base_version + '.*')
|
|
return restricted
|
|
|
|
def _compatible_specifier(s1, s2):
|
|
overlaps = 0
|
|
overlaps += _min_version(s1) in s2
|
|
overlaps += _max_version(s1) in s2
|
|
overlaps += _min_version(s2) in s1
|
|
overlaps += _max_version(s2) in s1
|
|
if overlaps > 1:
|
|
return True
|
|
if overlaps == 1:
|
|
#One overlap -- Generally compatible, but not for <x, >=x
|
|
return not _is_strict(s1) and not _is_strict(s2)
|
|
#overlaps == 0:
|
|
return False
|
|
|
|
MIN_VERSION = Version('0.0a0')
|
|
MAX_VERSION = Version('1000000')
|
|
|
|
def _min_version(s):
|
|
if s.operator in ('>', '>='):
|
|
return s.version
|
|
elif s.operator in ('<', '<=', '!='):
|
|
return MIN_VERSION
|
|
elif s.operator == '==':
|
|
v = s.version
|
|
if v[-1] == '*':
|
|
return v[:-1] + '0'
|
|
else:
|
|
return s.version
|
|
else:
|
|
# '~='
|
|
return s.version
|
|
|
|
def _max_version(s):
|
|
if s.operator in ('<', '<='):
|
|
return s.version
|
|
elif s.operator in ('>', '>=', '!='):
|
|
return MAX_VERSION
|
|
elif s.operator in ('~=', '=='):
|
|
v = s.version
|
|
if v[-1] == '*' or s.operator == '~=':
|
|
return v[:-1] + '1000000'
|
|
else:
|
|
return s.version
|
|
|
|
def _is_strict(s):
|
|
return s.operator in ('>', '<')
|