Compare commits
10 commits
da26d951cb
...
287dec65c3
| Author | SHA1 | Date | |
|---|---|---|---|
|
287dec65c3 |
|||
|
503fcbf4e2 |
|||
|
0eac066a04 |
|||
|
48ee32f3d6 |
|||
|
c57ef45679 |
|||
|
a22848532c |
|||
|
4a0db12ecb |
|||
|
58a3d68d22 |
|||
|
d652faacc2 |
|||
|
de877a01ab |
13 changed files with 825 additions and 553 deletions
32
Cargo.lock
generated
32
Cargo.lock
generated
|
|
@ -108,9 +108,9 @@ checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.48"
|
version = "4.5.51"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae"
|
checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
|
|
@ -118,9 +118,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.48"
|
version = "4.5.51"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9"
|
checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
|
|
@ -130,9 +130,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.47"
|
version = "4.5.49"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
|
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -229,14 +229,14 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "csv"
|
name = "csv"
|
||||||
version = "1.3.1"
|
version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
|
checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"csv-core",
|
"csv-core",
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -386,6 +386,12 @@ dependencies = [
|
||||||
"foldhash",
|
"foldhash",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
|
@ -406,12 +412,12 @@ checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.11.4"
|
version = "2.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
|
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown 0.16.0",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
@ -508,7 +514,7 @@ version = "0.12.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
|
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown",
|
"hashbrown 0.15.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,14 @@ rust-version = "1.85"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
anyhow = "1.0.100"
|
anyhow = "1.0.100"
|
||||||
clap = { version = "4.5.48", features = ["derive"] }
|
clap = { version = "4.5.51", features = ["derive"] }
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.145"
|
serde_json = "1.0.145"
|
||||||
serde_repr = "0.1.20"
|
serde_repr = "0.1.20"
|
||||||
crossterm = "0.29.0"
|
crossterm = "0.29.0"
|
||||||
ratatui = "0.29.0"
|
ratatui = "0.29.0"
|
||||||
indexmap = { version = "2.11.4", features = ["serde"] }
|
indexmap = { version = "2.12.0", features = ["serde"] }
|
||||||
csv = "1.3.1"
|
csv = "1.4.0"
|
||||||
thiserror = "2.0.17"
|
thiserror = "2.0.17"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
|
||||||
|
|
|
||||||
482
LICENSE
482
LICENSE
|
|
@ -1,328 +1,288 @@
|
||||||
Mozilla Public License, version 2.0
|
EUROPEAN UNION PUBLIC LICENCE v. 1.2
|
||||||
|
EUPL © the European Union 2007, 2016
|
||||||
|
|
||||||
|
This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined
|
||||||
|
below) which is provided under the terms of this Licence. Any use of the Work,
|
||||||
|
other than as authorised under this Licence is prohibited (to the extent such
|
||||||
|
use is covered by a right of the copyright holder of the Work).
|
||||||
|
|
||||||
|
The Work is provided under the terms of this Licence when the Licensor (as
|
||||||
|
defined below) has placed the following notice immediately following the
|
||||||
|
copyright notice for the Work:
|
||||||
|
|
||||||
|
Licensed under the EUPL
|
||||||
|
|
||||||
|
or has expressed by any other means his willingness to license under the EUPL.
|
||||||
|
|
||||||
1. Definitions
|
1. Definitions
|
||||||
|
|
||||||
1.1. “Contributor”
|
In this Licence, the following terms have the following meaning:
|
||||||
means each individual or legal entity that creates, contributes to the
|
|
||||||
creation of, or owns Covered Software.
|
|
||||||
|
|
||||||
1.2. “Contributor Version”
|
- ‘The Licence’: this Licence.
|
||||||
means the combination of the Contributions of others (if any) used by a
|
|
||||||
Contributor and that particular Contributor’s Contribution.
|
|
||||||
|
|
||||||
1.3. “Contribution”
|
- ‘The Original Work’: the work or software distributed or communicated by the
|
||||||
means Covered Software of a particular Contributor.
|
Licensor under this Licence, available as Source Code and also as Executable
|
||||||
|
Code as the case may be.
|
||||||
|
|
||||||
1.4. “Covered Software”
|
- ‘Derivative Works’: the works or software that could be created by the
|
||||||
means Source Code Form to which the initial Contributor has attached the
|
Licensee, based upon the Original Work or modifications thereof. This Licence
|
||||||
notice in Exhibit A, the Executable Form of such Source Code Form,
|
does not define the extent of modification or dependence on the Original Work
|
||||||
and Modifications of such Source Code Form, in each case
|
required in order to classify a work as a Derivative Work; this extent is
|
||||||
including portions thereof.
|
determined by copyright law applicable in the country mentioned in Article 15.
|
||||||
|
|
||||||
1.5. “Incompatible With Secondary Licenses”
|
- ‘The Work’: the Original Work or its Derivative Works.
|
||||||
means
|
|
||||||
|
|
||||||
a. that the initial Contributor has attached the notice described
|
- ‘The Source Code’: the human-readable form of the Work which is the most
|
||||||
in Exhibit B to the Covered Software; or
|
convenient for people to study and modify.
|
||||||
|
|
||||||
b. that the Covered Software was made available under the terms of
|
- ‘The Executable Code’: any code which has generally been compiled and which is
|
||||||
version 1.1 or earlier of the License, but not also under the terms
|
meant to be interpreted by a computer as a program.
|
||||||
of a Secondary License.
|
|
||||||
|
|
||||||
1.6. “Executable Form”
|
- ‘The Licensor’: the natural or legal person that distributes or communicates
|
||||||
means any form of the work other than Source Code Form.
|
the Work under the Licence.
|
||||||
|
|
||||||
1.7. “Larger Work”
|
- ‘Contributor(s)’: any natural or legal person who modifies the Work under the
|
||||||
means a work that combines Covered Software with other material,
|
Licence, or otherwise contributes to the creation of a Derivative Work.
|
||||||
in a separate file or files, that is not Covered Software.
|
|
||||||
|
|
||||||
1.8. “License”
|
- ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of
|
||||||
means this document.
|
the Work under the terms of the Licence.
|
||||||
|
|
||||||
1.9. “Licensable”
|
- ‘Distribution’ or ‘Communication’: any act of selling, giving, lending,
|
||||||
means having the right to grant, to the maximum extent possible,
|
renting, distributing, communicating, transmitting, or otherwise making
|
||||||
whether at the time of the initial grant or subsequently,
|
available, online or offline, copies of the Work or providing access to its
|
||||||
any and all of the rights conveyed by this License.
|
essential functionalities at the disposal of any other natural or legal
|
||||||
|
person.
|
||||||
|
|
||||||
1.10. “Modifications”
|
2. Scope of the rights granted by the Licence
|
||||||
means any of the following:
|
|
||||||
|
|
||||||
a. any file in Source Code Form that results from an addition to,
|
The Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
|
||||||
deletion from, or modification of the contents of Covered Software; or
|
sublicensable licence to do the following, for the duration of copyright vested
|
||||||
|
in the Original Work:
|
||||||
|
|
||||||
b. any new file in Source Code Form that contains any Covered Software.
|
- use the Work in any circumstance and for all usage,
|
||||||
|
- reproduce the Work,
|
||||||
|
- modify the Work, and make Derivative Works based upon the Work,
|
||||||
|
- communicate to the public, including the right to make available or display
|
||||||
|
the Work or copies thereof to the public and perform publicly, as the case may
|
||||||
|
be, the Work,
|
||||||
|
- distribute the Work or copies thereof,
|
||||||
|
- lend and rent the Work or copies thereof,
|
||||||
|
- sublicense rights in the Work or copies thereof.
|
||||||
|
|
||||||
1.11. “Patent Claims” of a Contributor
|
Those rights can be exercised on any media, supports and formats, whether now
|
||||||
means any patent claim(s), including without limitation, method, process,
|
known or later invented, as far as the applicable law permits so.
|
||||||
and apparatus claims, in any patent Licensable by such Contributor that
|
|
||||||
would be infringed, but for the grant of the License, by the making,
|
|
||||||
using, selling, offering for sale, having made, import, or transfer of
|
|
||||||
either its Contributions or its Contributor Version.
|
|
||||||
|
|
||||||
1.12. “Secondary License”
|
In the countries where moral rights apply, the Licensor waives his right to
|
||||||
means either the GNU General Public License, Version 2.0, the
|
exercise his moral right to the extent allowed by law in order to make effective
|
||||||
GNU Lesser General Public License, Version 2.1, the GNU Affero General
|
the licence of the economic rights here above listed.
|
||||||
Public License, Version 3.0, or any later versions of those licenses.
|
|
||||||
|
|
||||||
1.13. “Source Code Form”
|
The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to
|
||||||
means the form of the work preferred for making modifications.
|
any patents held by the Licensor, to the extent necessary to make use of the
|
||||||
|
rights granted on the Work under this Licence.
|
||||||
|
|
||||||
1.14. “You” (or “Your”)
|
3. Communication of the Source Code
|
||||||
means an individual or a legal entity exercising rights under this License.
|
|
||||||
For legal entities, “You” includes any entity that controls,
|
|
||||||
is controlled by, or is under common control with You. For purposes of
|
|
||||||
this definition, “control” means (a) the power, direct or indirect,
|
|
||||||
to cause the direction or management of such entity, whether by contract
|
|
||||||
or otherwise, or (b) ownership of more than fifty percent (50%) of the
|
|
||||||
outstanding shares or beneficial ownership of such entity.
|
|
||||||
|
|
||||||
2. License Grants and Conditions
|
The Licensor may provide the Work either in its Source Code form, or as
|
||||||
|
Executable Code. If the Work is provided as Executable Code, the Licensor
|
||||||
|
provides in addition a machine-readable copy of the Source Code of the Work
|
||||||
|
along with each copy of the Work that the Licensor distributes or indicates, in
|
||||||
|
a notice following the copyright notice attached to the Work, a repository where
|
||||||
|
the Source Code is easily and freely accessible for as long as the Licensor
|
||||||
|
continues to distribute or communicate the Work.
|
||||||
|
|
||||||
2.1. Grants
|
4. Limitations on copyright
|
||||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
|
||||||
non-exclusive license:
|
|
||||||
|
|
||||||
a. under intellectual property rights (other than patent or trademark)
|
Nothing in this Licence is intended to deprive the Licensee of the benefits from
|
||||||
Licensable by such Contributor to use, reproduce, make available,
|
any exception or limitation to the exclusive rights of the rights owners in the
|
||||||
modify, display, perform, distribute, and otherwise exploit its
|
Work, of the exhaustion of those rights or of other applicable limitations
|
||||||
Contributions, either on an unmodified basis, with Modifications,
|
thereto.
|
||||||
or as part of a Larger Work; and
|
|
||||||
|
|
||||||
b. under Patent Claims of such Contributor to make, use, sell,
|
5. Obligations of the Licensee
|
||||||
offer for sale, have made, import, and otherwise transfer either
|
|
||||||
its Contributions or its Contributor Version.
|
|
||||||
|
|
||||||
2.2. Effective Date
|
The grant of the rights mentioned above is subject to some restrictions and
|
||||||
The licenses granted in Section 2.1 with respect to any Contribution
|
obligations imposed on the Licensee. Those obligations are the following:
|
||||||
become effective for each Contribution on the date the Contributor
|
|
||||||
first distributes such Contribution.
|
|
||||||
|
|
||||||
2.3. Limitations on Grant Scope
|
Attribution right: The Licensee shall keep intact all copyright, patent or
|
||||||
The licenses granted in this Section 2 are the only rights granted
|
trademarks notices and all notices that refer to the Licence and to the
|
||||||
under this License. No additional rights or licenses will be implied
|
disclaimer of warranties. The Licensee must include a copy of such notices and a
|
||||||
from the distribution or licensing of Covered Software under this License.
|
copy of the Licence with every copy of the Work he/she distributes or
|
||||||
Notwithstanding Section 2.1(b) above, no patent license is granted
|
communicates. The Licensee must cause any Derivative Work to carry prominent
|
||||||
by a Contributor:
|
notices stating that the Work has been modified and the date of modification.
|
||||||
|
|
||||||
a. for any code that a Contributor has removed from
|
Copyleft clause: If the Licensee distributes or communicates copies of the
|
||||||
Covered Software; or
|
Original Works or Derivative Works, this Distribution or Communication will be
|
||||||
|
done under the terms of this Licence or of a later version of this Licence
|
||||||
|
unless the Original Work is expressly distributed only under this version of the
|
||||||
|
Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee
|
||||||
|
(becoming Licensor) cannot offer or impose any additional terms or conditions on
|
||||||
|
the Work or Derivative Work that alter or restrict the terms of the Licence.
|
||||||
|
|
||||||
b. for infringements caused by: (i) Your and any other third party’s
|
Compatibility clause: If the Licensee Distributes or Communicates Derivative
|
||||||
modifications of Covered Software, or (ii) the combination of its
|
Works or copies thereof based upon both the Work and another work licensed under
|
||||||
Contributions with other software (except as part of its
|
a Compatible Licence, this Distribution or Communication can be done under the
|
||||||
Contributor Version); or
|
terms of this Compatible Licence. For the sake of this clause, ‘Compatible
|
||||||
|
Licence’ refers to the licences listed in the appendix attached to this Licence.
|
||||||
|
Should the Licensee's obligations under the Compatible Licence conflict with
|
||||||
|
his/her obligations under this Licence, the obligations of the Compatible
|
||||||
|
Licence shall prevail.
|
||||||
|
|
||||||
c. under Patent Claims infringed by Covered Software in the
|
Provision of Source Code: When distributing or communicating copies of the Work,
|
||||||
absence of its Contributions.
|
the Licensee will provide a machine-readable copy of the Source Code or indicate
|
||||||
|
a repository where this Source will be easily and freely available for as long
|
||||||
|
as the Licensee continues to distribute or communicate the Work.
|
||||||
|
|
||||||
This License does not grant any rights in the trademarks, service marks,
|
Legal Protection: This Licence does not grant permission to use the trade names,
|
||||||
or logos of any Contributor (except as may be necessary to comply with
|
trademarks, service marks, or names of the Licensor, except as required for
|
||||||
the notice requirements in Section 3.4).
|
reasonable and customary use in describing the origin of the Work and
|
||||||
|
reproducing the content of the copyright notice.
|
||||||
|
|
||||||
2.4. Subsequent Licenses
|
6. Chain of Authorship
|
||||||
No Contributor makes additional grants as a result of Your choice to
|
|
||||||
distribute the Covered Software under a subsequent version of this
|
|
||||||
License (see Section 10.2) or under the terms of a Secondary License
|
|
||||||
(if permitted under the terms of Section 3.3).
|
|
||||||
|
|
||||||
2.5. Representation
|
The original Licensor warrants that the copyright in the Original Work granted
|
||||||
Each Contributor represents that the Contributor believes its
|
hereunder is owned by him/her or licensed to him/her and that he/she has the
|
||||||
Contributions are its original creation(s) or it has sufficient rights
|
power and authority to grant the Licence.
|
||||||
to grant the rights to its Contributions conveyed by this License.
|
|
||||||
|
|
||||||
2.6. Fair Use
|
Each Contributor warrants that the copyright in the modifications he/she brings
|
||||||
This License is not intended to limit any rights You have under
|
to the Work are owned by him/her or licensed to him/her and that he/she has the
|
||||||
applicable copyright doctrines of fair use, fair dealing,
|
power and authority to grant the Licence.
|
||||||
or other equivalents.
|
|
||||||
|
|
||||||
2.7. Conditions
|
Each time You accept the Licence, the original Licensor and subsequent
|
||||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the
|
Contributors grant You a licence to their contributions to the Work, under the
|
||||||
licenses granted in Section 2.1.
|
terms of this Licence.
|
||||||
|
|
||||||
3. Responsibilities
|
7. Disclaimer of Warranty
|
||||||
|
|
||||||
3.1. Distribution of Source Form
|
The Work is a work in progress, which is continuously improved by numerous
|
||||||
All distribution of Covered Software in Source Code Form, including
|
Contributors. It is not a finished work and may therefore contain defects or
|
||||||
any Modifications that You create or to which You contribute, must be
|
‘bugs’ inherent to this type of development.
|
||||||
under the terms of this License. You must inform recipients that the
|
|
||||||
Source Code Form of the Covered Software is governed by the terms
|
|
||||||
of this License, and how they can obtain a copy of this License.
|
|
||||||
You may not attempt to alter or restrict the recipients’ rights
|
|
||||||
in the Source Code Form.
|
|
||||||
|
|
||||||
3.2. Distribution of Executable Form
|
For the above reason, the Work is provided under the Licence on an ‘as is’ basis
|
||||||
If You distribute Covered Software in Executable Form then:
|
and without warranties of any kind concerning the Work, including without
|
||||||
|
limitation merchantability, fitness for a particular purpose, absence of defects
|
||||||
|
or errors, accuracy, non-infringement of intellectual property rights other than
|
||||||
|
copyright as stated in Article 6 of this Licence.
|
||||||
|
|
||||||
a. such Covered Software must also be made available in Source Code
|
This disclaimer of warranty is an essential part of the Licence and a condition
|
||||||
Form, as described in Section 3.1, and You must inform recipients of
|
for the grant of any rights to the Work.
|
||||||
the Executable Form how they can obtain a copy of such Source Code
|
|
||||||
Form by reasonable means in a timely manner, at a charge no more than
|
|
||||||
the cost of distribution to the recipient; and
|
|
||||||
|
|
||||||
b. You may distribute such Executable Form under the terms of this
|
8. Disclaimer of Liability
|
||||||
License, or sublicense it under different terms, provided that the
|
|
||||||
license for the Executable Form does not attempt to limit or alter
|
|
||||||
the recipients’ rights in the Source Code Form under this License.
|
|
||||||
|
|
||||||
3.3. Distribution of a Larger Work
|
Except in the cases of wilful misconduct or damages directly caused to natural
|
||||||
You may create and distribute a Larger Work under terms of Your choice,
|
persons, the Licensor will in no event be liable for any direct or indirect,
|
||||||
provided that You also comply with the requirements of this License for
|
material or moral, damages of any kind, arising out of the Licence or of the use
|
||||||
the Covered Software. If the Larger Work is a combination of
|
of the Work, including without limitation, damages for loss of goodwill, work
|
||||||
Covered Software with a work governed by one or more Secondary Licenses,
|
stoppage, computer failure or malfunction, loss of data or any commercial
|
||||||
and the Covered Software is not Incompatible With Secondary Licenses,
|
damage, even if the Licensor has been advised of the possibility of such damage.
|
||||||
this License permits You to additionally distribute such Covered Software
|
However, the Licensor will be liable under statutory product liability laws as
|
||||||
under the terms of such Secondary License(s), so that the recipient of
|
far such laws apply to the Work.
|
||||||
the Larger Work may, at their option, further distribute the
|
|
||||||
Covered Software under the terms of either this License or such
|
|
||||||
Secondary License(s).
|
|
||||||
|
|
||||||
3.4. Notices
|
9. Additional agreements
|
||||||
You may not remove or alter the substance of any license notices
|
|
||||||
(including copyright notices, patent notices, disclaimers of warranty,
|
|
||||||
or limitations of liability) contained within the Source Code Form of
|
|
||||||
the Covered Software, except that You may alter any license notices to
|
|
||||||
the extent required to remedy known factual inaccuracies.
|
|
||||||
|
|
||||||
3.5. Application of Additional Terms
|
While distributing the Work, You may choose to conclude an additional agreement,
|
||||||
You may choose to offer, and to charge a fee for, warranty, support,
|
defining obligations or services consistent with this Licence. However, if
|
||||||
indemnity or liability obligations to one or more recipients of
|
accepting obligations, You may act only on your own behalf and on your sole
|
||||||
Covered Software. However, You may do so only on Your own behalf,
|
responsibility, not on behalf of the original Licensor or any other Contributor,
|
||||||
and not on behalf of any Contributor. You must make it absolutely clear
|
and only if You agree to indemnify, defend, and hold each Contributor harmless
|
||||||
that any such warranty, support, indemnity, or liability obligation is
|
for any liability incurred by, or claims asserted against such Contributor by
|
||||||
offered by You alone, and You hereby agree to indemnify every Contributor
|
the fact You have accepted any warranty or additional liability.
|
||||||
for any liability incurred by such Contributor as a result of warranty,
|
|
||||||
support, indemnity or liability terms You offer. You may include
|
|
||||||
additional disclaimers of warranty and limitations of liability
|
|
||||||
specific to any jurisdiction.
|
|
||||||
|
|
||||||
4. Inability to Comply Due to Statute or Regulation
|
10. Acceptance of the Licence
|
||||||
|
|
||||||
If it is impossible for You to comply with any of the terms of this License
|
The provisions of this Licence can be accepted by clicking on an icon ‘I agree’
|
||||||
with respect to some or all of the Covered Software due to statute,
|
placed under the bottom of a window displaying the text of this Licence or by
|
||||||
judicial order, or regulation then You must: (a) comply with the terms of
|
affirming consent in any other similar way, in accordance with the rules of
|
||||||
this License to the maximum extent possible; and (b) describe the limitations
|
applicable law. Clicking on that icon indicates your clear and irrevocable
|
||||||
and the code they affect. Such description must be placed in a text file
|
acceptance of this Licence and all of its terms and conditions.
|
||||||
included with all distributions of the Covered Software under this License.
|
|
||||||
Except to the extent prohibited by statute or regulation, such description
|
|
||||||
must be sufficiently detailed for a recipient of ordinary skill
|
|
||||||
to be able to understand it.
|
|
||||||
|
|
||||||
5. Termination
|
Similarly, you irrevocably accept this Licence and all of its terms and
|
||||||
|
conditions by exercising any rights granted to You by Article 2 of this Licence,
|
||||||
|
such as the use of the Work, the creation by You of a Derivative Work or the
|
||||||
|
Distribution or Communication by You of the Work or copies thereof.
|
||||||
|
|
||||||
5.1. The rights granted under this License will terminate automatically
|
11. Information to the public
|
||||||
if You fail to comply with any of its terms. However, if You become
|
|
||||||
compliant, then the rights granted under this License from a particular
|
|
||||||
Contributor are reinstated (a) provisionally, unless and until such
|
|
||||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
|
||||||
ongoing basis, if such Contributor fails to notify You of the
|
|
||||||
non-compliance by some reasonable means prior to 60 days after You have
|
|
||||||
come back into compliance. Moreover, Your grants from a particular
|
|
||||||
Contributor are reinstated on an ongoing basis if such Contributor
|
|
||||||
notifies You of the non-compliance by some reasonable means,
|
|
||||||
this is the first time You have received notice of non-compliance with
|
|
||||||
this License from such Contributor, and You become compliant prior to
|
|
||||||
30 days after Your receipt of the notice.
|
|
||||||
|
|
||||||
5.2. If You initiate litigation against any entity by asserting a patent
|
In case of any Distribution or Communication of the Work by means of electronic
|
||||||
infringement claim (excluding declaratory judgment actions,
|
communication by You (for example, by offering to download the Work from a
|
||||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
remote location) the distribution channel or media (for example, a website) must
|
||||||
directly or indirectly infringes any patent, then the rights granted
|
at least provide to the public the information requested by the applicable law
|
||||||
to You by any and all Contributors for the Covered Software under
|
regarding the Licensor, the Licence and the way it may be accessible, concluded,
|
||||||
Section 2.1 of this License shall terminate.
|
stored and reproduced by the Licensee.
|
||||||
|
|
||||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
12. Termination of the Licence
|
||||||
end user license agreements (excluding distributors and resellers) which
|
|
||||||
have been validly granted by You or Your distributors under this License
|
|
||||||
prior to termination shall survive termination.
|
|
||||||
|
|
||||||
6. Disclaimer of Warranty
|
The Licence and the rights granted hereunder will terminate automatically upon
|
||||||
|
any breach by the Licensee of the terms of the Licence.
|
||||||
|
|
||||||
Covered Software is provided under this License on an “as is” basis, without
|
Such a termination will not terminate the licences of any person who has
|
||||||
warranty of any kind, either expressed, implied, or statutory, including,
|
received the Work from the Licensee under the Licence, provided such persons
|
||||||
without limitation, warranties that the Covered Software is free of defects,
|
remain in full compliance with the Licence.
|
||||||
merchantable, fit for a particular purpose or non-infringing. The entire risk
|
|
||||||
as to the quality and performance of the Covered Software is with You.
|
|
||||||
Should any Covered Software prove defective in any respect, You
|
|
||||||
(not any Contributor) assume the cost of any necessary servicing, repair,
|
|
||||||
or correction. This disclaimer of warranty constitutes an essential part of
|
|
||||||
this License. No use of any Covered Software is authorized under this
|
|
||||||
License except under this disclaimer.
|
|
||||||
|
|
||||||
7. Limitation of Liability
|
13. Miscellaneous
|
||||||
|
|
||||||
Under no circumstances and under no legal theory, whether tort
|
Without prejudice of Article 9 above, the Licence represents the complete
|
||||||
(including negligence), contract, or otherwise, shall any Contributor, or
|
agreement between the Parties as to the Work.
|
||||||
anyone who distributes Covered Software as permitted above, be liable to
|
|
||||||
You for any direct, indirect, special, incidental, or consequential damages
|
|
||||||
of any character including, without limitation, damages for lost profits,
|
|
||||||
loss of goodwill, work stoppage, computer failure or malfunction, or any and
|
|
||||||
all other commercial damages or losses, even if such party shall have been
|
|
||||||
informed of the possibility of such damages. This limitation of liability
|
|
||||||
shall not apply to liability for death or personal injury resulting from
|
|
||||||
such party’s negligence to the extent applicable law prohibits such
|
|
||||||
limitation. Some jurisdictions do not allow the exclusion or limitation of
|
|
||||||
incidental or consequential damages, so this exclusion and limitation may
|
|
||||||
not apply to You.
|
|
||||||
|
|
||||||
8. Litigation
|
If any provision of the Licence is invalid or unenforceable under applicable
|
||||||
|
law, this will not affect the validity or enforceability of the Licence as a
|
||||||
|
whole. Such provision will be construed or reformed so as necessary to make it
|
||||||
|
valid and enforceable.
|
||||||
|
|
||||||
Any litigation relating to this License may be brought only in the courts of
|
The European Commission may publish other linguistic versions or new versions of
|
||||||
a jurisdiction where the defendant maintains its principal place of business
|
this Licence or updated versions of the Appendix, so far this is required and
|
||||||
and such litigation shall be governed by laws of that jurisdiction, without
|
reasonable, without reducing the scope of the rights granted by the Licence. New
|
||||||
reference to its conflict-of-law provisions. Nothing in this Section shall
|
versions of the Licence will be published with a unique version number.
|
||||||
prevent a party’s ability to bring cross-claims or counter-claims.
|
|
||||||
|
|
||||||
9. Miscellaneous
|
All linguistic versions of this Licence, approved by the European Commission,
|
||||||
|
have identical value. Parties can take advantage of the linguistic version of
|
||||||
|
their choice.
|
||||||
|
|
||||||
This License represents the complete agreement concerning the subject matter
|
14. Jurisdiction
|
||||||
hereof. If any provision of this License is held to be unenforceable,
|
|
||||||
such provision shall be reformed only to the extent necessary to make it
|
|
||||||
enforceable. Any law or regulation which provides that the language of a
|
|
||||||
contract shall be construed against the drafter shall not be used to construe
|
|
||||||
this License against a Contributor.
|
|
||||||
|
|
||||||
10. Versions of the License
|
Without prejudice to specific agreement between parties,
|
||||||
|
|
||||||
10.1. New Versions
|
- any litigation resulting from the interpretation of this License, arising
|
||||||
Mozilla Foundation is the license steward. Except as provided in
|
between the European Union institutions, bodies, offices or agencies, as a
|
||||||
Section 10.3, no one other than the license steward has the right to
|
Licensor, and any Licensee, will be subject to the jurisdiction of the Court
|
||||||
modify or publish new versions of this License. Each version will be
|
of Justice of the European Union, as laid down in article 272 of the Treaty on
|
||||||
given a distinguishing version number.
|
the Functioning of the European Union,
|
||||||
|
|
||||||
10.2. Effect of New Versions
|
- any litigation arising between other parties and resulting from the
|
||||||
You may distribute the Covered Software under the terms of the version
|
interpretation of this License, will be subject to the exclusive jurisdiction
|
||||||
of the License under which You originally received the Covered Software,
|
of the competent court where the Licensor resides or conducts its primary
|
||||||
or under the terms of any subsequent version published
|
business.
|
||||||
by the license steward.
|
|
||||||
|
|
||||||
10.3. Modified Versions
|
15. Applicable Law
|
||||||
If you create software not governed by this License, and you want to
|
|
||||||
create a new license for such software, you may create and use a modified
|
|
||||||
version of this License if you rename the license and remove any
|
|
||||||
references to the name of the license steward (except to note that such
|
|
||||||
modified license differs from this License).
|
|
||||||
|
|
||||||
10.4. Distributing Source Code Form that is
|
Without prejudice to specific agreement between parties,
|
||||||
Incompatible With Secondary Licenses
|
|
||||||
If You choose to distribute Source Code Form that is
|
|
||||||
Incompatible With Secondary Licenses under the terms of this version of
|
|
||||||
the License, the notice described in Exhibit B of this
|
|
||||||
License must be attached.
|
|
||||||
|
|
||||||
Exhibit A - Source Code Form License Notice
|
- this Licence shall be governed by the law of the European Union Member State
|
||||||
|
where the Licensor has his seat, resides or has his registered office,
|
||||||
|
|
||||||
This Source Code Form is subject to the terms of the
|
- this licence shall be governed by Belgian law if the Licensor has no seat,
|
||||||
Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
|
residence or registered office inside a European Union Member State.
|
||||||
with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
If it is not possible or desirable to put the notice in a particular file,
|
Appendix
|
||||||
then You may include the notice in a location (such as a LICENSE file in a
|
|
||||||
relevant directory) where a recipient would be likely to
|
|
||||||
look for such a notice.
|
|
||||||
|
|
||||||
You may add additional accurate notices of copyright ownership.
|
‘Compatible Licences’ according to Article 5 EUPL are:
|
||||||
|
|
||||||
Exhibit B - “Incompatible With Secondary Licenses” Notice
|
- GNU General Public License (GPL) v. 2, v. 3
|
||||||
|
- GNU Affero General Public License (AGPL) v. 3
|
||||||
|
- Open Software License (OSL) v. 2.1, v. 3.0
|
||||||
|
- Eclipse Public License (EPL) v. 1.0
|
||||||
|
- CeCILL v. 2.0, v. 2.1
|
||||||
|
- Mozilla Public Licence (MPL) v. 2
|
||||||
|
- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
|
||||||
|
- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for
|
||||||
|
works other than software
|
||||||
|
- European Union Public Licence (EUPL) v. 1.1, v. 1.2
|
||||||
|
- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong
|
||||||
|
Reciprocity (LiLiQ-R+).
|
||||||
|
|
||||||
|
The European Commission may update this Appendix to later versions of the above
|
||||||
|
licences without producing a new version of the EUPL, as long as they provide
|
||||||
|
the rights granted in Article 2 of this Licence and protect the covered Source
|
||||||
|
Code from exclusive appropriation.
|
||||||
|
|
||||||
|
All other changes or additions to this Appendix require the production of a new
|
||||||
|
EUPL version.
|
||||||
|
|
||||||
This Source Code Form is “Incompatible With Secondary Licenses”,
|
|
||||||
as defined by the Mozilla Public License, v. 2.0.
|
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,27 @@ pub use aterm::{
|
||||||
parse_drv_file,
|
parse_drv_file,
|
||||||
};
|
};
|
||||||
pub use internal_json::{Actions, Activities, Id, Verbosity};
|
pub use internal_json::{Actions, Activities, Id, Verbosity};
|
||||||
pub use state::{BuildInfo, BuildStatus, Dependencies, Derivation, Host, OutputName, State, ProgressState};
|
pub use state::{
|
||||||
|
BuildInfo,
|
||||||
|
BuildStatus,
|
||||||
|
Dependencies,
|
||||||
|
Derivation,
|
||||||
|
Host,
|
||||||
|
OutputName,
|
||||||
|
ProgressState,
|
||||||
|
State,
|
||||||
|
};
|
||||||
|
|
||||||
/// Process a list of actions and return the resulting state
|
/// Process a list of actions and return the resulting state
|
||||||
#[must_use] pub fn process_actions(actions: Vec<Actions>) -> State {
|
#[must_use]
|
||||||
|
pub fn process_actions(actions: Vec<Actions>) -> State {
|
||||||
let mut state = State {
|
let mut state = State {
|
||||||
progress: ProgressState::JustStarted,
|
progress: ProgressState::JustStarted,
|
||||||
derivations: HashMap::new(),
|
derivations: HashMap::new(),
|
||||||
builds: HashMap::new(),
|
builds: HashMap::new(),
|
||||||
dependencies: Dependencies { deps: HashMap::new() },
|
dependencies: Dependencies {
|
||||||
|
deps: HashMap::new(),
|
||||||
|
},
|
||||||
store_paths: HashMap::new(),
|
store_paths: HashMap::new(),
|
||||||
dependency_states: HashMap::new(),
|
dependency_states: HashMap::new(),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,11 @@ pub struct State {
|
||||||
impl State {
|
impl State {
|
||||||
pub fn imbibe(&mut self, action: Actions) {
|
pub fn imbibe(&mut self, action: Actions) {
|
||||||
match action {
|
match action {
|
||||||
Actions::Start { id, activity: _activity, .. } => {
|
Actions::Start {
|
||||||
|
id,
|
||||||
|
activity: _activity,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
let derivation = Derivation {
|
let derivation = Derivation {
|
||||||
store_path: PathBuf::from("/nix/store/placeholder"),
|
store_path: PathBuf::from("/nix/store/placeholder"),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
6
flake.lock
generated
6
flake.lock
generated
|
|
@ -2,11 +2,11 @@
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1761672384,
|
"lastModified": 1765186076,
|
||||||
"narHash": "sha256-o9KF3DJL7g7iYMZq9SWgfS1BFlNbsm6xplRjVlOCkXI=",
|
"narHash": "sha256-hM20uyap1a0M9d344I692r+ik4gTMyj60cQWO+hAYP8=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "08dacfca559e1d7da38f3cf05f1f45ee9bfd213c",
|
"rev": "addf7cf5f383a3101ecfba091b98d0a1263dc9b8",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
{
|
{
|
||||||
description = "Rust Project Template";
|
inputs.nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
|
||||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
|
||||||
|
|
||||||
outputs = {
|
outputs = {
|
||||||
self,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
|
|
|
||||||
|
|
@ -254,7 +254,8 @@ pub fn run() -> eyre::Result<()> {
|
||||||
///
|
///
|
||||||
/// Everything before `--` is for the package name and rom arguments.
|
/// Everything before `--` is for the package name and rom arguments.
|
||||||
/// Everything after `--` goes directly to nix.
|
/// Everything after `--` goes directly to nix.
|
||||||
#[must_use] pub fn parse_args_with_separator(
|
#[must_use]
|
||||||
|
pub fn parse_args_with_separator(
|
||||||
args: &[String],
|
args: &[String],
|
||||||
) -> (Vec<String>, Vec<String>) {
|
) -> (Vec<String>, Vec<String>) {
|
||||||
if let Some(pos) = args.iter().position(|arg| arg == "--") {
|
if let Some(pos) = args.iter().position(|arg| arg == "--") {
|
||||||
|
|
@ -582,9 +583,7 @@ fn run_monitored_command(
|
||||||
|| !state.full_summary.planned_builds.is_empty();
|
|| !state.full_summary.planned_builds.is_empty();
|
||||||
|
|
||||||
if !silent {
|
if !silent {
|
||||||
if has_activity
|
if has_activity || state.progress_state != ProgressState::JustStarted {
|
||||||
|| state.progress_state != ProgressState::JustStarted
|
|
||||||
{
|
|
||||||
// Clear any previous timer display
|
// Clear any previous timer display
|
||||||
if last_timer_display.is_some() {
|
if last_timer_display.is_some() {
|
||||||
display.clear_previous().ok();
|
display.clear_previous().ok();
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@ use crossterm::{
|
||||||
use crate::state::{BuildStatus, DerivationId, State, current_time};
|
use crate::state::{BuildStatus, DerivationId, State, current_time};
|
||||||
|
|
||||||
/// Format a duration in seconds to a human-readable string
|
/// Format a duration in seconds to a human-readable string
|
||||||
#[must_use] pub fn format_duration(secs: f64) -> String {
|
#[must_use]
|
||||||
|
pub fn format_duration(secs: f64) -> String {
|
||||||
if secs < 60.0 {
|
if secs < 60.0 {
|
||||||
format!("{secs:.0}s")
|
format!("{secs:.0}s")
|
||||||
} else if secs < 3600.0 {
|
} else if secs < 3600.0 {
|
||||||
|
|
@ -381,9 +382,9 @@ impl<W: Write> Display<W> {
|
||||||
|| downloading > 0
|
|| downloading > 0
|
||||||
|| uploading > 0
|
|| uploading > 0
|
||||||
{
|
{
|
||||||
lines.push(self.colored(&"═".repeat(60), Color::Blue).to_string());
|
lines.push(self.colored(&"═".repeat(60), Color::Blue).clone());
|
||||||
lines.push(format!("{} Build Summary", self.colored("┃", Color::Blue)));
|
lines.push(format!("{} Build Summary", self.colored("┃", Color::Blue)));
|
||||||
lines.push(self.colored(&"─".repeat(60), Color::Blue).to_string());
|
lines.push(self.colored(&"─".repeat(60), Color::Blue).clone());
|
||||||
|
|
||||||
// Builds section
|
// Builds section
|
||||||
if running + completed + failed > 0 {
|
if running + completed + failed > 0 {
|
||||||
|
|
@ -430,7 +431,7 @@ impl<W: Write> Display<W> {
|
||||||
self.format_duration(duration)
|
self.format_duration(duration)
|
||||||
));
|
));
|
||||||
|
|
||||||
lines.push(self.colored(&"═".repeat(60), Color::Blue).to_string());
|
lines.push(self.colored(&"═".repeat(60), Color::Blue).clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
lines
|
lines
|
||||||
|
|
@ -860,8 +861,6 @@ impl<W: Write> Display<W> {
|
||||||
lines
|
lines
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn build_active_forest(
|
fn build_active_forest(
|
||||||
&self,
|
&self,
|
||||||
state: &State,
|
state: &State,
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,25 @@
|
||||||
//! Monitor module for orchestrating state updates and display rendering
|
//! Monitor module for orchestrating state updates and display rendering
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
io::{BufRead, Write},
|
io::{BufRead, Write},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use cognos::Host;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
display::{Display, DisplayConfig},
|
display::{Display, DisplayConfig, LegendStyle, SummaryStyle},
|
||||||
error::{Result, RomError},
|
error::{Result, RomError},
|
||||||
state::State,
|
state::{
|
||||||
|
BuildStatus,
|
||||||
|
Derivation,
|
||||||
|
FailType,
|
||||||
|
State,
|
||||||
|
StorePath,
|
||||||
|
StorePathState,
|
||||||
|
},
|
||||||
types::{Config, InputMode},
|
types::{Config, InputMode},
|
||||||
update,
|
update,
|
||||||
};
|
};
|
||||||
use cognos::Host;
|
|
||||||
|
|
||||||
/// Main monitor that processes nix output and displays progress
|
/// Main monitor that processes nix output and displays progress
|
||||||
pub struct Monitor<W: Write> {
|
pub struct Monitor<W: Write> {
|
||||||
|
|
@ -25,15 +32,15 @@ impl<W: Write> Monitor<W> {
|
||||||
/// Create a new monitor
|
/// Create a new monitor
|
||||||
pub fn new(config: Config, writer: W) -> Result<Self> {
|
pub fn new(config: Config, writer: W) -> Result<Self> {
|
||||||
let legend_style = match config.legend_style.to_lowercase().as_str() {
|
let legend_style = match config.legend_style.to_lowercase().as_str() {
|
||||||
"compact" => crate::display::LegendStyle::Compact,
|
"compact" => LegendStyle::Compact,
|
||||||
"verbose" => crate::display::LegendStyle::Verbose,
|
"verbose" => LegendStyle::Verbose,
|
||||||
_ => crate::display::LegendStyle::Table,
|
_ => LegendStyle::Table,
|
||||||
};
|
};
|
||||||
|
|
||||||
let summary_style = match config.summary_style.to_lowercase().as_str() {
|
let summary_style = match config.summary_style.to_lowercase().as_str() {
|
||||||
"table" => crate::display::SummaryStyle::Table,
|
"table" => SummaryStyle::Table,
|
||||||
"full" => crate::display::SummaryStyle::Full,
|
"full" => SummaryStyle::Full,
|
||||||
_ => crate::display::SummaryStyle::Concise,
|
_ => SummaryStyle::Concise,
|
||||||
};
|
};
|
||||||
|
|
||||||
let display_config = DisplayConfig {
|
let display_config = DisplayConfig {
|
||||||
|
|
@ -174,20 +181,21 @@ impl<W: Write> Monitor<W> {
|
||||||
let path_id = self.state.get_or_create_store_path_id(path);
|
let path_id = self.state.get_or_create_store_path_id(path);
|
||||||
let now = crate::state::current_time();
|
let now = crate::state::current_time();
|
||||||
|
|
||||||
|
// Try to extract byte size from the message
|
||||||
|
let total_bytes = extract_byte_size(line);
|
||||||
|
|
||||||
let transfer = crate::state::TransferInfo {
|
let transfer = crate::state::TransferInfo {
|
||||||
start: now,
|
start: now,
|
||||||
host: Host::Localhost,
|
host: Host::Localhost,
|
||||||
activity_id: 0, // No activity ID in human mode
|
activity_id: 0, // no activity ID in human mode
|
||||||
bytes_transferred: 0,
|
bytes_transferred: 0,
|
||||||
total_bytes: None,
|
total_bytes,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(path_info) = self.state.get_store_path_info_mut(path_id) {
|
if let Some(path_info) = self.state.get_store_path_info_mut(path_id) {
|
||||||
path_info
|
path_info
|
||||||
.states
|
.states
|
||||||
.insert(crate::state::StorePathState::Downloading(
|
.insert(StorePathState::Downloading(transfer.clone()));
|
||||||
transfer.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
|
|
@ -201,14 +209,115 @@ impl<W: Write> Monitor<W> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect download completions with byte sizes
|
||||||
|
if line.starts_with("downloaded") || line.contains("downloaded '") {
|
||||||
|
if let Some(path_str) = extract_path_from_message(line) {
|
||||||
|
if let Some(path) = StorePath::parse(&path_str) {
|
||||||
|
if let Some(&path_id) = self.state.store_path_ids.get(&path) {
|
||||||
|
let now = crate::state::current_time();
|
||||||
|
let total_bytes = extract_byte_size(line).unwrap_or(0);
|
||||||
|
|
||||||
|
// Get start time from running download if it exists
|
||||||
|
let start = self
|
||||||
|
.state
|
||||||
|
.full_summary
|
||||||
|
.running_downloads
|
||||||
|
.get(&path_id)
|
||||||
|
.map_or(now, |t| t.start);
|
||||||
|
|
||||||
|
let completed = crate::state::CompletedTransferInfo {
|
||||||
|
start,
|
||||||
|
end: now,
|
||||||
|
host: Host::Localhost,
|
||||||
|
total_bytes,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(path_info) = self.state.get_store_path_info_mut(path_id)
|
||||||
|
{
|
||||||
|
path_info
|
||||||
|
.states
|
||||||
|
.insert(StorePathState::Downloaded(completed.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state.full_summary.running_downloads.remove(&path_id);
|
||||||
|
self
|
||||||
|
.state
|
||||||
|
.full_summary
|
||||||
|
.completed_downloads
|
||||||
|
.insert(path_id, completed);
|
||||||
|
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect "checking outputs of" messages
|
||||||
|
if line.contains("checking outputs of") {
|
||||||
|
if let Some(drv_path) = extract_path_from_message(line) {
|
||||||
|
if let Some(drv) = crate::state::Derivation::parse(&drv_path) {
|
||||||
|
let drv_id = self.state.get_or_create_derivation_id(drv);
|
||||||
|
// Just mark it as "touched" - checking happens after build
|
||||||
|
// Reminds me of Sako...
|
||||||
|
self.state.touched_ids.insert(drv_id);
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect "copying N paths" messages
|
||||||
|
if line.starts_with("copying") && line.contains("paths") {
|
||||||
|
// Extract number of paths if present
|
||||||
|
let words: Vec<&str> = line.split_whitespace().collect();
|
||||||
|
if words.len() >= 2 {
|
||||||
|
if let Ok(_count) = words[1].parse::<usize>() {
|
||||||
|
// XXX: This is a PlanCopies message, we'll probably track this
|
||||||
|
// For now just acknowledge it, and let future work decide how
|
||||||
|
// we should go around doing it.
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Detect errors
|
// Detect errors
|
||||||
if line.starts_with("error:") || line.contains("error:") {
|
if line.starts_with("error:") || line.contains("error:") {
|
||||||
self.state.nix_errors.push(line.to_string());
|
self.state.nix_errors.push(line.to_string());
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect build completions
|
// Try to determine the error type and associated derivation
|
||||||
if line.starts_with("built") || line.contains("built '") {
|
let fail_type = if line.contains("hash mismatch")
|
||||||
|
|| line.contains("output path")
|
||||||
|
&& (line.contains("hash") || line.contains("differs"))
|
||||||
|
{
|
||||||
|
FailType::HashMismatch
|
||||||
|
} else if line.contains("timed out") || line.contains("timeout") {
|
||||||
|
FailType::Timeout
|
||||||
|
} else if line.contains("dependency failed")
|
||||||
|
|| line.contains("dependencies failed")
|
||||||
|
{
|
||||||
|
FailType::DependencyFailed
|
||||||
|
} else if line.contains("builder for")
|
||||||
|
&& line.contains("failed with exit code")
|
||||||
|
{
|
||||||
|
// Try to extract exit code
|
||||||
|
if let Some(code_pos) = line.find("exit code") {
|
||||||
|
let after_code = &line[code_pos + 10..];
|
||||||
|
let code_str = after_code
|
||||||
|
.split_whitespace()
|
||||||
|
.next()
|
||||||
|
.map(|s| s.trim_end_matches(|c: char| !c.is_ascii_digit()));
|
||||||
|
if let Some(code) = code_str.and_then(|s| s.parse::<i32>().ok()) {
|
||||||
|
FailType::BuildFailed(code)
|
||||||
|
} else {
|
||||||
|
FailType::Unknown
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FailType::Unknown
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FailType::Unknown
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to find the associated derivation and mark it as failed
|
||||||
if let Some(drv_path) = extract_path_from_message(line) {
|
if let Some(drv_path) = extract_path_from_message(line) {
|
||||||
if let Some(drv) = crate::state::Derivation::parse(&drv_path) {
|
if let Some(drv) = crate::state::Derivation::parse(&drv_path) {
|
||||||
if let Some(&drv_id) = self.state.derivation_ids.get(&drv) {
|
if let Some(&drv_id) = self.state.derivation_ids.get(&drv) {
|
||||||
|
|
@ -219,11 +328,35 @@ impl<W: Write> Monitor<W> {
|
||||||
let now = crate::state::current_time();
|
let now = crate::state::current_time();
|
||||||
self.state.update_build_status(
|
self.state.update_build_status(
|
||||||
drv_id,
|
drv_id,
|
||||||
crate::state::BuildStatus::Built {
|
crate::state::BuildStatus::Failed {
|
||||||
info: build_info.clone(),
|
info: build_info.clone(),
|
||||||
end: now,
|
fail: crate::state::BuildFail {
|
||||||
|
at: now,
|
||||||
|
fail_type: fail_type.clone(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect build completions
|
||||||
|
if line.starts_with("built") || line.contains("built '") {
|
||||||
|
if let Some(drv_path) = extract_path_from_message(line) {
|
||||||
|
if let Some(drv) = Derivation::parse(&drv_path) {
|
||||||
|
if let Some(&drv_id) = self.state.derivation_ids.get(&drv) {
|
||||||
|
if let Some(info) = self.state.get_derivation_info(drv_id) {
|
||||||
|
if let BuildStatus::Building(build_info) = &info.build_status {
|
||||||
|
let now = crate::state::current_time();
|
||||||
|
self.state.update_build_status(drv_id, BuildStatus::Built {
|
||||||
|
info: build_info.clone(),
|
||||||
|
end: now,
|
||||||
|
});
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -271,6 +404,33 @@ fn extract_path_from_message(line: &str) -> Option<String> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extract byte size from a message line (e.g., "downloaded 123 KiB")
|
||||||
|
fn extract_byte_size(line: &str) -> Option<u64> {
|
||||||
|
// Look for patterns like "123 KiB", "6.7 MiB", etc.
|
||||||
|
// Haha 6.7
|
||||||
|
let words: Vec<&str> = line.split_whitespace().collect();
|
||||||
|
for (i, word) in words.iter().enumerate() {
|
||||||
|
if i + 1 < words.len() {
|
||||||
|
let unit = words[i + 1];
|
||||||
|
if matches!(unit, "B" | "KiB" | "MiB" | "GiB" | "TiB" | "PiB") {
|
||||||
|
if let Ok(value) = word.parse::<f64>() {
|
||||||
|
let multiplier = match unit {
|
||||||
|
"B" => 1_u64,
|
||||||
|
"KiB" => 1024,
|
||||||
|
"MiB" => 1024 * 1024,
|
||||||
|
"GiB" => 1024 * 1024 * 1024,
|
||||||
|
"TiB" => 1024_u64 * 1024 * 1024 * 1024,
|
||||||
|
"PiB" => 1024_u64 * 1024 * 1024 * 1024 * 1024,
|
||||||
|
_ => 1,
|
||||||
|
};
|
||||||
|
return Some((value * multiplier as f64) as u64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
@ -297,4 +457,19 @@ mod tests {
|
||||||
let path = extract_path_from_message(line);
|
let path = extract_path_from_message(line);
|
||||||
assert!(path.is_some());
|
assert!(path.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_byte_size() {
|
||||||
|
let line = "downloaded 123 KiB in 2 seconds";
|
||||||
|
assert_eq!(extract_byte_size(line), Some(123 * 1024));
|
||||||
|
|
||||||
|
let line2 = "downloading 4.5 MiB";
|
||||||
|
assert_eq!(
|
||||||
|
extract_byte_size(line2),
|
||||||
|
Some((4.5 * 1024.0 * 1024.0) as u64)
|
||||||
|
);
|
||||||
|
|
||||||
|
let line3 = "no size here";
|
||||||
|
assert_eq!(extract_byte_size(line3), None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,6 @@ pub type DerivationId = usize;
|
||||||
/// Unique identifier for activities
|
/// Unique identifier for activities
|
||||||
pub type ActivityId = Id;
|
pub type ActivityId = Id;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// Store path representation
|
/// Store path representation
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct StorePath {
|
pub struct StorePath {
|
||||||
|
|
@ -85,9 +81,6 @@ impl Derivation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// Transfer information (download/upload)
|
/// Transfer information (download/upload)
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TransferInfo {
|
pub struct TransferInfo {
|
||||||
|
|
@ -324,6 +317,20 @@ pub struct ActivityStatus {
|
||||||
pub text: String,
|
pub text: String,
|
||||||
pub parent: Option<ActivityId>,
|
pub parent: Option<ActivityId>,
|
||||||
pub phase: Option<String>,
|
pub phase: Option<String>,
|
||||||
|
pub progress: Option<ActivityProgress>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Activity progress for downloads/uploads/builds
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct ActivityProgress {
|
||||||
|
/// Bytes completed
|
||||||
|
pub done: u64,
|
||||||
|
/// Total bytes expected
|
||||||
|
pub expected: u64,
|
||||||
|
/// Currently running transfers
|
||||||
|
pub running: u64,
|
||||||
|
/// Failed transfers
|
||||||
|
pub failed: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build report for caching
|
/// Build report for caching
|
||||||
|
|
@ -361,6 +368,7 @@ pub struct State {
|
||||||
pub activities: HashMap<ActivityId, ActivityStatus>,
|
pub activities: HashMap<ActivityId, ActivityStatus>,
|
||||||
pub nix_errors: Vec<String>,
|
pub nix_errors: Vec<String>,
|
||||||
pub build_logs: Vec<String>,
|
pub build_logs: Vec<String>,
|
||||||
|
pub traces: Vec<String>,
|
||||||
pub build_platform: Option<String>,
|
pub build_platform: Option<String>,
|
||||||
pub evaluation_state: EvalInfo,
|
pub evaluation_state: EvalInfo,
|
||||||
next_store_path_id: StorePathId,
|
next_store_path_id: StorePathId,
|
||||||
|
|
@ -390,6 +398,7 @@ impl State {
|
||||||
activities: HashMap::new(),
|
activities: HashMap::new(),
|
||||||
nix_errors: Vec::new(),
|
nix_errors: Vec::new(),
|
||||||
build_logs: Vec::new(),
|
build_logs: Vec::new(),
|
||||||
|
traces: Vec::new(),
|
||||||
build_platform: None,
|
build_platform: None,
|
||||||
evaluation_state: EvalInfo::default(),
|
evaluation_state: EvalInfo::default(),
|
||||||
next_store_path_id: 0,
|
next_store_path_id: 0,
|
||||||
|
|
@ -664,6 +673,30 @@ impl State {
|
||||||
.map(|(id, info)| (*id, info))
|
.map(|(id, info)| (*id, info))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if a derivation has a platform mismatch
|
||||||
|
#[must_use]
|
||||||
|
pub fn has_platform_mismatch(&self, id: DerivationId) -> bool {
|
||||||
|
if let (Some(build_platform), Some(info)) =
|
||||||
|
(&self.build_platform, self.get_derivation_info(id))
|
||||||
|
{
|
||||||
|
if let Some(drv_platform) = &info.platform {
|
||||||
|
return build_platform != drv_platform;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all derivations with platform mismatches
|
||||||
|
#[must_use]
|
||||||
|
pub fn platform_mismatches(&self) -> Vec<DerivationId> {
|
||||||
|
self
|
||||||
|
.derivation_infos
|
||||||
|
.keys()
|
||||||
|
.filter(|&&id| self.has_platform_mismatch(id))
|
||||||
|
.copied()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
@ -674,8 +707,6 @@ pub fn current_time() -> f64 {
|
||||||
.as_secs_f64()
|
.as_secs_f64()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,8 @@ pub enum SummaryStyle {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SummaryStyle {
|
impl SummaryStyle {
|
||||||
#[must_use] pub fn from_str(s: &str) -> Self {
|
#[must_use]
|
||||||
|
pub fn from_str(s: &str) -> Self {
|
||||||
match s.to_lowercase().as_str() {
|
match s.to_lowercase().as_str() {
|
||||||
"concise" => Self::Concise,
|
"concise" => Self::Concise,
|
||||||
"table" => Self::Table,
|
"table" => Self::Table,
|
||||||
|
|
@ -34,7 +35,8 @@ impl SummaryStyle {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DisplayFormat {
|
impl DisplayFormat {
|
||||||
#[must_use] pub fn from_str(s: &str) -> Self {
|
#[must_use]
|
||||||
|
pub fn from_str(s: &str) -> Self {
|
||||||
match s.to_lowercase().as_str() {
|
match s.to_lowercase().as_str() {
|
||||||
"tree" => Self::Tree,
|
"tree" => Self::Tree,
|
||||||
"plain" => Self::Plain,
|
"plain" => Self::Plain,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use cognos::{Actions, Activities, Host, Id, ProgressState, Verbosity};
|
||||||
use tracing::{debug, trace};
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
use crate::state::{
|
use crate::state::{
|
||||||
|
ActivityProgress,
|
||||||
ActivityStatus,
|
ActivityStatus,
|
||||||
BuildFail,
|
BuildFail,
|
||||||
BuildInfo,
|
BuildInfo,
|
||||||
|
|
@ -86,21 +87,29 @@ fn handle_start(
|
||||||
text: text.clone(),
|
text: text.clone(),
|
||||||
parent: parent_id,
|
parent: parent_id,
|
||||||
phase: None,
|
phase: None,
|
||||||
|
progress: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
let changed = match activity_u8 {
|
let changed = match activity_u8 {
|
||||||
104 | 105 => handle_build_start(state, id, parent_id, &text, &fields, now), /* Builds | Build */
|
105 => handle_build_start(state, id, parent_id, &text, &fields, now), /* Build */
|
||||||
108 => handle_substitute_start(state, id, &text, &fields, now), /* Substitute */
|
108 => handle_substitute_start(state, id, &text, &fields, now), /* Substitute */
|
||||||
101 => handle_transfer_start(state, id, &text, &fields, now, false), /* FileTransfer */
|
109 => handle_query_path_info_start(state, id, &text, &fields, now), /* QueryPathInfo */
|
||||||
100 | 103 => handle_transfer_start(state, id, &text, &fields, now, true), /* CopyPath | CopyPaths */
|
110 => handle_post_build_hook_start(state, id, &text, &fields, now), /* PostBuildHook */
|
||||||
_ => false,
|
101 => handle_file_transfer_start(state, id, &text, &fields, now), /* FileTransfer */
|
||||||
|
100 => handle_copy_path_start(state, id, &text, &fields, now), // CopyPath
|
||||||
|
102 | 103 | 104 | 106 | 107 | 111 | 112 => {
|
||||||
|
// Realise, CopyPaths, Builds, OptimiseStore, VerifyPaths, BuildWaiting,
|
||||||
|
// FetchTree These activities have no fields and are just tracked
|
||||||
|
true
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
debug!("Unknown activity type: {}", activity_u8);
|
||||||
|
false
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Track parent-child relationships for dependency tree
|
// Track parent-child relationships for dependency tree
|
||||||
if changed
|
if changed && activity_u8 == 105 && parent_id.is_some() {
|
||||||
&& (activity_u8 == 104 || activity_u8 == 105)
|
|
||||||
&& parent_id.is_some()
|
|
||||||
{
|
|
||||||
let parent_act_id = parent_id.unwrap();
|
let parent_act_id = parent_id.unwrap();
|
||||||
|
|
||||||
// Find parent and child derivation IDs
|
// Find parent and child derivation IDs
|
||||||
|
|
@ -110,8 +119,8 @@ fn handle_start(
|
||||||
if let Some(parent_drv_id) = parent_drv_id {
|
if let Some(parent_drv_id) = parent_drv_id {
|
||||||
if let Some(child_drv_id) = child_drv_id {
|
if let Some(child_drv_id) = child_drv_id {
|
||||||
debug!(
|
debug!(
|
||||||
"Establishing parent-child relationship: parent={}, child={}",
|
"Establishing parent-child relationship: parent={parent_drv_id}, \
|
||||||
parent_drv_id, child_drv_id
|
child={child_drv_id}"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add child as a dependency of parent
|
// Add child as a dependency of parent
|
||||||
|
|
@ -150,9 +159,19 @@ fn handle_stop(state: &mut State, id: Id, now: f64) -> bool {
|
||||||
state.activities.remove(&id);
|
state.activities.remove(&id);
|
||||||
|
|
||||||
match activity_status.activity {
|
match activity_status.activity {
|
||||||
104 | 105 => handle_build_stop(state, id, now), // Builds | Build
|
105 => handle_build_stop(state, id, now), // Build
|
||||||
108 => handle_substitute_stop(state, id, now), // Substitute
|
108 => handle_substitute_stop(state, id, now), // Substitute
|
||||||
101 | 100 | 103 => handle_transfer_stop(state, id, now), /* FileTransfer, CopyPath, CopyPaths */
|
101 | 100 => handle_transfer_stop(state, id, now), // FileTransfer,
|
||||||
|
// CopyPath
|
||||||
|
109 | 110 => {
|
||||||
|
// QueryPathInfo, PostBuildHook - just acknowledge stop
|
||||||
|
false
|
||||||
|
},
|
||||||
|
102 | 103 | 104 | 106 | 107 | 111 | 112 => {
|
||||||
|
// Realise, CopyPaths, Builds, OptimiseStore, VerifyPaths, BuildWaiting,
|
||||||
|
// FetchTree
|
||||||
|
false
|
||||||
|
},
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -166,7 +185,7 @@ fn handle_message(state: &mut State, level: Verbosity, msg: String) -> bool {
|
||||||
|
|
||||||
// Extract phase from log messages like "Running phase: configurePhase"
|
// Extract phase from log messages like "Running phase: configurePhase"
|
||||||
if let Some(phase_start) = msg.find("Running phase: ") {
|
if let Some(phase_start) = msg.find("Running phase: ") {
|
||||||
let phase_name = &msg[phase_start + 15..]; // Skip "Running phase: "
|
let phase_name = &msg[phase_start + 15..]; // skip "Running phase: "
|
||||||
let phase = phase_name.trim().to_string();
|
let phase = phase_name.trim().to_string();
|
||||||
|
|
||||||
// Find the active build and update its phase
|
// Find the active build and update its phase
|
||||||
|
|
@ -228,6 +247,14 @@ fn handle_message(state: &mut State, level: Verbosity, msg: String) -> bool {
|
||||||
}
|
}
|
||||||
true // return true since we stored the log
|
true // return true since we stored the log
|
||||||
},
|
},
|
||||||
|
Verbosity::Talkative
|
||||||
|
| Verbosity::Chatty
|
||||||
|
| Verbosity::Debug
|
||||||
|
| Verbosity::Vomit => {
|
||||||
|
// These are trace-level messages, store separately
|
||||||
|
state.traces.push(msg.clone());
|
||||||
|
true
|
||||||
|
},
|
||||||
_ => {
|
_ => {
|
||||||
true // return true since we stored the log
|
true // return true since we stored the log
|
||||||
},
|
},
|
||||||
|
|
@ -237,38 +264,118 @@ fn handle_message(state: &mut State, level: Verbosity, msg: String) -> bool {
|
||||||
fn handle_result(
|
fn handle_result(
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
id: Id,
|
id: Id,
|
||||||
activity: u8,
|
result_type: u8,
|
||||||
fields: Vec<serde_json::Value>,
|
fields: Vec<serde_json::Value>,
|
||||||
_now: f64,
|
_now: f64,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match activity {
|
// Result message types are DIFFERENT from Activity types
|
||||||
101 | 108 => {
|
// Type 100: FileLinked (2 ints)
|
||||||
// FileTransfer or Substitute
|
// Type 101: BuildLogLine (1 text)
|
||||||
// Fields contain progress information
|
// Type 102: UntrustedPath (1 text - store path)
|
||||||
// XXX: Format: [bytes_transferred, total_bytes]
|
// Type 103: CorruptedPath (1 text - store path)
|
||||||
|
// Type 104: SetPhase (1 text)
|
||||||
|
// Type 105: Progress (4 ints: done, expected, running, failed)
|
||||||
|
// Type 106: SetExpected (2 ints: activity type, count)
|
||||||
|
// Type 107: PostBuildLogLine (1 text)
|
||||||
|
// Type 108: FetchStatus (1 text)
|
||||||
|
|
||||||
|
match result_type {
|
||||||
|
100 => {
|
||||||
|
// FileLinked: 2 int fields
|
||||||
if fields.len() >= 2 {
|
if fields.len() >= 2 {
|
||||||
update_transfer_progress(state, id, &fields);
|
let _linked = fields[0].as_u64();
|
||||||
|
let _total = fields[1].as_u64();
|
||||||
|
// TODO: Track file linking progress
|
||||||
|
}
|
||||||
|
false
|
||||||
|
},
|
||||||
|
101 => {
|
||||||
|
// BuildLogLine: 1 text field
|
||||||
|
if let Some(line) = fields.first().and_then(|f| f.as_str()) {
|
||||||
|
state.build_logs.push(line.to_string());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
},
|
||||||
|
102 => {
|
||||||
|
// UntrustedPath: 1 text field (store path)
|
||||||
|
if let Some(path_str) = fields.first().and_then(|f| f.as_str()) {
|
||||||
|
debug!("Untrusted path: {}", path_str);
|
||||||
|
// TODO: Track untrusted paths
|
||||||
|
}
|
||||||
|
false
|
||||||
|
},
|
||||||
|
103 => {
|
||||||
|
// CorruptedPath: 1 text field (store path)
|
||||||
|
if let Some(path_str) = fields.first().and_then(|f| f.as_str()) {
|
||||||
|
state
|
||||||
|
.nix_errors
|
||||||
|
.push(format!("Corrupted path: {path_str}"));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
},
|
},
|
||||||
104 => {
|
104 => {
|
||||||
// Builds activity type - contains phase information
|
// SetPhase: 1 text field
|
||||||
if !fields.is_empty() {
|
if let Some(phase_str) = fields.first().and_then(|f| f.as_str()) {
|
||||||
if let Some(phase_str) = fields[0].as_str() {
|
|
||||||
// Update the activity's phase field
|
|
||||||
if let Some(activity) = state.activities.get_mut(&id) {
|
if let Some(activity) = state.activities.get_mut(&id) {
|
||||||
activity.phase = Some(phase_str.to_string());
|
activity.phase = Some(phase_str.to_string());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
false
|
||||||
|
},
|
||||||
|
105 => {
|
||||||
|
// Progress: 4 int fields (done, expected, running, failed)
|
||||||
|
if fields.len() >= 4 {
|
||||||
|
if let (Some(done), Some(expected), Some(running), Some(failed)) = (
|
||||||
|
fields[0].as_u64(),
|
||||||
|
fields[1].as_u64(),
|
||||||
|
fields[2].as_u64(),
|
||||||
|
fields[3].as_u64(),
|
||||||
|
) {
|
||||||
|
if let Some(activity) = state.activities.get_mut(&id) {
|
||||||
|
activity.progress = Some(ActivityProgress {
|
||||||
|
done,
|
||||||
|
expected,
|
||||||
|
running,
|
||||||
|
failed,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
},
|
},
|
||||||
105 => {
|
106 => {
|
||||||
// Build completed, fields contain output path
|
// SetExpected: 2 int fields (activity type, count)
|
||||||
complete_build(state, id)
|
if fields.len() >= 2 {
|
||||||
|
let _activity_type = fields[0].as_u64();
|
||||||
|
let _expected_count = fields[1].as_u64();
|
||||||
|
// TODO: Track expected counts
|
||||||
|
}
|
||||||
|
false
|
||||||
|
},
|
||||||
|
107 => {
|
||||||
|
// PostBuildLogLine: 1 text field
|
||||||
|
if let Some(line) = fields.first().and_then(|f| f.as_str()) {
|
||||||
|
state.build_logs.push(format!("[post-build] {line}"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
},
|
||||||
|
108 => {
|
||||||
|
// FetchStatus: 1 text field
|
||||||
|
if let Some(status) = fields.first().and_then(|f| f.as_str()) {
|
||||||
|
debug!("Fetch status: {}", status);
|
||||||
|
// TODO: Track fetch status
|
||||||
|
}
|
||||||
|
false
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
debug!("Unknown result type: {}", result_type);
|
||||||
|
false
|
||||||
},
|
},
|
||||||
_ => false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -320,55 +427,19 @@ fn handle_build_start(
|
||||||
);
|
);
|
||||||
|
|
||||||
// Mark as forest root if no parent
|
// Mark as forest root if no parent
|
||||||
// Only add to forest roots if no parent
|
|
||||||
if parent_id.is_none() && !state.forest_roots.contains(&drv_id) {
|
if parent_id.is_none() && !state.forest_roots.contains(&drv_id) {
|
||||||
state.forest_roots.push(drv_id);
|
state.forest_roots.push(drv_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store activity -> derivation mapping
|
|
||||||
// Phase will be extracted from log messages
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
debug!("Failed to parse derivation from path: {}", drv_path);
|
debug!("Failed to parse derivation from path: {}", drv_path);
|
||||||
} else {
|
} else {
|
||||||
debug!(
|
debug!(
|
||||||
"No derivation path found - creating placeholder for activity {}",
|
"No derivation path in fields for Build activity {} - this should not \
|
||||||
|
happen",
|
||||||
id
|
id
|
||||||
);
|
);
|
||||||
// For shell/develop commands, nix doesn't report specific derivation paths
|
|
||||||
// Create a placeholder derivation to track that builds are happening
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
let placeholder_name = format!("building-{id}");
|
|
||||||
let placeholder_path = format!("/nix/store/placeholder-{id}.drv");
|
|
||||||
|
|
||||||
let placeholder_drv = Derivation {
|
|
||||||
path: PathBuf::from(placeholder_path),
|
|
||||||
name: placeholder_name,
|
|
||||||
};
|
|
||||||
|
|
||||||
let drv_id = state.get_or_create_derivation_id(placeholder_drv);
|
|
||||||
let host = extract_host(text);
|
|
||||||
|
|
||||||
let build_info = BuildInfo {
|
|
||||||
start: now,
|
|
||||||
host,
|
|
||||||
estimate: None,
|
|
||||||
activity_id: Some(id),
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
"Setting placeholder derivation {} to Building status",
|
|
||||||
drv_id
|
|
||||||
);
|
|
||||||
state.update_build_status(drv_id, BuildStatus::Building(build_info));
|
|
||||||
|
|
||||||
// Mark as forest root if no parent
|
|
||||||
if parent_id.is_none() && !state.forest_roots.contains(&drv_id) {
|
|
||||||
state.forest_roots.push(drv_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
@ -474,45 +545,111 @@ fn handle_substitute_stop(state: &mut State, id: Id, now: f64) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_transfer_start(
|
fn handle_file_transfer_start(
|
||||||
|
_state: &mut State,
|
||||||
|
id: Id,
|
||||||
|
_text: &str,
|
||||||
|
fields: &[serde_json::Value],
|
||||||
|
_now: f64,
|
||||||
|
) -> bool {
|
||||||
|
// FileTransfer expects 1 text field: URL or description
|
||||||
|
if fields.is_empty() {
|
||||||
|
debug!("FileTransfer activity {} has no fields", id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just track the activity, actual progress comes via Result messages
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_copy_path_start(
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
id: Id,
|
id: Id,
|
||||||
text: &str,
|
_text: &str,
|
||||||
fields: &[serde_json::Value],
|
fields: &[serde_json::Value],
|
||||||
now: f64,
|
now: f64,
|
||||||
is_copy: bool,
|
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let path_str = if fields.is_empty() {
|
// CopyPath expects 3 text fields: path, from, to
|
||||||
extract_store_path(text)
|
if fields.len() < 3 {
|
||||||
} else {
|
debug!("CopyPath activity {} has insufficient fields", id);
|
||||||
fields[0].as_str().map(std::string::ToString::to_string)
|
return false;
|
||||||
};
|
}
|
||||||
|
|
||||||
if let Some(path_str) = path_str {
|
let path_str = fields[0].as_str();
|
||||||
if let Some(path) = StorePath::parse(&path_str) {
|
let _from_host = fields[1].as_str().map(|s| {
|
||||||
|
if s.is_empty() || s == "localhost" {
|
||||||
|
Host::Localhost
|
||||||
|
} else {
|
||||||
|
Host::Remote(s.to_string())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let to_host = fields[2].as_str().map(|s| {
|
||||||
|
if s.is_empty() || s == "localhost" {
|
||||||
|
Host::Localhost
|
||||||
|
} else {
|
||||||
|
Host::Remote(s.to_string())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let (Some(path_str), Some(to)) = (path_str, to_host) {
|
||||||
|
if let Some(path) = StorePath::parse(path_str) {
|
||||||
let path_id = state.get_or_create_store_path_id(path);
|
let path_id = state.get_or_create_store_path_id(path);
|
||||||
let host = extract_host(text);
|
|
||||||
|
|
||||||
let transfer = TransferInfo {
|
let transfer = TransferInfo {
|
||||||
start: now,
|
start: now,
|
||||||
host,
|
host: to, // destination host
|
||||||
activity_id: id,
|
activity_id: id,
|
||||||
bytes_transferred: 0,
|
bytes_transferred: 0,
|
||||||
total_bytes: None,
|
total_bytes: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_copy {
|
// CopyPath is an upload from 'from' to 'to'
|
||||||
state.full_summary.running_uploads.insert(path_id, transfer);
|
state.full_summary.running_uploads.insert(path_id, transfer);
|
||||||
} else {
|
|
||||||
state
|
|
||||||
.full_summary
|
|
||||||
.running_downloads
|
|
||||||
.insert(path_id, transfer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_query_path_info_start(
|
||||||
|
_state: &mut State,
|
||||||
|
id: Id,
|
||||||
|
_text: &str,
|
||||||
|
fields: &[serde_json::Value],
|
||||||
|
_now: f64,
|
||||||
|
) -> bool {
|
||||||
|
// QueryPathInfo expects 2 text fields: path, host
|
||||||
|
if fields.len() < 2 {
|
||||||
|
debug!("QueryPathInfo activity {} has insufficient fields", id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just track the activity
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_post_build_hook_start(
|
||||||
|
_state: &mut State,
|
||||||
|
id: Id,
|
||||||
|
_text: &str,
|
||||||
|
fields: &[serde_json::Value],
|
||||||
|
_now: f64,
|
||||||
|
) -> bool {
|
||||||
|
// PostBuildHook expects 1 text field: derivation path
|
||||||
|
if fields.is_empty() {
|
||||||
|
debug!("PostBuildHook activity {} has no fields", id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let drv_path = fields[0].as_str();
|
||||||
|
if let Some(drv_path) = drv_path {
|
||||||
|
if let Some(_drv) = Derivation::parse(drv_path) {
|
||||||
|
// Just track that the hook is running
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -561,54 +698,6 @@ fn handle_transfer_stop(state: &mut State, id: Id, now: f64) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_transfer_progress(
|
|
||||||
state: &mut State,
|
|
||||||
id: Id,
|
|
||||||
fields: &[serde_json::Value],
|
|
||||||
) {
|
|
||||||
if fields.len() < 2 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let bytes_transferred = fields[0].as_u64().unwrap_or(0);
|
|
||||||
let total_bytes = fields[1].as_u64();
|
|
||||||
|
|
||||||
// Update running downloads
|
|
||||||
for transfer_info in state.full_summary.running_downloads.values_mut() {
|
|
||||||
if transfer_info.activity_id == id {
|
|
||||||
transfer_info.bytes_transferred = bytes_transferred;
|
|
||||||
transfer_info.total_bytes = total_bytes;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update running uploads
|
|
||||||
for transfer_info in state.full_summary.running_uploads.values_mut() {
|
|
||||||
if transfer_info.activity_id == id {
|
|
||||||
transfer_info.bytes_transferred = bytes_transferred;
|
|
||||||
transfer_info.total_bytes = total_bytes;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn complete_build(state: &mut State, id: Id) -> bool {
|
|
||||||
// Find the derivation that just completed
|
|
||||||
for (drv_id, info) in &state.derivation_infos.clone() {
|
|
||||||
if let BuildStatus::Building(build_info) = &info.build_status {
|
|
||||||
if build_info.activity_id == Some(id) {
|
|
||||||
let end = current_time();
|
|
||||||
state.update_build_status(*drv_id, BuildStatus::Built {
|
|
||||||
info: build_info.clone(),
|
|
||||||
end,
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_derivation_path(text: &str) -> Option<String> {
|
fn extract_derivation_path(text: &str) -> Option<String> {
|
||||||
// Look for .drv paths in the text
|
// Look for .drv paths in the text
|
||||||
if let Some(start) = text.find("/nix/store/") {
|
if let Some(start) = text.find("/nix/store/") {
|
||||||
|
|
@ -883,6 +972,3 @@ pub fn finish_state(state: &mut State) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue