website: Add draft post about the Guix Build Coordinator.
* website/posts/drafts/building-derivations-how-complicated-can-it-be.md: New file. * website/static/blog/img/build-coordinator-architecture.dia: New file. * website/static/blog/img/build-coordinator-architecture.svg: New file.
This commit is contained in:
parent
57b2376c88
commit
94c7a50a92
|
@ -0,0 +1,238 @@
|
|||
title: Building derivations, how complicated can it be?
|
||||
date: 2021-04-23 20:00
|
||||
author: Christopher Baines
|
||||
tags: Guix Build Coordinator, Continuous integration, Quality Assurance
|
||||
---
|
||||
|
||||
[Derivations][derivations] are key to Guix, they're the low-level build instructions
|
||||
used for things like packages, disk images, and most things than end
|
||||
up in the store.
|
||||
|
||||
Around a year ago, the established approach to build derivations
|
||||
across multiple machines was [daemon offloading][offloading]. This
|
||||
offloading approach is mostly static in terms of the machines involved
|
||||
and uses SSH to communicate and move things between machines.
|
||||
|
||||
[derivations]: https://guix.gnu.org/manual/en/html_node/Derivations.html
|
||||
[offloading]: https://guix.gnu.org/manual/en/html_node/Daemon-Offload-Setup.html
|
||||
|
||||
The Guix Build Coordinator project set out to provide an alternative
|
||||
approach, both to explore what's possible, but also to provide a
|
||||
usable tool to address two specific use cases.
|
||||
|
||||
The first use case was building things (mostly packages) for the
|
||||
purpose of providing substitutes. At the time, the daemon offloading
|
||||
approach used on ci.guix.gnu.org which is the default source of
|
||||
substitutes. This approach was not scaling particularly well, so there
|
||||
was room for improvement.
|
||||
|
||||
The second use case was more aspirational, support various quality
|
||||
assurance tasks, like building packages changed by patches, regularly
|
||||
testing fixed output derivations, or building the same derivations
|
||||
across different machines to test for hardware specific differences.
|
||||
|
||||
While both these tasks have quite a lot in common, there's still quite
|
||||
a lot of differences, this in part led to a lot of flexibility in the
|
||||
design of the Guix Build Coordinator.
|
||||
|
||||
# Architecture
|
||||
|
||||
Like offloading, the Guix Build Coordinator works in a centralised
|
||||
manner. There's one coordinator process which manages state, and agent
|
||||
processes run on the machines that perform builds. The coordinator
|
||||
plans which builds to allocate to which agents, and agents ask the
|
||||
coordinator for builds, which they then attempt.
|
||||
|
||||
Once agents complete a build, they send the log file and any outputs
|
||||
back to the coordinator. This is shown in the diagram below. Note that
|
||||
the Guix Build Coordinator doesn't actually take care of building the
|
||||
individual derivations, that's still left to `guix-daemon`'s on the
|
||||
machines involved.
|
||||
|
||||
![Guix Build Coordinator sequence diagram](/static/blog/img/build-coordinator-architecture.svg)
|
||||
|
||||
The builds to perform are worked through methodically, a build won't
|
||||
start unless all the inputs are available. This behaviour replicates
|
||||
what `guix-daemon` does, but across all the machines involved.
|
||||
|
||||
If agents can't setup to perform a build, they report this back to the
|
||||
coordinator, which may then perform other builds to produce those
|
||||
required inputs.
|
||||
|
||||
Currently HTTP is used when agents want to communicate to the
|
||||
coordinator, although additional approaches could be implemented in
|
||||
the future. Similarly, SQLite is used as the database, but from the
|
||||
start there has been a plan to support PostgreSQL, but that's yet to
|
||||
be implemented.
|
||||
|
||||
# Comparison to offloading
|
||||
|
||||
There's lots more to the internal workings of the Guix Build
|
||||
Coordinator, but how does this compare to the daemon offloading
|
||||
builds?
|
||||
|
||||
Starting from the outside and working in, the API for the Guix Build
|
||||
Coordinator is all asynchronous. When you submit a build, you get an
|
||||
ID back, which you can use to track the progress of that build. This
|
||||
is in contrast to the way the daemon is used, where you keep a
|
||||
connection established while builds are progressing.
|
||||
|
||||
Offloading is tightly integrated with the daemon, which can be both
|
||||
useful as offloading can transparently apply to anything that would
|
||||
otherwise be built locally. However, this can also be a limitation
|
||||
since the daemon is one of the harder components to modify.
|
||||
|
||||
With offloading, `guix-daemon` reaches out to another machine, copies
|
||||
over all the inputs and the derivation, and then starts the
|
||||
build. Rather than doing this, the Guix Build Coordinator agent pulls
|
||||
in the inputs and derivation using substitutes.
|
||||
|
||||
This pull approach has a few advantages, firstly it removes the need
|
||||
to keep a large store on the machine running the coordinator, and this
|
||||
large store requirement of using offloading became a problem in terms
|
||||
of scalability for the offloading approach. Another advantage is that
|
||||
it makes deploying agents easier, as they don't need to be reachable
|
||||
from the coordinator over the network, which can be an issue with NATs
|
||||
or virtual machines.
|
||||
|
||||
When offloading builds, the outputs would be copied back to the store
|
||||
on build success. Instead, the Guix Build Coordinator agent sends the
|
||||
outputs back as nar files. The coordinator would then process these
|
||||
nar files to make substitutes available. This helps distribute the
|
||||
work in generating the nar files, which can be quite expensive.
|
||||
|
||||
These differences may be subtle, but the architecture makes a big
|
||||
difference, it's much easier to store and serve nars at scale if this
|
||||
doesn't require a large store managed by a single guix-daemon.
|
||||
|
||||
There's also quite a few things in common with the daemon offloading
|
||||
approach. Builds are still methodically performed across multiple
|
||||
machines, and load is taken in to account when starting new builds.
|
||||
|
||||
# A basic example
|
||||
|
||||
Getting the Guix Build Coordinator up and running does require some
|
||||
work, the following commands should get the coordinator and an agent
|
||||
running on a single machine. First, you start the coordinator.
|
||||
|
||||
```sh
|
||||
guix-build-coordinator
|
||||
```
|
||||
|
||||
Then in another terminal, you interact with the running coordinator to
|
||||
create an agent in it's database.
|
||||
|
||||
```sh
|
||||
guix-build-coordinator agent new
|
||||
```
|
||||
|
||||
Note the UUID of the generated agent.
|
||||
|
||||
```sh
|
||||
guix-build-coordinator agent <AGENT ID> password new
|
||||
```
|
||||
|
||||
Note the generated password for the agent. With the UUID and password,
|
||||
the agent can then be started.
|
||||
|
||||
```sh
|
||||
guix-build-coordinator-agent --uuid=<AGENT ID> --password=<AGENT PASSWORD>
|
||||
```
|
||||
|
||||
At this point, both processes should be running and the
|
||||
guix-build-coordinator should be logging requests from the agent.
|
||||
|
||||
In a third terminal, also at the root of the repository, generate a
|
||||
derivation, and then instruct the coordinator to have it built.
|
||||
|
||||
```sh
|
||||
guix build --no-grafts -d hello
|
||||
```
|
||||
|
||||
Note the derivation that is output.
|
||||
|
||||
```sh
|
||||
guix-build-coordinator build <DERIVATION FILE>
|
||||
```
|
||||
|
||||
This will return a randomly generated UUID that represents the build.
|
||||
|
||||
While running from the command line is useful for development and
|
||||
experimentation, there are [services in Guix for running the
|
||||
coordinator and agents][services].
|
||||
|
||||
[services]: https://guix.gnu.org/en/manual/en/html_node/Guix-Services.html#Guix-Build-Coordinator
|
||||
|
||||
# Applications of the Guix Build Coordinator
|
||||
|
||||
While I think the Guix Build Coordinator is a better choice than
|
||||
daemon offloading in some circumstances, it doesn't currently replace
|
||||
it.
|
||||
|
||||
At a high level, the Guix Build Coordinator is useful where there's a
|
||||
need to build derivations and do something with the outputs or build
|
||||
results, more than just having the outputs in the local store. This
|
||||
could be serving substitutes, or testing derivations for example.
|
||||
|
||||
At small scales, the additional complexity of the coordinator is
|
||||
probably unnecessary, but when it's useful to use multiple machines,
|
||||
either because of the resources that provides, or because of a more
|
||||
diverse range of hardware, then it makes much more sense to use the
|
||||
Guix Build Coordinator to coordinate what's going on.
|
||||
|
||||
# Looking forward
|
||||
|
||||
The Guix Build Coordinator isn't just an alternative to daemon
|
||||
offloading, it's more a general toolkit for coordinating the building
|
||||
of derivations.
|
||||
|
||||
Much of the functionality in the Guix Build Coordinator happens in
|
||||
hooks. There are bits of code (hooks) that run when certain events
|
||||
happen, like a build gets submitted, or a build finished
|
||||
successfully. It's these hooks that are responsible for doing things
|
||||
like processing nars to be served as substitutes, or submitting
|
||||
retries for a failed build.
|
||||
|
||||
This hook to automatically retry building particular derivations is
|
||||
particularly useful when trying to provide substitutes where you want
|
||||
to lessen the impact of random failures, or for quality assurance
|
||||
purposes, where you want more data to better identify problems.
|
||||
|
||||
There are also more features such as build and agent tags and build
|
||||
priorities that can be really useful in some scenarios.
|
||||
|
||||
My hope is that the Guix Build Coordinator will enable a better
|
||||
substitute experience for Guix users, as well as enabling a whole new
|
||||
range of quality assurance tasks. It's already possible to see some
|
||||
impact from the Guix Build Coordinator, but there's still much work to
|
||||
do!
|
||||
|
||||
## Additional material
|
||||
|
||||
- [Guix Build Coordinator Git repository](https://git.cbaines.net/guix/build-coordinator/)
|
||||
- [2020/04/17 - Initial announcement - Prototype tool for building derivations](https://lists.gnu.org/archive/html/guix-devel/2020-04/msg00323.html)
|
||||
- [2020/09/19 - \[PATCH 0/4\] Add package and services for the Guix Build Coordinator](https://issues.guix.gnu.org/43494)
|
||||
- [2020/11/17 - Thoughts on building things for substitutes and the Guix Build Coordinator](https://lists.gnu.org/archive/html/guix-devel/2020-11/msg00417.html)
|
||||
- [2020/11/22 - Guix Days 2020 - Progress so far on the Guix Build Coordinator](https://xana.lepiller.eu/guix-days-2020/guix-days-2020-christopher-baines-guix-build-coordinator.mp4)
|
||||
- [2021/02/09 - The Guix Build Coordinator in 2021](https://lists.gnu.org/archive/html/guix-devel/2021-02/msg00148.html)
|
||||
- [2021/02/14 - Getting the Guix Build Coordinator agent working on the Hurd](https://lists.gnu.org/archive/html/guix-devel/2021-02/msg00223.html)
|
||||
- [2021/03/08 - Hurd substitute availability (27.5%) and next steps?](https://lists.gnu.org/archive/html/guix-devel/2021-03/msg00074.html)
|
||||
|
||||
#### About GNU Guix
|
||||
|
||||
[GNU Guix](https://www.gnu.org/software/guix) is a transactional package
|
||||
manager and an advanced distribution of the GNU system that [respects
|
||||
user
|
||||
freedom](https://www.gnu.org/distros/free-system-distribution-guidelines.html).
|
||||
Guix can be used on top of any system running the Hurd or the Linux
|
||||
kernel, or it can be used as a standalone operating system distribution
|
||||
for i686, x86_64, ARMv7, and AArch64 machines.
|
||||
|
||||
In addition to standard package management features, Guix supports
|
||||
transactional upgrades and roll-backs, unprivileged package management,
|
||||
per-user profiles, and garbage collection. When used as a standalone
|
||||
GNU/Linux distribution, Guix offers a declarative, stateless approach to
|
||||
operating system configuration management. Guix is highly customizable
|
||||
and hackable through [Guile](https://www.gnu.org/software/guile)
|
||||
programming interfaces and extensions to the
|
||||
[Scheme](http://schemers.org) language.
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,127 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
|
||||
<svg width="24cm" height="27cm" viewBox="1 1 470 526" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="Background">
|
||||
<g>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke-dasharray: 8; stroke: #000000" x1="48.7857" y1="38.8521" x2="48.7857" y2="85.2373"/>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke-dasharray: 8; stroke: #000000" x1="48.7857" y1="125.237" x2="48.7857" y2="155.729"/>
|
||||
<rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" x="41.7857" y="85.2373" width="14" height="40"/>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke-dasharray: 8; stroke: #000000" x1="229.745" y1="39.5549" x2="229.745" y2="86.7882"/>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke-dasharray: 8; stroke: #000000" x1="229.745" y1="476.788" x2="229.745" y2="518.438"/>
|
||||
<rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" x="222.745" y="86.7882" width="14" height="390"/>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 2; stroke: #000000" x1="69.5416" y1="85.3651" x2="222.745" y2="86.7882"/>
|
||||
<polygon style="fill: #000000; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" fill-rule="evenodd" points="69.5786,81.3789 56.7857,85.2466 69.5045,89.3513 "/>
|
||||
<text font-size="10.205" style="fill: #000000; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="139.265" y="96.0128">fetch-builds</text>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" x1="209.045" y1="126.661" x2="55.7857" y2="125.237"/>
|
||||
<polygon style="fill: #000000; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" fill-rule="evenodd" points="209.008,130.63 221.745,126.779 209.082,122.692 "/>
|
||||
<text font-size="10.1598" style="fill: #000000; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="139.265" y="136.013">(new build details)</text>
|
||||
</g>
|
||||
<g>
|
||||
<rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" x="2.01072" y="2.85208" width="93.55" height="36"/>
|
||||
<text font-size="12.7998" style="fill: #000000; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="48.7857" y="24.7521">
|
||||
<tspan x="48.7857" y="24.7521">coordinator</tspan>
|
||||
</text>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke: #000000" x1="12.0107" y1="27.8021" x2="85.5607" y2="27.8021"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" x="201.295" y="3.55488" width="56.9" height="36"/>
|
||||
<text font-size="12.7998" style="fill: #000000; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="229.745" y="25.4549">
|
||||
<tspan x="229.745" y="25.4549">agent</tspan>
|
||||
</text>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke: #000000" x1="211.295" y1="28.5049" x2="248.195" y2="28.5049"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" x="364.985" y="3.05566" width="104.1" height="36"/>
|
||||
<text font-size="12.7998" style="fill: #000000; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="417.035" y="24.9557">
|
||||
<tspan x="417.035" y="24.9557">guix-daemon</tspan>
|
||||
</text>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke: #000000" x1="374.985" y1="28.0057" x2="459.085" y2="28.0057"/>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke-dasharray: 8; stroke: #000000" x1="417.035" y1="39.0557" x2="417.035" y2="126.46"/>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke-dasharray: 8; stroke: #000000" x1="417.035" y1="166.46" x2="417.035" y2="236.427"/>
|
||||
<rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" x="410.035" y="126.46" width="14" height="40"/>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 2; stroke: #000000" x1="396.335" y1="126.486" x2="236.745" y2="126.788"/>
|
||||
<polygon style="fill: #000000; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" fill-rule="evenodd" points="396.343,130.455 409.035,126.462 396.328,122.517 "/>
|
||||
<text font-size="10.1598" style="fill: #000000; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="323.39" y="136.624">substitute derivation</text>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" x1="250.445" y1="166.762" x2="410.035" y2="166.46"/>
|
||||
<polygon style="fill: #000000; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" fill-rule="evenodd" points="250.437,162.794 237.745,166.786 250.452,170.731 "/>
|
||||
<text font-size="10.1598" style="fill: #000000; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="323.39" y="176.624">(finished)</text>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke-dasharray: 8; stroke: #000000" x1="417.178" y1="182.992" x2="417.178" y2="206.983"/>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke-dasharray: 8; stroke: #000000" x1="417.178" y1="246.983" x2="417.178" y2="276.775"/>
|
||||
<rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" x="410.178" y="206.983" width="14" height="40"/>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 2; stroke: #000000" x1="396.478" y1="206.968" x2="236.745" y2="206.788"/>
|
||||
<polygon style="fill: #000000; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" fill-rule="evenodd" points="396.473,210.936 409.178,206.982 396.482,202.999 "/>
|
||||
<text font-size="10.1598" style="fill: #000000; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="323.461" y="216.886">substitute inputs</text>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke-dasharray: 8; stroke: #000000" x1="417.089" y1="263.144" x2="417.089" y2="268.051"/>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke-dasharray: 8; stroke: #000000" x1="417.089" y1="348.051" x2="417.089" y2="374.991"/>
|
||||
<rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" x="410.089" y="268.051" width="14" height="80"/>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 2; stroke: #000000" x1="396.39" y1="267.951" x2="236.745" y2="266.788"/>
|
||||
<polygon style="fill: #000000; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" fill-rule="evenodd" points="396.361,271.92 409.089,268.044 396.419,263.983 "/>
|
||||
<text font-size="10.1598" style="fill: #000000; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="323.417" y="277.42">build derivation</text>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 2; stroke: #000000" x1="69.4856" y1="277.27" x2="222.745" y2="276.788"/>
|
||||
<polygon style="fill: #000000; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" fill-rule="evenodd" points="69.4732,273.301 56.7857,277.31 69.4981,281.239 "/>
|
||||
<text font-size="10.1598" style="fill: #000000; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="139.265" y="287.051">report build start</text>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" x1="250.444" y1="346.888" x2="410.089" y2="348.051"/>
|
||||
<polygon style="fill: #000000; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" fill-rule="evenodd" points="250.473,342.919 237.745,346.795 250.415,350.857 "/>
|
||||
<text font-size="10.1598" style="fill: #000000; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="323.417" y="357.42">build finished (successfuly)</text>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 2; stroke: #000000" x1="69.3795" y1="357.259" x2="222.745" y2="356.788"/>
|
||||
<polygon style="fill: #000000; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" fill-rule="evenodd" points="69.3673,353.29 56.6795,357.298 69.3917,361.228 "/>
|
||||
<text font-size="10.1598" style="fill: #000000; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="139.212" y="367.045">submit log file</text>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 2; stroke: #000000" x1="69.2831" y1="417.631" x2="222.745" y2="416.788"/>
|
||||
<polygon style="fill: #000000; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" fill-rule="evenodd" points="69.2613,413.662 56.5833,417.701 69.3049,421.6 "/>
|
||||
<text font-size="10.1598" style="fill: #000000; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="139.164" y="427.247">submit output(s)</text>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 2; stroke: #000000" x1="69.3372" y1="477.186" x2="223.891" y2="476.622"/>
|
||||
<polygon style="fill: #000000; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" fill-rule="evenodd" points="69.3227,473.217 56.6373,477.233 69.3517,481.155 "/>
|
||||
<text font-size="10.1598" style="fill: #000000; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="139.764" y="486.929">report build result</text>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke-dasharray: 8; stroke: #000000" x1="48.7857" y1="125.237" x2="48.7857" y2="257.313"/>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke-dasharray: 8; stroke: #000000" x1="48.7857" y1="297.313" x2="48.7857" y2="350.287"/>
|
||||
<rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" x="41.7857" y="257.313" width="14" height="40"/>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke-dasharray: 8; stroke: #000000" x1="48.6795" y1="328.801" x2="48.6795" y2="337.301"/>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke-dasharray: 8; stroke: #000000" x1="48.6795" y1="377.301" x2="48.6795" y2="400.395"/>
|
||||
<rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" x="41.6795" y="337.301" width="14" height="40"/>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke-dasharray: 8; stroke: #000000" x1="48.5833" y1="387.706" x2="48.5833" y2="397.706"/>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke-dasharray: 8; stroke: #000000" x1="48.5833" y1="437.706" x2="48.5833" y2="467.38"/>
|
||||
<rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" x="41.5833" y="397.706" width="14" height="40"/>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke-dasharray: 8; stroke: #000000" x1="48.6373" y1="447.236" x2="48.6373" y2="457.236"/>
|
||||
<line style="fill: none; stroke-opacity: 1; stroke-width: 1; stroke-dasharray: 8; stroke: #000000" x1="48.6373" y1="497.236" x2="48.6373" y2="526.91"/>
|
||||
<rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #000000" x="41.6373" y="457.236" width="14" height="40"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 12 KiB |
Loading…
Reference in New Issue