mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
9f5ca81401
Now in the pipeline.yml you can specify lists of analysis that are run sequentially.
186 lines
6.2 KiB
Python
186 lines
6.2 KiB
Python
#
|
|
# This file is distributed under the MIT License. See LICENSE.md for details.
|
|
#
|
|
|
|
import asyncio
|
|
import json
|
|
import sys
|
|
from graphlib import TopologicalSorter
|
|
from typing import Awaitable, Callable, Iterable, List, Tuple
|
|
|
|
import aiohttp
|
|
import yaml
|
|
from aiohttp import ClientSession, ClientTimeout, ServerDisconnectedError
|
|
from gql import Client, gql
|
|
from gql.client import AsyncClientSession
|
|
from gql.transport.aiohttp import AIOHTTPTransport
|
|
|
|
from revng.pipeline_description import Artifacts, YamlLoader # type: ignore
|
|
|
|
from .daemon_handler import DaemonHandler
|
|
|
|
Runner = Callable[[AsyncClientSession], Awaitable[None]]
|
|
|
|
|
|
async def run_on_daemon(handler: DaemonHandler, runners: Iterable[Runner]):
|
|
await handler.wait_for_start()
|
|
await check_server_up(handler.url)
|
|
|
|
connector, address = get_connection(handler.url)
|
|
transport = AIOHTTPTransport(
|
|
f"http://{address}/graphql/",
|
|
client_session_args={"connector": connector, "timeout": ClientTimeout()},
|
|
)
|
|
async with Client(
|
|
transport=transport, fetch_schema_from_transport=True, execute_timeout=None
|
|
) as client:
|
|
try:
|
|
for runner in runners:
|
|
await runner(client)
|
|
except ServerDisconnectedError:
|
|
log("Server disconnected, aborting")
|
|
return 1
|
|
|
|
return 0
|
|
|
|
|
|
def upload_file(executable_path: str):
|
|
async def runner(client: AsyncClientSession):
|
|
upload_q = gql(
|
|
"""
|
|
mutation upload($file: Upload!) {
|
|
uploadFile(file: $file, container: "input")
|
|
}"""
|
|
)
|
|
|
|
with open(executable_path, "rb") as binary_file:
|
|
await client.execute(upload_q, variable_values={"file": binary_file}, upload_files=True)
|
|
log("Upload complete")
|
|
|
|
return runner
|
|
|
|
|
|
def run_analysis_lists(analysis_lists: List[str]):
|
|
async def runner(client: AsyncClientSession):
|
|
q = gql("""{ pipelineDescription }""")
|
|
description_req = await client.execute(q)
|
|
description = yaml.load(description_req["pipelineDescription"], Loader=YamlLoader)
|
|
|
|
list_names = [al.Name for al in description.AnalysesLists]
|
|
|
|
for list_name in analysis_lists:
|
|
assert list_name in list_names, f"Missing analyses list {list_name}"
|
|
|
|
log(f"Running analyses list {list_name}")
|
|
index = (await client.execute(gql("{ index: contextCommitIndex }")))["index"]
|
|
q = gql(
|
|
"mutation {"
|
|
+ f'runAnalysesList(name: "{list_name}", index: "{index}")'
|
|
+ "{ __typename ... on SimpleError { errorType message } } }"
|
|
)
|
|
res = await client.execute(q)
|
|
|
|
if res["runAnalysesList"]["__typename"] != "Diff":
|
|
log(f"Analysis failed: {json.dumps(res, indent=2)}")
|
|
assert False
|
|
|
|
return runner
|
|
|
|
|
|
def produce_artifacts(filter_: List[str] | None = None):
|
|
async def runner(client: AsyncClientSession):
|
|
q = gql("""{ pipelineDescription contextCommitIndex }""")
|
|
req = await client.execute(q)
|
|
description = yaml.load(req["pipelineDescription"], Loader=YamlLoader)
|
|
index = req["contextCommitIndex"]
|
|
|
|
if filter_ is None:
|
|
filtered_steps = list(description.Steps)
|
|
else:
|
|
filtered_steps = [
|
|
step
|
|
for step in description.Steps
|
|
if step.Component in filter_ or step.Name == "begin"
|
|
]
|
|
|
|
steps = {step.Name: step for step in filtered_steps}
|
|
topo_sorter: TopologicalSorter = TopologicalSorter()
|
|
for step in steps.values():
|
|
if step.Parent != "":
|
|
if step.Parent in steps:
|
|
topo_sorter.add(step.Name, step.Parent)
|
|
else:
|
|
topo_sorter.add(step.Name, "begin")
|
|
|
|
for step_name in topo_sorter.static_order():
|
|
step = steps[step_name]
|
|
if step.Artifacts == Artifacts():
|
|
continue
|
|
|
|
artifacts_container = step.Artifacts.Container
|
|
artifacts_kind = step.Artifacts.Kind
|
|
|
|
q = gql(
|
|
"""
|
|
query cq($step: String!, $container: String!) {
|
|
targets(step: $step, container: $container) {
|
|
serialized
|
|
}
|
|
}"""
|
|
)
|
|
arguments = {"step": step_name, "container": artifacts_container}
|
|
res = await client.execute(q, arguments)
|
|
|
|
target_list = {
|
|
target["serialized"]
|
|
for target in res["targets"]
|
|
if target["serialized"].endswith(f":{artifacts_kind}")
|
|
}
|
|
targets = ",".join(target_list)
|
|
|
|
log(f"Producing {step_name}/{artifacts_container}/*:{artifacts_kind}")
|
|
q = gql(
|
|
"""
|
|
query($step: String!, $container: String!, $target: String!, $index: BigInt!) {
|
|
produce(step: $step, container: $container, targetList: $target, index: $index) {
|
|
__typename
|
|
... on Produced {
|
|
result
|
|
}
|
|
}
|
|
}"""
|
|
)
|
|
result = await client.execute(q, {**arguments, "target": targets, "index": index})
|
|
assert result["produce"]["__typename"] == "Produced"
|
|
json_result = json.loads(result["produce"]["result"])
|
|
assert target_list == set(json_result.keys()), "Some targets were not produced"
|
|
|
|
return runner
|
|
|
|
|
|
async def check_server_up(url: str):
|
|
connector, address = get_connection(url)
|
|
session = ClientSession(connector=connector, timeout=ClientTimeout())
|
|
for _ in range(10):
|
|
try:
|
|
async with session.get(f"http://{address}/status") as req:
|
|
if req.status == 200:
|
|
connector.close()
|
|
return
|
|
await asyncio.sleep(1.0)
|
|
except aiohttp.ClientConnectionError:
|
|
await asyncio.sleep(1.0)
|
|
connector.close()
|
|
raise ValueError()
|
|
|
|
|
|
def get_connection(url) -> Tuple[aiohttp.BaseConnector, str]:
|
|
if url.startswith("unix:"):
|
|
return (aiohttp.UnixConnector(url.replace("unix:", "", 1)), "dummy")
|
|
return (aiohttp.TCPConnector(), url)
|
|
|
|
|
|
def log(string: str):
|
|
sys.stderr.write(f"{string}\n")
|
|
sys.stderr.flush()
|