#!/usr/bin/python3.5
|
#
|
# CDDL HEADER START
|
#
|
# The contents of this file are subject to the terms of the
|
# Common Development and Distribution License (the "License").
|
# You may not use this file except in compliance with the License.
|
#
|
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
# or http://www.opensolaris.org/os/licensing.
|
# See the License for the specific language governing permissions
|
# and limitations under the License.
|
#
|
# When distributing Covered Code, include this CDDL HEADER in each
|
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
# If applicable, add the following below this CDDL HEADER, with the
|
# fields enclosed by brackets "[]" replaced with your own identifying
|
# information: Portions Copyright [yyyy] [name of copyright owner]
|
#
|
# CDDL HEADER END
|
#
|
# This is all very naive and will hurt pythonists' eyes.
|
#
|
|
import json
|
import os
|
import re
|
import subprocess
|
import warnings
|
from urllib.request import urlopen
|
|
from .component import Component
|
|
|
class Keywords(object):
|
def __init__(self):
|
self.variables = {
|
"BUILD_BITS":
|
["NO_ARCH",
|
"32",
|
"64",
|
"32_and_64",
|
"64_and_32"],
|
"BUILD_STYLE":
|
["ant",
|
"attpackagemake",
|
"cmake",
|
"configure",
|
"gem",
|
"justmake",
|
"makemaker",
|
"meson",
|
"ocaml",
|
"setup.py",
|
"waf"],
|
"MK_BITS":
|
["NO_ARCH",
|
"32",
|
"64",
|
"32_and_64"],
|
"COMPONENT_NAME": [],
|
"COMPONENT_VERSION": [],
|
"COMPONENT_REVISION": [],
|
"COMPONENT_FMRI": [],
|
"COMPONENT_CLASSIFICATION": [],
|
"COMPONENT_SUMMARY": [],
|
"COMPONENT_PROJECT_URL": [],
|
"COMPONENT_SRC": ["$(COMPONENT_NAME)-$(COMPONENT_VERSION)"],
|
"COMPONENT_ARCHIVE": [],
|
"COMPONENT_ARCHIVE_URL": [],
|
"COMPONENT_ARCHIVE_HASH": [],
|
"COMPONENT_LICENSE": [],
|
"COMPONENT_LICENSE_FILE": []
|
}
|
self.targets = {
|
"build": [ "BUILD_$(MK_BITS)"],
|
"install": ["INSTALL_$(MK_BITS)"],
|
"test": ["TEST_$(MK_BITS)", "NO_TESTS"],
|
"system-test": ["SYSTEM_TEST_$(MK_BITS)", "SYSTEM_TESTS_NOT_IMPLEMENTED"]
|
}
|
|
@staticmethod
|
def assignment(name, value):
|
return name + "=" + value + "\n"
|
|
@staticmethod
|
def target_variable_assignment(name, value):
|
return Keywords.assignment(name.upper()+"_TARGET", value)
|
|
|
class Item(object):
|
def __init__(self, line=None, content=[]):
|
self.idx = line
|
self.str = content
|
for l in iter(self.str):
|
l = l.strip()
|
|
def append(self, line):
|
self.str.append(line.strip())
|
|
def extend(self, content):
|
for l in content:
|
self.append(l)
|
|
def line(self):
|
return self.idx
|
|
def length(self):
|
return len(self.str)
|
|
def value(self):
|
return "".join(self.str).replace("\n","").strip()
|
|
def set_value(self, value):
|
self.str = [ i.strip()+"\n" for i in value.split("\n") ]
|
|
def include_line(self):
|
return "include "+self.str[0]
|
|
def variable_assignment(self, variable):
|
if self.length() == 1:
|
return ["{0:<24}{1}".format(variable+"=",self.str[0])]
|
# Handle continuation lines
|
lines = ["{0:<24}{1}".format(variable+"=",self.str[0])]
|
for l in self.str[1:]:
|
lines[-1] += "\\\n"
|
lines.append("\t"+l)
|
lines[-1] += "\n"
|
return lines
|
|
def target_definition(self, target):
|
lines = ["{0:<24}{1}".format(target+":", self.str[0])]
|
for l in self.str[1:]:
|
lines.append("\t"+l)
|
return lines
|
|
|
class Makefile(object):
|
def __init__(self, path=None, debug=False):
|
self.debug = debug
|
self.path = path
|
self.component = Component()
|
self.includes = []
|
self.variables = {}
|
self.targets = {}
|
makefile = os.path.join(path, 'Makefile')
|
with open(makefile, 'r') as f:
|
self.contents = f.readlines()
|
self.update()
|
|
def update(self, contents=None):
|
self.includes = []
|
self.variables = {}
|
self.targets = {}
|
if contents is not None:
|
self.contents = contents
|
# Construct list of keywords
|
kw = Keywords()
|
|
# Variable is set
|
m = None
|
# Target is set
|
t = None
|
# Rule definition
|
d = None
|
for idx, line in enumerate(self.contents):
|
# Continuation of target line
|
if t is not None:
|
r = re.match(r"^[\s]*(.*)[\s]*([\\]?)[\s]*$", line)
|
# Concatenate
|
self.targets[t].str[0] += "\\".join(r.group(1))
|
# Check for continuation or move to definition
|
if not r.group(2):
|
d = t
|
t = None
|
continue
|
if d is not None:
|
# Concatenate
|
r = re.match(r"^[\t][\s]*(.*)[\s]*$", line)
|
# End of definition
|
if r is None:
|
d = None
|
continue
|
self.targets[d].append(r.group(1))
|
# Continuation line of variable
|
if m is not None:
|
r = re.match(r"^[\s]*(.*)[\s]*([\\]?)[\s]*$", line)
|
self.variables[m].append(r.group(1))
|
if not r.group(2):
|
m = None
|
continue
|
if re.match(r"^#", line):
|
continue
|
# Check for include
|
r = re.match(r"^include[\s]+(.*)", line)
|
if r is not None:
|
self.includes.append(Item(idx, [r.group(1)]))
|
else:
|
found = False
|
# Collect known variables
|
for k in list(kw.variables.keys()):
|
r = re.match(
|
r"^[\s]*("+k+r")[\s]*=[\s]*([^\\]*)[\s]*([\\]?)[\s]*$", line)
|
if r is not None:
|
found = True
|
v = r.group(2)
|
if v in self.variables.keys():
|
warnings.warn("Variable '"+v+"' redefined line "+idx)
|
self.variables[k] = Item(idx, [v])
|
if r.group(3):
|
m = k
|
break
|
if found is True:
|
continue
|
# Collect known targets
|
for k in list(kw.targets.keys()):
|
r = re.match(
|
"^"+k+r"[\s]*:[\s]*(.*)[\s]*([\\]?)[\s]*$", line)
|
if r is not None:
|
found = True
|
v = r.group(1)
|
if v in self.targets.keys():
|
warnings.warn("Target '"+v+"' redefined line "+idx)
|
self.targets[k] = Item(idx, [v])
|
if r.group(2):
|
t = k
|
d = None
|
else:
|
t = None
|
d = k
|
break
|
if found is True:
|
continue
|
|
def write(self):
|
with open(os.path.join(self.path, "Makefile"), 'w') as f:
|
for line in self.contents:
|
f.write(line)
|
|
def display(self):
|
print(self.path)
|
print('-' * 78)
|
if self.includes:
|
print("includes:")
|
print("---------")
|
for i in iter(self.includes):
|
print("{0:>3}: {1}".format(i.line(), i.value()))
|
print("")
|
if self.variables:
|
print("variables:")
|
print("----------")
|
for k,i in iter(sorted(self.variables.items())):
|
print("{0:>3}: {1:<24}= {2}".format(i.line(), k, i.value()))
|
print("")
|
if self.targets:
|
print("targets:")
|
print("--------")
|
for k,i in iter(sorted(self.targets.items())):
|
print("{0:>3}: {1:<24}= {2}".format(i.line(), k, i.value()))
|
print("")
|
print('-' * 78)
|
|
def run(self, targets):
|
path = self.path
|
result = []
|
|
if self.debug:
|
logger.debug('Executing \'gmake %s\' in %s', targets, path)
|
|
proc = subprocess.Popen(['gmake', '-s', targets],
|
stdout=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
cwd=path,
|
universal_newlines=True)
|
stdout, stderr = proc.communicate()
|
|
for out in stdout.splitlines():
|
result.append(out.rstrip())
|
|
if self.debug:
|
if proc.returncode != 0:
|
logger.debug('exit: %d, %s', proc.returncode, stderr)
|
|
return result
|
|
def print_value(self, name):
|
return self.run('print-value-'+name)[0]
|
|
def build_style(self):
|
return self.variables['BUILD_STYLE'].value()
|
|
def build_bits(self):
|
return self.variables['BUILD_BITS'].value()
|
|
def has_mk_include(self, name):
|
for i in iter(self.includes):
|
if re.match('^.*/'+name+'.mk$', i.value()):
|
return True
|
return False
|
|
def get_mk_include(self, name):
|
for i in iter(self.includes):
|
if re.match('^.*/'+name+'.mk$', i.value()):
|
return i
|
return None
|
|
def has_variable(self, variable):
|
return variable in self.variables
|
|
def variable(self, variable):
|
return self.variables[variable]
|
|
def remove_variable(self, variable):
|
idx = self.variable(variable).line()
|
del self.contents[idx]
|
self.update()
|
|
def set_variable(self, variable, value, line=None):
|
if not self.has_variable(variable):
|
self.variables[variable] = Item(None)
|
self.variables[variable].set_value(str(value))
|
if line is not None:
|
contents = self.contents[0:line]
|
contents.extend(self.variable_assignment(variable))
|
contents.extend(self.contents[line:])
|
self.update(contents)
|
return True
|
else:
|
idx = self.variables[variable].line()
|
oln = self.variables[variable].length()
|
self.variables[variable].set_value(str(value))
|
nln = self.variables[variable].length()
|
if idx is not None:
|
if line is None:
|
line = idx
|
if line == idx and nln == 1 and oln == 1:
|
self.contents[idx] = self.variable_assignment(variable)[0]
|
elif line <= idx:
|
contents = self.contents[0:line]
|
contents.extend(self.variable_assignment(variable))
|
contents.extend(self.contents[line:idx])
|
contents.extend(self.contents[idx+oln:])
|
self.update(contents)
|
else:
|
contents = self.contents[0:idx]
|
contents.extend(self.contents[idx+oln:line])
|
contents.extend(self.variable_assignment(variable))
|
contents.extend(self.contents[line:])
|
self.update(contents)
|
# Add variable at given line
|
elif line is not None:
|
contents = self.contents[0:line]
|
contents.extend(self.variable_assignment(variable))
|
contents.extend(self.contents[line:])
|
self.update(contents)
|
|
def set_archive_hash(self, checksum):
|
self.set_variable('COMPONENT_ARCHIVE_HASH', "sha256:"+str(checksum))
|
|
def variable_assignment(self, variable):
|
return self.variables[variable].variable_assignment(variable)
|
|
def has_target(self, target):
|
return target in self.targets
|
|
def target(self, target):
|
return self.targets[target]
|
|
def target_definition(self, target):
|
return self.targets[target].target_definition(target)
|
|
def required_packages(self):
|
return self.run("print-value-REQUIRED_PACKAGES")[0]
|
|
def uses_pypi(self):
|
# Default build style is configure
|
if not self.has_variable('BUILD_STYLE'):
|
return False
|
is_py = (self.build_style() == 'setup.py')
|
urlnone = (not self.has_variable('COMPONENT_ARCHIVE_URL'))
|
urlpipy = urlnone or (self.variable('COMPONENT_ARCHIVE_URL').value() == '$(call pypi_url)')
|
return is_py and urlpipy
|
|
def get_pypi_data(self):
|
name = self.print_value('COMPONENT_PYPI')
|
jsurl = "https://pypi.python.org/pypi/%s/json" % name
|
try:
|
f = urlopen(jsurl, data=None)
|
except HTTPError as e:
|
if e.getcode() == 404:
|
print("Unknown component '%s'" % name)
|
else:
|
printIOError(e, "Can't open PyPI JSON url %s" % jsurl)
|
return None
|
except IOError as e:
|
printIOError(e, "Can't open PyPI JSON url %s" % jsurl)
|
return None
|
content = f.read().decode("utf-8")
|
return json.loads(content)
|
|
@staticmethod
|
def value(variable):
|
return "$("+variable+")"
|
|
@staticmethod
|
def directory_variable(subdir):
|
return Makefile.value("WS_"+subdir.upper().replace("-","_"))
|
|
@staticmethod
|
def makefile_path(name):
|
return os.path.join("$(WS_MAKE_RULES)", name+".mk")
|
|
@staticmethod
|
def target_value(name, bits):
|
return Makefile.value(name.upper()+"_"+bits)
|