# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-## Copyright 2017-2022 Canonical Ltd.## This program is free software; you can redistribute it and/or# modify it under the terms of the GNU Lesser General Public# License version 3 as published by the Free Software Foundation.## This program is distributed in the hope that it will be useful,# but WITHOUT ANY WARRANTY; without even the implied warranty of# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU# Lesser General Public License for more details.## You should have received a copy of the GNU Lesser General Public License# along with this program. If not, see <http://www.gnu.org/licenses/>."""Helpers to invoke step execution handlers from the command line."""importjsonimportloggingimportosimportsyslogger=logging.getLogger(__name__)
[docs]classCraftCtl:"""Client for the craft-parts ctl protocol. Craftctl is used to execute built-in step handlers and to get and set variables in the running part's processor context. """
[docs]@classmethoddefrun(cls,cmd:str,args:list[str])->str|None:"""Handle craftctl commands. :param cmd: The command to handle. :param args: Command arguments. :raises RuntimeError: If the command is not handled. """ifcmdin["default","set"]:_client(cmd,args)returnNoneifcmdin"get":return_client(cmd,args)raiseRuntimeError(f"invalid command {cmd!r}")
def_client(cmd:str,args:list[str])->str|None:"""Execute a command in the running step processor. The control protocol client allows a user scriptlet to execute the default handler for a step in the running application context, or set the value of a custom variable previously passed as an argument to :class:`craft_parts.LifecycleManager`. :param cmd: The command to execute in the step processor. :param args: Optional arguments. :raise RuntimeError: If the command is invalid. """try:call_fifo=os.environ["PARTS_CALL_FIFO"]feedback_fifo=os.environ["PARTS_FEEDBACK_FIFO"]exceptKeyErroraserr:raiseRuntimeError(f"{err!s} environment variable must be defined.\nNote that this "f"utility is designed for use only in part scriptlets.")fromerrdata={"function":cmd,"args":args}withopen(call_fifo,"w")asfifo:fifo.write(json.dumps(data))withopen(feedback_fifo)asfifo:feedback=fifo.readline().split(" ",1)# response from server is in the form "<status> <message>" where# <status> can be either "OK" or "ERR". Previous server versions# used an empty response as success, anything else was an error# message.status=feedback[0]message=feedback[1].strip()iflen(feedback)>1else""retval=Noneifstatus=="OK":# command has succeededretval=messageelifstatus=="ERR":# command has failedraiseRuntimeError(message)returnretval