1485 lines
51 KiB
Python
Executable File
1485 lines
51 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
import sys
|
|
import os
|
|
import shlex
|
|
import signal
|
|
import argparse
|
|
import shutil
|
|
import tempfile
|
|
import subprocess
|
|
import errno
|
|
import fcntl
|
|
from Queue import Empty as EmptyQueue
|
|
from multiprocessing import Queue, Lock
|
|
|
|
# Entropy imports
|
|
sys.path.insert(0,'/usr/lib/entropy/libraries')
|
|
sys.path.insert(0,'/usr/lib/entropy/server')
|
|
sys.path.insert(0,'/usr/lib/entropy/client')
|
|
sys.path.insert(0,'../libraries')
|
|
sys.path.insert(0,'../server')
|
|
sys.path.insert(0,'../client')
|
|
|
|
# Entropy imports
|
|
from entropy.exceptions import PermissionDenied
|
|
from entropy.const import etpConst, etpUi, const_convert_to_unicode, \
|
|
const_get_stringtype
|
|
from entropy.output import print_info, print_error, print_warning, \
|
|
purple, darkgreen, is_stdout_a_tty, getcolor
|
|
from entropy.exceptions import InvalidAtom, SPMError
|
|
from entropy.server.interfaces import Server
|
|
|
|
import entropy.tools
|
|
import entropy.dep
|
|
|
|
# Portage imports
|
|
from _emerge.depgraph import backtrack_depgraph
|
|
from _emerge.actions import load_emerge_config, action_build
|
|
from _emerge.create_depgraph_params import create_depgraph_params
|
|
from _emerge.main import parse_opts, post_emerge, \
|
|
validate_ebuild_environment
|
|
from _emerge.stdout_spinner import stdout_spinner
|
|
import portage.versions
|
|
import portage
|
|
|
|
|
|
def get_entropy_server(community_mode):
|
|
"""
|
|
Return Entropy Server interface object.
|
|
"""
|
|
return Server(community_repo = community_mode)
|
|
|
|
def exec_cmd(args, env = None):
|
|
"""
|
|
Execute a command with given environment.
|
|
"""
|
|
pid = os.fork()
|
|
if pid == 0:
|
|
try:
|
|
if env is not None:
|
|
os.execvpe(args[0], args, env)
|
|
else:
|
|
os.execvp(args[0], args)
|
|
except Exception as exc:
|
|
entropy.tools.print_traceback()
|
|
os._exit(1)
|
|
else:
|
|
try:
|
|
rcpid, rc = os.waitpid(pid, os.P_WAIT)
|
|
except KeyboardInterrupt:
|
|
rc = 1
|
|
os.kill(pid, signal.SIGTERM)
|
|
return rc
|
|
|
|
|
|
class EntropyResourceLock(object):
|
|
"""
|
|
This class exposes a Lock-like interface for acquiring Entropy Server
|
|
resources.
|
|
"""
|
|
|
|
class NotAcquired(Exception):
|
|
""" Raised when Entropy Resource Lock cannot be acquired """
|
|
|
|
def __init__(self, entropy_server, blocking):
|
|
"""
|
|
EntropyResourceLock constructor.
|
|
|
|
@param entropy_server: Entropy Server instance
|
|
@type entropy_server: entropy.server.interfaces.Server
|
|
@param blocking: acquire lock in blocking mode?
|
|
@type blocking: bool
|
|
"""
|
|
self._entropy = entropy_server
|
|
self._blocking = blocking
|
|
self.__inside_with_stmt = 0
|
|
|
|
def acquire(self):
|
|
acquired = entropy.tools.acquire_entropy_resources_locks(self._entropy,
|
|
blocking = self._blocking)
|
|
if not acquired:
|
|
raise EntropyResourceLock.NotAcquired("unable to acquire lock")
|
|
|
|
def release(self):
|
|
entropy.tools.release_entropy_locks(self._entropy)
|
|
|
|
def __enter__(self):
|
|
"""
|
|
Acquire lock. Not thread-safe.
|
|
"""
|
|
if self.__inside_with_stmt < 1:
|
|
self.acquire()
|
|
self.__inside_with_stmt += 1
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
"""
|
|
Release lock. Not thread-safe.
|
|
"""
|
|
self.__inside_with_stmt -= 1
|
|
if self.__inside_with_stmt < 1:
|
|
self.release()
|
|
|
|
class MatterResourceLock(object):
|
|
"""
|
|
This class exposes a Lock-like interface for acquiring Matter lock file.
|
|
"""
|
|
|
|
LOCK_FILE_PATH = "/var/tmp/.matter_resource.lock"
|
|
|
|
class NotAcquired(Exception):
|
|
""" Raised when Lock cannot be acquired """
|
|
|
|
def __init__(self, blocking):
|
|
"""
|
|
MatterResourceLock constructor.
|
|
|
|
@param blocking: acquire lock in blocking mode?
|
|
@type blocking: bool
|
|
"""
|
|
self._blocking = blocking
|
|
self.__inside_with_stmt = 0
|
|
self.__lock_f = None
|
|
self.__call_lock = Lock()
|
|
|
|
def acquire(self):
|
|
"""
|
|
Acquire the lock file.
|
|
"""
|
|
file_path = MatterResourceLock.LOCK_FILE_PATH
|
|
if self._blocking:
|
|
flags = fcntl.LOCK_EX | fcntl.LOCK_NB
|
|
else:
|
|
flags = fcntl.LOCK_EX
|
|
|
|
with self.__call_lock:
|
|
if self.__lock_f is None:
|
|
self.__lock_f = open(file_path, "wb")
|
|
try:
|
|
fcntl.flock(self.__lock_f.fileno(), flags)
|
|
except IOError as err:
|
|
if err.errno not in (errno.EACCES, errno.EAGAIN,):
|
|
# ouch, wtf?
|
|
raise
|
|
raise MatterResourceLock.NotAcquired(
|
|
"unable to acquire lock")
|
|
|
|
def release(self):
|
|
with self.__call_lock:
|
|
if self.__lock_f is not None:
|
|
fcntl.flock(self.__lock_f.fileno(), fcntl.LOCK_UN)
|
|
self.__lock_f.close()
|
|
self.__lock_f = None
|
|
|
|
def __enter__(self):
|
|
"""
|
|
Acquire lock. Not thread-safe.
|
|
"""
|
|
if self.__inside_with_stmt < 1:
|
|
self.acquire()
|
|
self.__inside_with_stmt += 1
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
"""
|
|
Release lock. Not thread-safe.
|
|
"""
|
|
self.__inside_with_stmt -= 1
|
|
if self.__inside_with_stmt < 1:
|
|
self.release()
|
|
|
|
class GenericSpecFunctions(object):
|
|
|
|
def ne_string(self, x):
|
|
return x, 'raw_unicode_escape'
|
|
|
|
def ne_list(self, x):
|
|
return x
|
|
|
|
def not_none(self, x):
|
|
return x is not None
|
|
|
|
def valid_integer(self, x):
|
|
try:
|
|
int(x)
|
|
except (TypeError, ValueError,):
|
|
return False
|
|
return True
|
|
|
|
def always_valid(self, *args):
|
|
return True
|
|
|
|
def valid_path(self, x):
|
|
return os.path.lexists(x)
|
|
|
|
def valid_file(self, x):
|
|
return os.path.isfile(x)
|
|
|
|
def valid_dir(self, x):
|
|
return os.path.isdir(x)
|
|
|
|
def ve_string_open_file_read(self, x):
|
|
try:
|
|
return open(x, "r")
|
|
except (IOError, OSError):
|
|
return None
|
|
|
|
def ve_string_stripper(self, x):
|
|
return const_convert_to_unicode(x).strip()
|
|
|
|
def ve_string_splitter(self, x):
|
|
return const_convert_to_unicode(x).strip().split()
|
|
|
|
def ve_integer_converter(self, x):
|
|
return int(x)
|
|
|
|
def valid_ascii(self, x):
|
|
try:
|
|
x = str(x)
|
|
return x
|
|
except (UnicodeDecodeError, UnicodeEncodeError,):
|
|
return ''
|
|
|
|
def valid_yes_no(self, x):
|
|
return x in ("yes", "no")
|
|
|
|
def valid_path_string(self, x):
|
|
try:
|
|
os.path.split(x)
|
|
except OSError:
|
|
return False
|
|
return True
|
|
|
|
def valid_path_string_first_list_item(self, x):
|
|
if not x:
|
|
return False
|
|
myx = x[0]
|
|
try:
|
|
os.path.split(myx)
|
|
except OSError:
|
|
return False
|
|
return True
|
|
|
|
def valid_comma_sep_list(self, x):
|
|
return [y.strip() for y in \
|
|
const_convert_to_unicode(x).split(",") if y.strip()]
|
|
|
|
def valid_path_list(self, x):
|
|
return [y.strip() for y in \
|
|
const_convert_to_unicode(x).split(",") if \
|
|
self.valid_path_string(y) and y.strip()]
|
|
|
|
class MatterSpec(GenericSpecFunctions):
|
|
|
|
def vital_parameters(self):
|
|
"""
|
|
Return a list of vital .spec file parameters
|
|
|
|
@return: list of vital .spec file parameters
|
|
@rtype: list
|
|
"""
|
|
return ["packages", "repository"]
|
|
|
|
def parser_data_path(self):
|
|
"""
|
|
Return a dictionary containing parameter names as key and
|
|
dict containing keys 've' and 'cb' which values are three
|
|
callable functions that respectively do value extraction (ve),
|
|
value verification (cb) and value modding (mod).
|
|
|
|
@return: data path dictionary (see ChrootSpec code for more info)
|
|
@rtype: dict
|
|
"""
|
|
return {
|
|
'dependencies': {
|
|
'cb': self.valid_yes_no,
|
|
've': self.ve_string_stripper,
|
|
'default': "no",
|
|
},
|
|
'downgrade': {
|
|
'cb': self.valid_yes_no,
|
|
've': self.ve_string_stripper,
|
|
'default': "no",
|
|
},
|
|
'keep-going': {
|
|
'cb': self.valid_yes_no,
|
|
've': self.ve_string_stripper,
|
|
'default': "no",
|
|
},
|
|
'new-useflags': {
|
|
'cb': self.valid_yes_no,
|
|
've': self.ve_string_stripper,
|
|
'default': "no",
|
|
},
|
|
'removed-useflags': {
|
|
'cb': self.valid_yes_no,
|
|
've': self.ve_string_stripper,
|
|
'default': "no",
|
|
},
|
|
'rebuild': {
|
|
'cb': self.valid_yes_no,
|
|
've': self.ve_string_stripper,
|
|
'default': "no",
|
|
},
|
|
'spm-repository-change': {
|
|
'cb': self.valid_yes_no,
|
|
've': self.ve_string_stripper,
|
|
'default': "no",
|
|
},
|
|
'not-installed': {
|
|
'cb': self.valid_yes_no,
|
|
've': self.ve_string_stripper,
|
|
'default': "no",
|
|
},
|
|
'pkgpre': {
|
|
'cb': self.not_none,
|
|
've': self.ve_string_open_file_read,
|
|
'default': None,
|
|
},
|
|
'pkgpost': {
|
|
'cb': self.not_none,
|
|
've': self.ve_string_open_file_read,
|
|
'default': None,
|
|
},
|
|
'buildfail': {
|
|
'cb': self.not_none,
|
|
've': self.ve_string_open_file_read,
|
|
'default': None,
|
|
},
|
|
'packages': {
|
|
'cb': self.always_valid,
|
|
've': self.valid_comma_sep_list,
|
|
},
|
|
'repository': {
|
|
'cb': self.ne_string,
|
|
've': self.ve_string_stripper,
|
|
},
|
|
}
|
|
|
|
|
|
class SpecPreprocessor:
|
|
|
|
PREFIX = "%"
|
|
class PreprocessorError(Exception):
|
|
""" Error while preprocessing file """
|
|
|
|
def __init__(self, spec_file_obj):
|
|
self.__expanders = {}
|
|
self.__builtin_expanders = {}
|
|
self._spec_file_obj = spec_file_obj
|
|
self._add_builtin_expanders()
|
|
|
|
def add_expander(self, statement, expander_callback):
|
|
"""
|
|
Add Preprocessor expander.
|
|
|
|
@param statement: statement to expand
|
|
@type statement: string
|
|
@param expand_callback: one argument callback that is used to expand
|
|
given line (line is raw format). Line is already pre-parsed and
|
|
contains a valid preprocessor statement that callback can handle.
|
|
Preprocessor callback should raise SpecPreprocessor.PreprocessorError
|
|
if line is malformed.
|
|
@type expander_callback: callable
|
|
@raise KeyError: if expander is already available
|
|
@return: a raw string (containing \n and whatever)
|
|
@rtype: string
|
|
"""
|
|
return self._add_expander(statement, expander_callback, builtin = False)
|
|
|
|
def _add_expander(self, statement, expander_callback, builtin = False):
|
|
obj = self.__expanders
|
|
if builtin:
|
|
obj = self.__builtin_expanders
|
|
if statement in obj:
|
|
raise KeyError("expander %s already provided" % (statement,))
|
|
obj[SpecPreprocessor.PREFIX + statement] = \
|
|
expander_callback
|
|
|
|
def _add_builtin_expanders(self):
|
|
# import statement
|
|
self._add_expander("import", self._import_expander, builtin = True)
|
|
|
|
def _import_expander(self, line):
|
|
|
|
rest_line = line.split(" ", 1)[1].strip()
|
|
if not rest_line:
|
|
return line
|
|
|
|
spec_f = self._spec_file_obj
|
|
spec_f.seek(0)
|
|
lines = ''
|
|
try:
|
|
for line in spec_f.readlines():
|
|
# call recursively
|
|
split_line = line.split(" ", 1)
|
|
if split_line:
|
|
expander = self.__builtin_expanders.get(split_line[0])
|
|
if expander is not None:
|
|
try:
|
|
line = expander(line)
|
|
except RuntimeError as err:
|
|
raise SpecPreprocessor.PreprocessorError(
|
|
"invalid preprocessor line: %s" % (err,))
|
|
lines += line
|
|
finally:
|
|
spec_f.seek(0)
|
|
|
|
return lines
|
|
|
|
def parse(self):
|
|
|
|
content = []
|
|
spec_f = self._spec_file_obj
|
|
spec_f.seek(0)
|
|
|
|
try:
|
|
for line in spec_f.readlines():
|
|
split_line = line.split(" ", 1)
|
|
if split_line:
|
|
expander = self.__builtin_expanders.get(split_line[0])
|
|
if expander is not None:
|
|
line = expander(line)
|
|
content.append(line)
|
|
finally:
|
|
spec_f.seek(0)
|
|
|
|
final_content = []
|
|
for line in content:
|
|
split_line = line.split(" ", 1)
|
|
if split_line:
|
|
expander = self.__expanders.get(split_line[0])
|
|
if expander is not None:
|
|
line = expander(line)
|
|
final_content.append(line)
|
|
|
|
final_content = (''.join(final_content)).split("\n")
|
|
|
|
return final_content
|
|
|
|
|
|
class SpecParser:
|
|
|
|
def __init__(self, file_object):
|
|
|
|
self.file_object = file_object
|
|
self._preprocessor = SpecPreprocessor(self.file_object)
|
|
|
|
self.__plugin = MatterSpec()
|
|
self.vital_parameters = self.__plugin.vital_parameters()
|
|
self._parser_data_path = self.__plugin.parser_data_path()
|
|
|
|
def _parse_line_statement(self, line_stmt):
|
|
try:
|
|
key, value = line_stmt.split(":", 1)
|
|
except ValueError:
|
|
return None, None
|
|
key, value = key.strip(), value.strip()
|
|
return key, value
|
|
|
|
def parse(self):
|
|
mydict = {}
|
|
data = self._generic_parser()
|
|
# compact lines properly
|
|
old_key = None
|
|
for line in data:
|
|
key = None
|
|
value = None
|
|
v_key, v_value = self._parse_line_statement(line)
|
|
check_dict = self._parser_data_path.get(v_key)
|
|
if check_dict is not None:
|
|
key, value = v_key, v_value
|
|
old_key = key
|
|
elif isinstance(old_key, const_get_stringtype()):
|
|
key = old_key
|
|
value = line.strip()
|
|
if not value:
|
|
continue
|
|
# gather again... key is changed
|
|
check_dict = self._parser_data_path.get(key)
|
|
if not isinstance(check_dict, dict):
|
|
continue
|
|
value = check_dict['ve'](value)
|
|
if not check_dict['cb'](value):
|
|
continue
|
|
if key in mydict:
|
|
if isinstance(value, const_get_stringtype()):
|
|
mydict[key] += " %s" % (value,)
|
|
elif isinstance(value, list):
|
|
mydict[key] += value
|
|
else:
|
|
continue
|
|
else:
|
|
mydict[key] = value
|
|
self._validate_parse(mydict)
|
|
self._extend_parse(mydict)
|
|
return mydict.copy()
|
|
|
|
def _extend_parse(self, mydata):
|
|
"""
|
|
Extend parsed data with default values for statements with
|
|
default option available.
|
|
"""
|
|
for statement, opts in self._parser_data_path.items():
|
|
if "default" in opts and (statement not in mydata):
|
|
mydata[statement] = opts['default']
|
|
|
|
def _validate_parse(self, mydata):
|
|
for param in self.vital_parameters:
|
|
if param not in mydata:
|
|
raise ValueError(
|
|
"'%s' missing or invalid"
|
|
" '%s' parameter, it's vital. Your specification"
|
|
" file is incomplete!" % (self.file_object.name, param,)
|
|
)
|
|
|
|
def _generic_parser(self):
|
|
data = []
|
|
content = self._preprocessor.parse()
|
|
# filter comments and white lines
|
|
content = [x.strip().rsplit("#", 1)[0].strip() for x in content if \
|
|
not x.startswith("#") and x.strip()]
|
|
for line in content:
|
|
if line in data:
|
|
continue
|
|
data.append(line)
|
|
return data
|
|
|
|
|
|
class PackageBuilder(object):
|
|
"""
|
|
Portage Package builder class
|
|
"""
|
|
|
|
DEFAULT_PORTAGE_SYNC_CMD = "emerge --sync"
|
|
PORTAGE_SYNC_CMD = shlex.split(os.getenv("MATTER_PORTAGE_SYNC_CMD",
|
|
DEFAULT_PORTAGE_SYNC_CMD))
|
|
|
|
DEFAULT_OVERLAYS_SYNC_CMD = "layman -S"
|
|
OVERLAYS_SYNC_CMD = shlex.split(os.getenv("MATTER_OVERLAYS_SYNC_CMD",
|
|
DEFAULT_OVERLAYS_SYNC_CMD))
|
|
|
|
DEFAULT_PORTAGE_BUILD_ARGS = "--verbose --nospinner"
|
|
PORTAGE_BUILD_ARGS = os.getenv("MATTER_PORTAGE_BUILD_ARGS",
|
|
DEFAULT_PORTAGE_BUILD_ARGS).split()
|
|
|
|
def __init__(self, entropy_server, emerge_config, package, params,
|
|
spec_number, tot_spec, pkg_number, tot_pkgs):
|
|
self._entropy = entropy_server
|
|
self._emerge_config = emerge_config
|
|
self._package = package
|
|
self._params = params
|
|
self._spec_number = spec_number
|
|
self._tot_spec = tot_spec
|
|
self._pkg_number = pkg_number
|
|
self._tot_pkgs = tot_pkgs
|
|
self._built_packages = []
|
|
self._not_found_packages = []
|
|
self._not_installed_packages = []
|
|
self._not_merged_packages = []
|
|
|
|
@staticmethod
|
|
def _build_standard_environment(repository=None):
|
|
env = os.environ.copy()
|
|
if repository is not None:
|
|
env["MATTER_REPOSITORY_ID"] = repository
|
|
return env
|
|
|
|
@staticmethod
|
|
def setup(executable_hook_f, cwd):
|
|
|
|
# ignore exit status
|
|
subprocess.call(["env-update"])
|
|
|
|
hook_name = executable_hook_f.name
|
|
if not hook_name.endswith("/"):
|
|
# complete with current directory
|
|
hook_name = os.path.join(cwd, hook_name)
|
|
|
|
print_info("spawning pre hook: %s" % (hook_name,))
|
|
return exec_cmd([hook_name],
|
|
env = PackageBuilder._build_standard_environment())
|
|
|
|
@staticmethod
|
|
def teardown(executable_hook_f, cwd, exit_st):
|
|
hook_name = executable_hook_f.name
|
|
if not hook_name.endswith("/"):
|
|
# complete with current directory
|
|
hook_name = os.path.join(cwd, hook_name)
|
|
|
|
print_info("spawning post hook: %s, passing exit status: %d" % (
|
|
hook_name, exit_st,))
|
|
env = PackageBuilder._build_standard_environment()
|
|
env["MATTER_EXIT_STATUS"] = str(exit_st)
|
|
return exec_cmd([hook_name], env = env)
|
|
|
|
def _build_execution_header_output(self):
|
|
"""
|
|
Return a string used as stdout/stderr header text.
|
|
"""
|
|
my_str = "{%s of %s particles | %s of %s packages} " % (
|
|
darkgreen(str(self._spec_number)),
|
|
purple(str(self._tot_spec)),
|
|
darkgreen(str(self._pkg_number)),
|
|
purple(str(self._tot_pkgs)),)
|
|
return my_str
|
|
|
|
def get_built_packages(self):
|
|
"""
|
|
Return the list of successfully built packages.
|
|
"""
|
|
return self._built_packages
|
|
|
|
def get_not_found_packages(self):
|
|
"""
|
|
Return the list of packages that haven't been found in Portage.
|
|
"""
|
|
return self._not_found_packages
|
|
|
|
def get_not_installed_packages(self):
|
|
"""
|
|
Return the list of packages that haven't been found on the System.
|
|
"""
|
|
return self._not_installed_packages
|
|
|
|
def get_not_merged_packages(self):
|
|
"""
|
|
Return the list of packages that haven't been able to compile.
|
|
"""
|
|
return self._not_merged_packages
|
|
|
|
def run(self):
|
|
"""
|
|
Execute Package building action.
|
|
"""
|
|
header = self._build_execution_header_output()
|
|
print_info(
|
|
header + "spawning package build: %s" % (self._package,))
|
|
|
|
std_env = PackageBuilder._build_standard_environment(
|
|
repository=self._params["repository"])
|
|
std_env["MATTER_PACKAGE_NAME"] = self._package
|
|
print_info("MATTER_PACKAGE_NAME = %s" % (self._package,))
|
|
|
|
# run pkgpre, if any
|
|
pkgpre = self._params["pkgpre"]
|
|
if pkgpre is not None:
|
|
print_info(
|
|
"spawning pkgpre: %s, name: %s" % (pkgpre, pkgpre.name))
|
|
tmp_fd, tmp_path = tempfile.mkstemp(prefix="matter")
|
|
with os.fdopen(tmp_fd, "wb") as tmp_f:
|
|
tmp_f.write(pkgpre.read())
|
|
try:
|
|
# now execute
|
|
os.chmod(tmp_path, 0o700)
|
|
exit_st = exec_cmd([tmp_path], env = std_env)
|
|
if exit_st != 0:
|
|
return exit_st
|
|
finally:
|
|
os.remove(tmp_path)
|
|
|
|
dirs_cleanup = []
|
|
exit_st = self._run_builder(std_env, dirs_cleanup)
|
|
|
|
print_info("builder terminated, exit status: %d" % (exit_st,))
|
|
|
|
# cleanup temporary directories registered on the queue
|
|
for tmp_dir in dirs_cleanup:
|
|
self.__cleanup_dir(tmp_dir)
|
|
|
|
# run pkgpre, if any
|
|
pkgpost = self._params["pkgpost"]
|
|
if pkgpost is not None:
|
|
print_info(
|
|
"spawning --pkgpost: %s, name: %s" % (pkgpost, pkgpost.name))
|
|
tmp_fd, tmp_path = tempfile.mkstemp(prefix="matter")
|
|
with os.fdopen(tmp_fd, "wb") as tmp_f:
|
|
tmp_f.write(pkgpost.read())
|
|
try:
|
|
# now execute
|
|
os.chmod(tmp_path, 0o700)
|
|
post_exit_st = exec_cmd([tmp_path, str(exit_st)],
|
|
env = std_env)
|
|
if post_exit_st != 0:
|
|
return post_exit_st
|
|
finally:
|
|
os.remove(tmp_path)
|
|
|
|
return exit_st
|
|
|
|
def __cleanup_dir(self, tmp_dir):
|
|
if os.path.isdir(tmp_dir) \
|
|
and (not os.path.islink(tmp_dir)):
|
|
shutil.rmtree(tmp_dir, True)
|
|
|
|
def _run_builder(self, env, dirs_cleanup_queue):
|
|
"""
|
|
This method is called by _run and executes the whole package build
|
|
logic, including constraints validation given by argv parameters.
|
|
NOTE: negative errors indicate warnings that can be skipped.
|
|
"""
|
|
os.environ['ACCEPT_PROPERTIES'] = "* -interactive"
|
|
os.environ['FEATURES'] = "split-log"
|
|
os.environ['CMAKE_NO_COLOR'] = "yes"
|
|
log_dir = tempfile.mkdtemp(prefix="matter_build.",
|
|
suffix="." + self._package.replace("/", "_").lstrip("<>=~"))
|
|
dirs_cleanup_queue.append(log_dir)
|
|
os.environ["PORT_LOGDIR"] = log_dir
|
|
|
|
emerge_settings, emerge_trees, mtimedb = self._emerge_config
|
|
settings = portage.config(clone=emerge_settings)
|
|
|
|
portdb = emerge_trees[settings["ROOT"]]["porttree"].dbapi
|
|
if not portdb.frozen:
|
|
portdb.freeze()
|
|
vardb = emerge_trees[settings["ROOT"]]["vartree"].dbapi
|
|
fakedb = portage.fakedbapi(settings=settings)
|
|
|
|
# Load the most current variables from /etc/profile.env, which
|
|
# has been re-generated by the env-update call in _run()
|
|
settings.unlock()
|
|
settings.reload()
|
|
settings.regenerate()
|
|
settings.lock()
|
|
|
|
best_visible = portdb.xmatch("bestmatch-visible", self._package)
|
|
if not best_visible:
|
|
# package not found, return error
|
|
print_error("cannot match: %s, aborting" % (self._package,))
|
|
self._not_found_packages.append(self._package)
|
|
return 1
|
|
|
|
allow_not_installed = self._params['not-installed'] == "yes"
|
|
print_info("matched: %s for %s" % (best_visible, self._package,))
|
|
# now determine what's the installed version.
|
|
best_installed = portage.best(vardb.match(self._package))
|
|
if (not best_installed) and (not allow_not_installed):
|
|
# package not installed
|
|
print_error("package not installed: %s, aborting" % (
|
|
self._package,))
|
|
self._not_installed_packages.append(self._package)
|
|
return 1
|
|
if (not best_installed) and (allow_not_installed):
|
|
print_warning(
|
|
"package not installed: %s, but not-installed: yes provided" % (
|
|
self._package,))
|
|
|
|
cmp_res = -1
|
|
if best_installed:
|
|
print_info("found installed: %s for %s" % (best_installed,
|
|
self._package,))
|
|
# now compare
|
|
# -1 if best_installed is older than best_visible
|
|
# 1 if best_installed is newer than best_visible
|
|
# 0 if they are equal
|
|
cmp_res = portage.versions.pkgcmp(
|
|
portage.versions.pkgsplit(best_installed),
|
|
portage.versions.pkgsplit(best_visible))
|
|
|
|
allow_rebuild = self._params['rebuild'] == "yes"
|
|
allow_downgrade = self._params['downgrade'] == "yes"
|
|
is_rebuild = cmp_res == 0
|
|
|
|
if (cmp_res == 1) and (not allow_downgrade):
|
|
# downgrade in action and downgrade not allowed, aborting!
|
|
print_warning(
|
|
"package: %s, would be downgraded from %s to %s, aborting" % (
|
|
self._package, best_installed, best_visible,))
|
|
return 0
|
|
|
|
if (is_rebuild) and (not allow_rebuild):
|
|
# rebuild in action and rebuild not allowed, aborting!
|
|
print_warning(
|
|
"package: %s, would be rebuilt to %s, aborting" % (
|
|
self._package, best_visible,))
|
|
return 0
|
|
|
|
# at this point we can go ahead building self._package
|
|
print_info("starting to build: %s, to %s" % (self._package,
|
|
best_visible,))
|
|
|
|
if not getcolor():
|
|
portage.output.nocolor()
|
|
|
|
# non interactive properties, this is not really required
|
|
# accept-properties just sets os.environ...
|
|
builtin_args = ["--accept-properties=-interactive"]
|
|
myaction, myopts, myfiles = parse_opts(
|
|
PackageBuilder.PORTAGE_BUILD_ARGS + builtin_args + \
|
|
["="+best_visible])
|
|
if "--pretend" in myopts:
|
|
print_warning("cannot use --pretend emerge argument, you idiot")
|
|
del myopts["--pretend"]
|
|
if "--ask" in myopts:
|
|
print_warning("cannot use --ask emerge argument, you idiot")
|
|
del myopts["--ask"]
|
|
spinner = stdout_spinner()
|
|
if "--quiet" in myopts:
|
|
spinner.update = spinner.update_basic
|
|
elif "--nospinner" in myopts:
|
|
spinner.update = spinner.update_basic
|
|
if emerge_settings.get("TERM") == "dumb" or not is_stdout_a_tty():
|
|
spinner.update = spinner.update_basic
|
|
|
|
print_info("emerge args: %s" % (" ".join(sorted(myopts.keys())),))
|
|
|
|
params = create_depgraph_params(myopts, myaction)
|
|
success, graph, favorites = backtrack_depgraph(emerge_settings,
|
|
emerge_trees, myopts, params, myaction, myfiles, spinner)
|
|
|
|
if not success:
|
|
# print issues to stdout and give up
|
|
print_warning("dependencies calculation failed for %s, aborting" % (
|
|
best_visible,))
|
|
graph.display_problems()
|
|
return 0
|
|
print_info("dependency graph generated successfully")
|
|
|
|
# list of _emerge.Package.Package objects
|
|
package_queue = graph.altlist()
|
|
dep_list = [p.cpv+"::"+p.repo for p in package_queue]
|
|
|
|
# calculate dependencies, if --dependencies is not enabled
|
|
# because we have to validate it
|
|
if (self._params['dependencies'] == "no") \
|
|
and (len(package_queue) > 1):
|
|
# package is pulling in dependencies, but --dependencies is not
|
|
# enabled. need to give up
|
|
deps = ", ".join(dep_list)
|
|
print_warning(
|
|
"package %s is pulling in: %s, but --dependencies "
|
|
"not specified, aborting" % (best_visible, deps,))
|
|
return 0
|
|
|
|
# inspect use flags changes
|
|
allow_new_useflags = self._params['new-useflags'] == "yes"
|
|
allow_removed_useflags = \
|
|
self._params['removed-useflags'] == "yes"
|
|
|
|
use_flags_give_up = False
|
|
if (not allow_new_useflags) or (not allow_removed_useflags):
|
|
# checking for use flag changes
|
|
for pkg in package_queue:
|
|
# frozenset
|
|
enabled_flags = pkg.use.enabled
|
|
inst_atom = portage.best(vardb.match(pkg.slot_atom))
|
|
if not inst_atom:
|
|
# new package, ignore check
|
|
continue
|
|
installed_flags = frozenset(
|
|
vardb.aux_get(inst_atom, ["USE"])[0].split())
|
|
|
|
new_flags = enabled_flags - installed_flags
|
|
removed_flags = installed_flags - enabled_flags
|
|
|
|
if (not allow_new_useflags) and new_flags:
|
|
print_warning("ouch: %s wants these new USE flags: %s" % (
|
|
p.cpv+"::"+p.repo, " ".join(sorted(new_flags)),))
|
|
use_flags_give_up = True
|
|
if (not allow_removed_useflags) and removed_flags:
|
|
print_warning("ouch: %s has these USE flags removed: %s" % (
|
|
p.cpv+"::"+p.repo, " ".join(sorted(removed_flags)),))
|
|
use_flags_give_up = True
|
|
|
|
if use_flags_give_up:
|
|
print_warning("cannot continue due to unmet USE flags constraint")
|
|
return 0
|
|
|
|
# check the whole queue against downgrade directive
|
|
if not allow_downgrade:
|
|
allow_downgrade_give_ups = []
|
|
for pkg in package_queue:
|
|
inst_atom = portage.best(vardb.match(pkg.slot_atom))
|
|
cmp_res = -1
|
|
if inst_atom:
|
|
# -1 if inst_atom is older than pkg.cpv
|
|
# 1 if inst_atom is newer than pkg.cpv
|
|
# 0 if they are equal
|
|
cmp_res = portage.versions.pkgcmp(
|
|
portage.versions.pkgsplit(inst_atom),
|
|
portage.versions.pkgsplit(pkg.cpv))
|
|
if cmp_res > 0:
|
|
allow_downgrade_give_ups.append((inst_atom, pkg.cpv))
|
|
|
|
if allow_downgrade_give_ups:
|
|
print_warning(
|
|
"cannot continue due to package downgrade not allowed for:")
|
|
for inst_atom, avail_atom in allow_downgrade_give_ups:
|
|
print_warning(" installed: %s | wanted: %s" % (
|
|
inst_atom, avail_atom,))
|
|
return 0
|
|
|
|
changing_repo_pkgs = []
|
|
for pkg in package_queue:
|
|
wanted_repo = pkg.repo
|
|
inst_atom = portage.best(vardb.match(pkg.slot_atom))
|
|
current_repo = vardb.aux_get(inst_atom, ["repository"])[0]
|
|
if current_repo:
|
|
if current_repo != wanted_repo:
|
|
changing_repo_pkgs.append(
|
|
(pkg.cpv, current_repo, wanted_repo))
|
|
|
|
if changing_repo_pkgs:
|
|
print_warning("")
|
|
print_warning(
|
|
"Attention, packages are moving across SPM repositories:")
|
|
for pkg_atom, current_repo, wanted_repo in changing_repo_pkgs:
|
|
print_warning(" %s [%s->%s]" % (pkg_atom,
|
|
current_repo, wanted_repo,))
|
|
print_warning("")
|
|
|
|
allow_spm_repo_change = self._params['spm-repository-change'] == "yes"
|
|
if changing_repo_pkgs and (not allow_spm_repo_change):
|
|
print_warning(
|
|
"cannot continue due to unmet SPM repository change constraint")
|
|
return 0
|
|
|
|
print_info("USE flags constraints are met for all the queued packages")
|
|
print_info("about to build the following packages:")
|
|
for dep in dep_list:
|
|
print_info(" %s" % (dep,))
|
|
|
|
# re-calling action_build(), deps are re-calculated though
|
|
validate_ebuild_environment(emerge_trees)
|
|
retval = action_build(emerge_settings, emerge_trees, mtimedb,
|
|
myopts, myaction, myfiles, spinner)
|
|
|
|
not_merged = []
|
|
package_queue_map = dict((pkg.cpv, pkg) for pkg in package_queue)
|
|
failed_package = None
|
|
if retval != 0:
|
|
merge_list = mtimedb.get("resume", {}).get("mergelist")
|
|
for merge_type, merge_root, merge_atom, merge_act in merge_list:
|
|
if failed_package is None:
|
|
# we consider the first encountered package the one
|
|
# that failed. It makes sense since packages are built
|
|
# serially as of today.
|
|
# Also, the package object must be available in our
|
|
# package queue, so grab it from there.
|
|
failed_package = package_queue_map.get(merge_atom)
|
|
not_merged.append(merge_atom)
|
|
self._not_merged_packages.append(merge_atom)
|
|
|
|
for pkg in package_queue:
|
|
cpv = pkg.cpv
|
|
if cpv not in not_merged:
|
|
# add to build queue
|
|
print_info("package: %s, successfully built" % (cpv,))
|
|
self._built_packages.append(cpv)
|
|
|
|
post_emerge(myaction, myopts, myfiles, emerge_settings["ROOT"],
|
|
emerge_trees, mtimedb, retval)
|
|
|
|
subprocess.call(["env-update"])
|
|
|
|
if failed_package is not None:
|
|
print_warning("failed package: %s::%s" % (failed_package.cpv,
|
|
failed_package.repo,))
|
|
|
|
if self._params['buildfail'] and (failed_package is not None):
|
|
|
|
std_env = PackageBuilder._build_standard_environment(
|
|
repository=self._params["repository"])
|
|
std_env["MATTER_PACKAGE_NAME"] = self._package
|
|
std_env["MATTER_PORTAGE_FAILED_PACKAGE_NAME"] = failed_package.cpv
|
|
std_env["MATTER_PORTAGE_REPOSITORY"] = failed_package.repo
|
|
# call pkgfail hook if defined
|
|
std_env["MATTER_PORTAGE_BUILD_LOG_DIR"] = os.path.join(log_dir,
|
|
"build")
|
|
|
|
buildfail = self._params['buildfail']
|
|
print_info(
|
|
"spawning buildfail: %s, name: %s" % (buildfail,
|
|
buildfail.name))
|
|
tmp_fd, tmp_path = tempfile.mkstemp(prefix="matter")
|
|
with os.fdopen(tmp_fd, "wb") as tmp_f:
|
|
tmp_f.write(buildfail.read())
|
|
try:
|
|
# now execute
|
|
os.chmod(tmp_path, 0o700)
|
|
exit_st = exec_cmd([tmp_path], env = std_env)
|
|
if exit_st != 0:
|
|
return exit_st
|
|
finally:
|
|
os.remove(tmp_path)
|
|
|
|
print_info("portage spawned, return value: %d" % (retval,))
|
|
return retval
|
|
|
|
@staticmethod
|
|
def sync():
|
|
"""
|
|
Execute Portage and Overlays sync
|
|
"""
|
|
sync_cmd = PackageBuilder.PORTAGE_SYNC_CMD
|
|
std_env = PackageBuilder._build_standard_environment()
|
|
rc = exec_cmd(sync_cmd, env = std_env)
|
|
if rc != 0:
|
|
return rc
|
|
|
|
# overlays update
|
|
overlay_cmd = PackageBuilder.OVERLAYS_SYNC_CMD
|
|
return exec_cmd(overlay_cmd, env = std_env)
|
|
|
|
@staticmethod
|
|
def check_preserved_libraries(emerge_config):
|
|
"""
|
|
Ask portage whether there are preserved libraries on the system.
|
|
This usually indicates that Entropy packages should not be really
|
|
committed.
|
|
|
|
@param emerge_config: tuple returned by load_emerge_config(),
|
|
-> (emerge_settings, emerge_trees, mtimedb)
|
|
@type emerge_config: tuple
|
|
@return: True, if preserved libraries are found
|
|
@rtype: bool
|
|
"""
|
|
emerge_settings, emerge_trees, mtimedb = emerge_config
|
|
vardb = emerge_trees[emerge_settings["ROOT"]]["vartree"].dbapi
|
|
vardb._plib_registry.load()
|
|
return vardb._plib_registry.hasEntries()
|
|
|
|
@staticmethod
|
|
def commit(entropy_server, repository, packages):
|
|
"""
|
|
Commit packages to Entropy repository.
|
|
"""
|
|
spm = entropy_server.Spm()
|
|
spm_atoms = set()
|
|
exit_st = 0
|
|
|
|
print_info("committing packages: %s, to repository: %s" % (
|
|
", ".join(sorted(packages)), repository,))
|
|
|
|
# if we get here, something has been compiled
|
|
# successfully
|
|
for package in packages:
|
|
try:
|
|
spm_atom = spm.match_installed_package(package)
|
|
spm_atoms.add(spm_atom)
|
|
except InvalidAtom:
|
|
exit_st = 1
|
|
print_warning(
|
|
"cannot find installed package: %s" % (
|
|
package,))
|
|
continue
|
|
|
|
if not spm_atoms:
|
|
return exit_st
|
|
|
|
print_info("about to commit:")
|
|
spm_packages = sorted(spm_atoms)
|
|
|
|
for atom in spm_packages:
|
|
item_txt = atom
|
|
|
|
# this is a spm atom
|
|
spm_key = entropy.dep.dep_getkey(atom)
|
|
try:
|
|
spm_slot = spm.get_installed_package_metadata(
|
|
atom, "SLOT")
|
|
spm_repo = spm.get_installed_package_metadata(
|
|
atom, "repository")
|
|
except KeyError:
|
|
spm_slot = None
|
|
spm_repo = None
|
|
|
|
etp_repo = None
|
|
if spm_repo is not None:
|
|
pkg_id, repo_id = entropy_server.atom_match(spm_key,
|
|
match_slot = spm_slot)
|
|
if repo_id != 1:
|
|
repo_db = entropy_server.open_repository(repo_id)
|
|
etp_repo = repo_db.retrieveSpmRepository(pkg_id)
|
|
|
|
if (etp_repo is not None) and (etp_repo != spm_repo):
|
|
item_txt += ' [%s {%s=>%s}]' % ("warning",
|
|
etp_repo, spm_repo,)
|
|
|
|
print_info(item_txt)
|
|
|
|
# always stuff new configuration files here
|
|
# if --gentle was specified, the uncommitted stuff here belongs
|
|
# to our packages.
|
|
# if --gentle was NOT specified, we just don't give a shit
|
|
uncommitted = entropy_server._check_config_file_updates()
|
|
if uncommitted:
|
|
subprocess.call("echo -5 | etc-update", shell = True)
|
|
# test again
|
|
uncommitted = entropy_server._check_config_file_updates()
|
|
if uncommitted:
|
|
# ouch, wtf? better aborting
|
|
print_error("tried to commit configuration file changes and failed")
|
|
return 1
|
|
|
|
print_info("about to compress:")
|
|
|
|
store_dir = entropy_server._get_local_store_directory(repository)
|
|
package_paths = []
|
|
for atom in spm_packages:
|
|
print_info(atom)
|
|
try:
|
|
pkg_list = spm.generate_package(atom, store_dir)
|
|
except OSError:
|
|
entropy.tools.print_traceback()
|
|
print_error("problem during package generation, aborting")
|
|
return 1
|
|
except SPMError:
|
|
entropy.tools.print_traceback()
|
|
print_error("problem during package generation (2), aborting")
|
|
return 1
|
|
package_paths.append(pkg_list)
|
|
|
|
etp_pkg_files = [(pkg_list, False) for pkg_list in package_paths]
|
|
# NOTE: any missing runtime dependency will be added
|
|
# (beside those blacklisted), since this execution is not interactive
|
|
package_ids = entropy_server.add_packages_to_repository(
|
|
repository, etp_pkg_files, ask = False)
|
|
if package_ids:
|
|
# checking dependencies and print issues
|
|
entropy_server.dependencies_test(repository)
|
|
entropy_server.close_repositories()
|
|
|
|
return exit_st
|
|
|
|
@staticmethod
|
|
def push(entropy_server, repository):
|
|
"""
|
|
Push staged packages in repository to online Entropy mirrors.
|
|
"""
|
|
rc = PackageBuilder._push_packages(entropy_server, repository)
|
|
if rc != 0:
|
|
return rc
|
|
rc = PackageBuilder._push_repository(entropy_server, repository)
|
|
return rc
|
|
|
|
@staticmethod
|
|
def _push_packages(entropy_server, repository):
|
|
"""
|
|
Upload newly built packages.
|
|
"""
|
|
mirrors_tainted, mirrors_errors, successfull_mirrors, \
|
|
broken_mirrors, check_data = \
|
|
entropy_server.Mirrors.sync_packages(
|
|
repository, ask = False, pretend = False)
|
|
if mirrors_errors and not successfull_mirrors:
|
|
return 1
|
|
return 0
|
|
|
|
@staticmethod
|
|
def _push_repository(entropy_server, repository):
|
|
"""
|
|
Update remote repository.
|
|
"""
|
|
sts = entropy_server.Mirrors.sync_repository(repository)
|
|
return sts
|
|
|
|
|
|
def matter_main(entropy_server, nsargs, cwd, specs):
|
|
"""
|
|
Main application code run after all the resources setup.
|
|
"""
|
|
exit_st = 0
|
|
|
|
emerge_config = load_emerge_config()
|
|
preserved_libs = PackageBuilder.check_preserved_libraries(
|
|
emerge_config)
|
|
if preserved_libs:
|
|
print_error(
|
|
"preserved libraries are found on system, aborting.")
|
|
raise SystemExit(7)
|
|
|
|
if nsargs.gentle:
|
|
# check if there is something to do
|
|
to_be_added, to_be_removed, to_be_injected = \
|
|
entropy_server.scan_package_changes()
|
|
if to_be_added: # only check this, others we can ignore
|
|
to_be_added = [x[0] for x in to_be_added]
|
|
to_be_added.sort()
|
|
print_error("--gentle specified, and unstaged packages found:")
|
|
for name in to_be_added:
|
|
print_warning(" " + name)
|
|
raise SystemExit(5)
|
|
|
|
# also check for uncommitted configuration files changed
|
|
problems = entropy_server._check_config_file_updates()
|
|
if problems:
|
|
print_error(
|
|
"some configuration files have to be merged manually")
|
|
raise SystemExit(6)
|
|
|
|
print_info("matter loaded, starting to scan particles, pid: %s" % (
|
|
os.getpid(),))
|
|
|
|
# setup
|
|
if nsargs.pre:
|
|
rc = PackageBuilder.setup(nsargs.pre, cwd)
|
|
if rc != 0:
|
|
exit_st = rc
|
|
|
|
if exit_st == 0:
|
|
|
|
if nsargs.sync:
|
|
rc = PackageBuilder.sync()
|
|
if rc != 0:
|
|
exit_st = rc
|
|
|
|
if exit_st == 0:
|
|
completed = []
|
|
not_found = []
|
|
not_installed = []
|
|
not_merged = []
|
|
tainted_repositories = set()
|
|
preserved_libs_error = False
|
|
spec_count = 0
|
|
tot_spec = len(specs)
|
|
for spec in specs:
|
|
|
|
spec_count += 1
|
|
keep_going = spec["keep-going"] == "yes"
|
|
local_completed = []
|
|
|
|
pkg_count = 0
|
|
tot_pkgs = len(spec['packages'])
|
|
for package in spec['packages']:
|
|
pkg_count += 1
|
|
builder = PackageBuilder(entropy_server, emerge_config,
|
|
package, spec, spec_count, tot_spec, pkg_count,
|
|
tot_pkgs)
|
|
rc = builder.run()
|
|
not_found.extend(builder.get_not_found_packages())
|
|
not_installed.extend(
|
|
builder.get_not_installed_packages())
|
|
not_merged.extend(
|
|
builder.get_not_merged_packages())
|
|
preserved_libs = \
|
|
PackageBuilder.check_preserved_libraries(emerge_config)
|
|
if preserved_libs:
|
|
# abort, library breakages detected
|
|
exit_st = 1
|
|
print_error(
|
|
"preserved libraries detected, aborting")
|
|
preserved_libs_error = True
|
|
break
|
|
|
|
if rc == 0:
|
|
built_packages = builder.get_built_packages()
|
|
print_info("built packages, in queue: %s" % (
|
|
" ".join(built_packages),))
|
|
# make some room
|
|
print_info("")
|
|
local_completed.extend([x for x in built_packages \
|
|
if x not in local_completed])
|
|
tainted_repositories.add(spec['repository'])
|
|
elif rc < 0:
|
|
# ignore warning and go ahead
|
|
print_info("")
|
|
continue
|
|
else:
|
|
print_info("")
|
|
exit_st = rc
|
|
if not keep_going:
|
|
break
|
|
|
|
if preserved_libs_error:
|
|
# completely abort
|
|
break
|
|
|
|
completed.extend(local_completed)
|
|
# portage calls setcwd()
|
|
os.chdir(cwd)
|
|
|
|
if local_completed and nsargs.commit:
|
|
rc = PackageBuilder.commit(entropy_server,
|
|
spec['repository'], local_completed)
|
|
if exit_st == 0 and rc != 0:
|
|
exit_st = rc
|
|
if not keep_going:
|
|
break
|
|
|
|
if tainted_repositories and nsargs.push and nsargs.commit \
|
|
and not preserved_libs_error:
|
|
for repository in tainted_repositories:
|
|
rc = PackageBuilder.push(entropy_server,
|
|
repository)
|
|
if exit_st == 0 and rc != 0:
|
|
exit_st = rc
|
|
|
|
# print summary
|
|
print_info("")
|
|
print_info("Summary")
|
|
print_info("Packages built: %s" % (
|
|
" ".join(completed),))
|
|
print_info("Packages not built: %s" % (
|
|
" ".join(not_merged),))
|
|
print_info("Packages not found: %s" % (
|
|
" ".join(not_found),))
|
|
print_info("Packages not installed: %s" % (
|
|
" ".join(not_installed),))
|
|
print_info("")
|
|
|
|
if nsargs.post:
|
|
rc = PackageBuilder.teardown(nsargs.post, cwd,
|
|
exit_st)
|
|
if exit_st == 0 and rc != 0:
|
|
exit_st = rc
|
|
|
|
raise SystemExit(exit_st)
|
|
|
|
if __name__ == "__main__":
|
|
|
|
ENV_VARS_HELP = """\
|
|
|
|
Environment variables for Package Builder module:
|
|
%s = repository identifier
|
|
%s = alternative command used to sync Portage
|
|
default: %s
|
|
%s = alternative command used to sync Portage overlays
|
|
default: %s
|
|
%s = custom emerge arguments
|
|
default: %s
|
|
|
|
Environment variables passed to --post executables:
|
|
%s = exit status from previous execution phases, useful for detecting
|
|
execution errors.
|
|
|
|
Matter Resources Lock file you can use to detect if matter is running:
|
|
%s (--blocking switch makes it acquire in blocking mode)
|
|
""" % (
|
|
purple("MATTER_REPOSITORY_ID"),
|
|
purple("MATTER_PORTAGE_SYNC_CMD"),
|
|
darkgreen(PackageBuilder.DEFAULT_PORTAGE_SYNC_CMD),
|
|
purple("MATTER_OVERLAYS_SYNC_CMD"),
|
|
darkgreen(PackageBuilder.DEFAULT_OVERLAYS_SYNC_CMD),
|
|
purple("MATTER_PORTAGE_BUILD_ARGS"),
|
|
darkgreen(PackageBuilder.DEFAULT_PORTAGE_BUILD_ARGS),
|
|
purple("MATTER_EXIT_STATUS"),
|
|
darkgreen(MatterResourceLock.LOCK_FILE_PATH),)
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description='Automated Packages Builder',
|
|
epilog=ENV_VARS_HELP,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
|
|
# * instead of + in order to support --sync only tasks
|
|
parser.add_argument("spec", nargs='+', metavar="<spec>", type=file,
|
|
help="matter spec file")
|
|
|
|
parser.add_argument("--blocking",
|
|
help="when trying to acquire Entropy Server locks, block until success",
|
|
action="store_true")
|
|
|
|
parser.add_argument("--commit",
|
|
help="commit built packages to repository",
|
|
action="store_true")
|
|
|
|
parser.add_argument("--community",
|
|
help="enforce Community Repository mode on Entropy Server",
|
|
action="store_true")
|
|
|
|
parser.add_argument("--debug",
|
|
help="print debug output",
|
|
action="store_true")
|
|
|
|
parser.add_argument("--gentle",
|
|
help="do not run if staged packages are present in Entropy repository",
|
|
action="store_true")
|
|
|
|
parser.add_argument("--pre", metavar="<exec>", type=file,
|
|
help="executable to be called once for setup purposes",
|
|
default=None)
|
|
|
|
parser.add_argument("--post", metavar="<exec>", type=file,
|
|
help="executable to be called once for teardown purposes",
|
|
default=None)
|
|
|
|
parser.add_argument("--push",
|
|
help="push entropy package updates to online repository (only if --commit)",
|
|
action="store_true")
|
|
|
|
parser.add_argument("--sync",
|
|
help="sync Portage tree, and attached overlays, before starting",
|
|
action="store_true")
|
|
|
|
try:
|
|
nsargs = parser.parse_args(sys.argv[1:])
|
|
except IOError as err:
|
|
if err.errno == errno.ENOENT:
|
|
print_error(err.strerror + ": " + err.filename)
|
|
raise SystemExit(1)
|
|
raise
|
|
|
|
if os.getuid() != 0:
|
|
# root access required
|
|
print_error("superuser access required")
|
|
raise SystemExit(1)
|
|
|
|
if nsargs.community:
|
|
etpConst['community']['mode'] = True
|
|
|
|
if nsargs.debug:
|
|
print_warning(repr(nsargs))
|
|
# if just one, drop from argv, so that it doesn't interfere with
|
|
# entropy --debug support
|
|
if sys.argv.count("--debug") < 2:
|
|
etpUi['debug'] = False
|
|
|
|
# parse spec files
|
|
specs = []
|
|
for spec_f in nsargs.spec:
|
|
spec = SpecParser(spec_f)
|
|
data = spec.parse()
|
|
if data:
|
|
specs.append(data)
|
|
|
|
if not specs:
|
|
print_error("invalid spec files provided")
|
|
raise SystemExit(1)
|
|
|
|
entropy_server = None
|
|
exit_st = 0
|
|
cwd = os.getcwd()
|
|
# This application doesn't like implicit inteactivity
|
|
etpUi['interactive'] = False
|
|
|
|
try:
|
|
try:
|
|
entropy_server = get_entropy_server(etpConst['community']['mode'])
|
|
except PermissionDenied:
|
|
# repository not available or not configured
|
|
print_error("no valid server-side repositories configured")
|
|
raise SystemExit(3)
|
|
|
|
# validate repository entries of spec metadata
|
|
avail_repos = entropy_server.repositories()
|
|
for spec in specs:
|
|
if spec["repository"] not in avail_repos:
|
|
print_error("invalid repository %s" % (spec["repository"],))
|
|
raise SystemExit(10)
|
|
|
|
if nsargs.blocking:
|
|
print_info("--blocking enabled, please wait for locks...")
|
|
|
|
with EntropyResourceLock(entropy_server, nsargs.blocking):
|
|
with MatterResourceLock(nsargs.blocking):
|
|
matter_main(entropy_server, nsargs, cwd, specs)
|
|
|
|
except EntropyResourceLock.NotAcquired:
|
|
print_error("unable to acquire Entropy Resources lock")
|
|
raise SystemExit(42)
|
|
except MatterResourceLock.NotAcquired:
|
|
print_error("unable to acquire Matter Resources lock")
|
|
raise SystemExit(42)
|
|
except KeyboardInterrupt:
|
|
print_error("Keyboard Interrupt, pid: %s" % (os.getpid(),))
|
|
raise SystemExit(100)
|
|
finally:
|
|
if entropy_server is not None:
|
|
entropy_server.shutdown()
|
|
|
|
print_warning("")
|
|
print_warning("")
|
|
print_warning("Tasks complete, exit status: %d" % (exit_st,))
|
|
raise SystemExit(exit_st)
|