Compare commits
699 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bd5f10f0c | ||
|
|
bfaca61deb | ||
|
|
b00c4c1503 | ||
|
|
54a722be21 | ||
|
|
bf4555ed29 | ||
|
|
46eb74109e | ||
|
|
0bde798b4a | ||
|
|
ebb4448950 | ||
|
|
b2b51bc8d2 | ||
|
|
8840a273b4 | ||
|
|
50e55dea77 | ||
|
|
f7c9e42066 | ||
|
|
ef3ee7bea7 | ||
|
|
8eb300c4fb | ||
|
|
b7b99a725c | ||
|
|
df090a89c4 | ||
|
|
856fa0ed5b | ||
|
|
9bb5a680ac | ||
|
|
2d40f7bdb3 | ||
|
|
06b210d1c9 | ||
|
|
e0e714e7b5 | ||
|
|
dfb8342d8b | ||
|
|
9d662e36a1 | ||
|
|
45159c29fe | ||
|
|
a27c68a1dd | ||
|
|
cdee1c14d9 | ||
|
|
8de6a36d86 | ||
|
|
b03a8d53bb | ||
|
|
b0510cdade | ||
|
|
7945311b4b | ||
|
|
75de4dfd3b | ||
|
|
6a838bbcb7 | ||
|
|
ab2bab8c9b | ||
|
|
9a55278d32 | ||
|
|
f9735f3b31 | ||
|
|
50be214b56 | ||
|
|
9ec8e83f41 | ||
|
|
c70984d458 | ||
|
|
a3503c0c70 | ||
|
|
ca2d16edfd | ||
|
|
722b1ad09e | ||
|
|
743845a66b | ||
|
|
2594f4c0fb | ||
|
|
639d055f9a | ||
|
|
ea2ccdd69f | ||
|
|
84a50afa83 | ||
|
|
b13c1bd2d7 | ||
|
|
7749f41d56 | ||
|
|
593a2a33d9 | ||
|
|
4f693aeaf3 | ||
|
|
c3d34184d0 | ||
|
|
4aa224de87 | ||
|
|
320b13c06b | ||
|
|
907d778c55 | ||
|
|
f3fccb86c0 | ||
|
|
bb4afb71e9 | ||
|
|
ec8d30f634 | ||
|
|
50d318641a | ||
|
|
c5267f6087 | ||
|
|
d80e8f64d1 | ||
|
|
c6f2e0cc44 | ||
|
|
99c3e8af26 | ||
|
|
30d3537c0e | ||
|
|
90cb16e3d0 | ||
|
|
5192a0f1dc | ||
|
|
bec7edf1fc | ||
|
|
051784ac0d | ||
|
|
17d715479a | ||
|
|
39a90f5ebe | ||
|
|
80c4bd5065 | ||
|
|
222d800a32 | ||
|
|
b29699fc55 | ||
|
|
fc0e5461eb | ||
|
|
75da4a709c | ||
|
|
02e388122b | ||
|
|
02fe1087de | ||
|
|
34b7943fd1 | ||
|
|
b61886f0f9 | ||
|
|
d9a8ecfd33 | ||
|
|
6c68bfaf64 | ||
|
|
5866a0570f | ||
|
|
28f5754efd | ||
|
|
ea1b286c98 | ||
|
|
22ab77de6d | ||
|
|
410bd61c75 | ||
|
|
07b422915c | ||
|
|
5b9d387ef3 | ||
|
|
5b5dd27834 | ||
|
|
79f65981a5 | ||
|
|
a52c775247 | ||
|
|
549111db3a | ||
|
|
1572974ec4 | ||
|
|
8387468607 | ||
|
|
94979d6b7a | ||
|
|
6652a2aa90 | ||
|
|
f943b220d9 | ||
|
|
0fc7016d68 | ||
|
|
9e9e6c9d55 | ||
|
|
bafa15c8f1 | ||
|
|
f669de8272 | ||
|
|
ff26835406 | ||
|
|
99892359c7 | ||
|
|
8fc25d7fd4 | ||
|
|
942bfaa708 | ||
|
|
a1fd324e82 | ||
|
|
62bf2c6e90 | ||
|
|
9f1e0c8eef | ||
|
|
027de7c865 | ||
|
|
266adabd13 | ||
|
|
7d2e43f83b | ||
|
|
294a90a7c3 | ||
|
|
af5def1551 | ||
|
|
8c63ee6a18 | ||
|
|
ee8ae8623d | ||
|
|
222e6b55c0 | ||
|
|
2050a80665 | ||
|
|
e01be14041 | ||
|
|
dd6bc580fa | ||
|
|
22ef36a185 | ||
|
|
b57ceccbe1 | ||
|
|
e9d430a4e4 | ||
|
|
8a247fba95 | ||
|
|
5b6c31bd89 | ||
|
|
c316e2af69 | ||
|
|
7270aa96f0 | ||
|
|
dd823eb489 | ||
|
|
cb29305385 | ||
|
|
2dfa37dd0c | ||
|
|
b3d3284f18 | ||
|
|
73e3e133c6 | ||
|
|
9828af9f03 | ||
|
|
48aa6b5ac5 | ||
|
|
e5b3ed1461 | ||
|
|
5fad9f0ec6 | ||
|
|
f4a5507716 | ||
|
|
2ea9d1d6fd | ||
|
|
e393b1f90b | ||
|
|
2259e81bb0 | ||
|
|
65a30292a3 | ||
|
|
494eef3472 | ||
|
|
bee1e865b2 | ||
|
|
c92b049bbc | ||
|
|
c9985480fe | ||
|
|
e1b5b76d8e | ||
|
|
762a74f5f9 | ||
|
|
a7a2d8493e | ||
|
|
bc6538d209 | ||
|
|
93d841310e | ||
|
|
2ac679f17e | ||
|
|
9a3ef463f9 | ||
|
|
affc5fcd75 | ||
|
|
96bff5e974 | ||
|
|
e828e14fa5 | ||
|
|
4c6dc8ff82 | ||
|
|
75bd7c90d3 | ||
|
|
2aa3d94a98 | ||
|
|
f4ac809dff | ||
|
|
0dee534f84 | ||
|
|
ad9f2c2ccb | ||
|
|
a886d20a7b | ||
|
|
791993795a | ||
|
|
a2afdb821f | ||
|
|
47b51a8be0 | ||
|
|
7c7e7c3ce4 | ||
|
|
fec08a5ad1 | ||
|
|
3961ef61c8 | ||
|
|
84692da9a2 | ||
|
|
fb7ba52e39 | ||
|
|
898823abb2 | ||
|
|
ccefd0a43a | ||
|
|
98f0be61ed | ||
|
|
6bb1d54cb0 | ||
|
|
69a76e32b7 | ||
|
|
99d989d486 | ||
|
|
456d62224e | ||
|
|
b662fae11e | ||
|
|
0926bd2f6c | ||
|
|
3fb473ae95 | ||
|
|
603ed18a4c | ||
|
|
0307fdd296 | ||
|
|
c10dcdbfdb | ||
|
|
31d8e579c6 | ||
|
|
7b3fec0349 | ||
|
|
e32a58f6ff | ||
|
|
36cd726676 | ||
|
|
8dc08de628 | ||
|
|
0361954919 | ||
|
|
29a62575f4 | ||
|
|
547a6df2ae | ||
|
|
53d08cdf28 | ||
|
|
a033152c60 | ||
|
|
9472aaca7a | ||
|
|
6254b99e02 | ||
|
|
3d17bdb747 | ||
|
|
d2eeeb9129 | ||
|
|
1d626e0add | ||
|
|
9f4cb8c1b6 | ||
|
|
91fc5e3902 | ||
|
|
9048cd8f47 | ||
|
|
4f5e8a8836 | ||
|
|
29b05fa50f | ||
|
|
dbe1a5c988 | ||
|
|
b6c1290934 | ||
|
|
306ff3c7c5 | ||
|
|
0e43e0d7fc | ||
|
|
4da696321a | ||
|
|
a95dd1e037 | ||
|
|
012a6bbde3 | ||
|
|
32197f79f3 | ||
|
|
257d202646 | ||
|
|
c00365c19d | ||
|
|
3f9fe845e5 | ||
|
|
b166aae835 | ||
|
|
31f0097862 | ||
|
|
f78514dbd8 | ||
|
|
9fc5fe9798 | ||
|
|
6d14ac1693 | ||
|
|
b8ab573c00 | ||
|
|
75ac6808a1 | ||
|
|
6719ff93d8 | ||
|
|
6b8327faad | ||
|
|
85c8bd2277 | ||
|
|
23fff2a09f | ||
|
|
689db93c99 | ||
|
|
1114556661 | ||
|
|
f8c910a3c2 | ||
|
|
f18ae089ff | ||
|
|
4a64992054 | ||
|
|
9fefb47242 | ||
|
|
fc5cc3c43b | ||
|
|
27464b795e | ||
|
|
845558c1da | ||
|
|
31fe5aa452 | ||
|
|
b354e07ef3 | ||
|
|
50a74dac4b | ||
|
|
7558bbfe9b | ||
|
|
7518676ac9 | ||
|
|
b7b665ff48 | ||
|
|
1be941e815 | ||
|
|
d1b7eba44e | ||
|
|
38e2d5663a | ||
|
|
3db95a3e67 | ||
|
|
ef0a0d69bb | ||
|
|
4b3a3e74f8 | ||
|
|
2c4751c7b2 | ||
|
|
30941ed26d | ||
|
|
c7163b63db | ||
|
|
6e6b3dcbfe | ||
|
|
1d136a6635 | ||
|
|
0ee67d78ef | ||
|
|
7356b920d4 | ||
|
|
ce8a325c1f | ||
|
|
a2f57e4769 | ||
|
|
751f41bc5e | ||
|
|
fd406f0f82 | ||
|
|
801dddacd4 | ||
|
|
397a537eef | ||
|
|
0423c836eb | ||
|
|
3250337e70 | ||
|
|
9dcd7fffe2 | ||
|
|
30b727b138 | ||
|
|
b86d6981ab | ||
|
|
2bf6a2b100 | ||
|
|
3dc8d31d57 | ||
|
|
b308fb92c0 | ||
|
|
bc9746455e | ||
|
|
109a9c76e3 | ||
|
|
4488f3d5d3 | ||
|
|
5a7958d20e | ||
|
|
481a942b76 | ||
|
|
a601d8429d | ||
|
|
a4a2d52a6d | ||
|
|
47fa3ba7de | ||
|
|
e6bb6709b3 | ||
|
|
c421742c4f | ||
|
|
1312cc8f6e | ||
|
|
ed37763d30 | ||
|
|
583bbf65e2 | ||
|
|
5770a5caa7 | ||
|
|
722903fec3 | ||
|
|
30f1c3c1b4 | ||
|
|
ef7d146282 | ||
|
|
20667a23d3 | ||
|
|
26f05827ae | ||
|
|
b1ffe7d553 | ||
|
|
368a060529 | ||
|
|
b40bffb1f2 | ||
|
|
488ae149f7 | ||
|
|
fa3e4726b7 | ||
|
|
66a12cc8bf | ||
|
|
3e0c21e981 | ||
|
|
da270ae7d9 | ||
|
|
4624f11ba5 | ||
|
|
224bb96a98 | ||
|
|
9a6fe8eea9 | ||
|
|
aebc035ec0 | ||
|
|
bd348c328e | ||
|
|
c5f2d7b473 | ||
|
|
dc9d8d55f2 | ||
|
|
b172ba7f03 | ||
|
|
8227890808 | ||
|
|
a0963fe3fc | ||
|
|
4df30c2587 | ||
|
|
305a5fbcae | ||
|
|
4f4dcbb643 | ||
|
|
202897ba35 | ||
|
|
444689c899 | ||
|
|
98ec13f8db | ||
|
|
39f76a3a71 | ||
|
|
f181a795a6 | ||
|
|
ea2f3e07e9 | ||
|
|
8aad6eae0d | ||
|
|
e86e5fe3e7 | ||
|
|
2c2569c4f8 | ||
|
|
9ffdc9649e | ||
|
|
a5d4f2eec9 | ||
|
|
a5df40e01d | ||
|
|
0573fc97c6 | ||
|
|
1ae95f41a1 | ||
|
|
8a7af2e14d | ||
|
|
c36da89933 | ||
|
|
bbb84c2ee7 | ||
|
|
36fd4b13c0 | ||
|
|
49327000fc | ||
|
|
9c25cd7426 | ||
|
|
9767e4169c | ||
|
|
0854f9c559 | ||
|
|
e4a068d808 | ||
|
|
4c793b0df8 | ||
|
|
a021441135 | ||
|
|
29c555c394 | ||
|
|
c33d396489 | ||
|
|
f6d2ba4dae | ||
|
|
a88574204d | ||
|
|
9435bc4b7d | ||
|
|
27245cbd7b | ||
|
|
21751aa8a5 | ||
|
|
ad41948450 | ||
|
|
e32246f172 | ||
|
|
25d3a816b4 | ||
|
|
05b1a565e0 | ||
|
|
7b2623ea3c | ||
|
|
983c5243ba | ||
|
|
1958fe1e5b | ||
|
|
ca8558d9b4 | ||
|
|
1b534800a9 | ||
|
|
e91c00c9c0 | ||
|
|
a2375b4820 | ||
|
|
2e0c8e9e17 | ||
|
|
dc0ddcf9f0 | ||
|
|
a1f3c86a39 | ||
|
|
55f672eff7 | ||
|
|
8ece0346d8 | ||
|
|
b1fe1d201a | ||
|
|
5010abdc22 | ||
|
|
e4441d5021 | ||
|
|
5af0c6a7e5 | ||
|
|
b8da17106a | ||
|
|
fdf40dbf43 | ||
|
|
f3b6530969 | ||
|
|
cbc5fc94f9 | ||
|
|
dceb697355 | ||
|
|
07118fa0d2 | ||
|
|
16e6db0def | ||
|
|
64d8f6d632 | ||
|
|
180b5cba58 | ||
|
|
bac416e907 | ||
|
|
cb674a1572 | ||
|
|
960b14fa20 | ||
|
|
a9f57d4205 | ||
|
|
13330b6950 | ||
|
|
1ebcc9beee | ||
|
|
55e1bbf2b9 | ||
|
|
f2dfa1e475 | ||
|
|
fcd53e772a | ||
|
|
8b9d7ef8f3 | ||
|
|
d8406a8cfe | ||
|
|
4a9ef581e5 | ||
|
|
a52db1f261 | ||
|
|
8e16174ce7 | ||
|
|
c748bb5d7a | ||
|
|
3cc8f0d818 | ||
|
|
f96eeeda6b | ||
|
|
d1d8904376 | ||
|
|
3b329fe687 | ||
|
|
9eb1b4ac9f | ||
|
|
c4c0bd7383 | ||
|
|
1e9de5832d | ||
|
|
f2b17cdd9d | ||
|
|
7bfd6c2439 | ||
|
|
0e8d5f0266 | ||
|
|
32add8f046 | ||
|
|
f661f00277 | ||
|
|
2a1999fe20 | ||
|
|
4d66431aad | ||
|
|
767f0d91f4 | ||
|
|
a3428e3477 | ||
|
|
614131b7bf | ||
|
|
9b0681f3b8 | ||
|
|
ecf8fb7a47 | ||
|
|
04bfb45a97 | ||
|
|
d90ce30452 | ||
|
|
ab21600ca6 | ||
|
|
728ea26204 | ||
|
|
373cd3b3ae | ||
|
|
f4e0258b09 | ||
|
|
d50360a69a | ||
|
|
351922c81f | ||
|
|
9518f43866 | ||
|
|
2c1ce3d4e6 | ||
|
|
12116c3261 | ||
|
|
fbc84e8aa1 | ||
|
|
6dab1e4f37 | ||
|
|
650a143602 | ||
|
|
9b6027fe78 | ||
|
|
0e30e05ce8 | ||
|
|
eea952fa78 | ||
|
|
6071a1ee3b | ||
|
|
a801b7b9f4 | ||
|
|
c6e3f0ae0a | ||
|
|
a43b03d3db | ||
|
|
12b0fa57ad | ||
|
|
d9e304f0ef | ||
|
|
842b92cca7 | ||
|
|
485f0ec9c8 | ||
|
|
5e3b5fc9a7 | ||
|
|
7c63541cad | ||
|
|
238e089d74 | ||
|
|
8991bc9f62 | ||
|
|
7a3f3a8905 | ||
|
|
e4085e03eb | ||
|
|
4b0c366e5f | ||
|
|
ea97240d09 | ||
|
|
12de531abb | ||
|
|
c3876ce3bf | ||
|
|
cbbfc3a114 | ||
|
|
ad2bfc9abd | ||
|
|
528461412e | ||
|
|
64db679390 | ||
|
|
77a8b3b7d2 | ||
|
|
7007e76ab5 | ||
|
|
3c970063a9 | ||
|
|
b70830015e | ||
|
|
b43f2c8b3a | ||
|
|
c311da16f3 | ||
|
|
37608a338c | ||
|
|
b07288e674 | ||
|
|
707698faab | ||
|
|
2e70d132d0 | ||
|
|
30c5b31e21 | ||
|
|
77ff6cb714 | ||
|
|
ea13c51b7d | ||
|
|
3ed763b884 | ||
|
|
10e1e170b7 | ||
|
|
ffa62afc66 | ||
|
|
f794329913 | ||
|
|
f9a35c7661 | ||
|
|
ed496f3462 | ||
|
|
6accdae232 | ||
|
|
96efcc6c0d | ||
|
|
bf72d7bb5a | ||
|
|
dadffb1081 | ||
|
|
78dc567226 | ||
|
|
362ce4f4f9 | ||
|
|
ab35cd7b10 | ||
|
|
15f4ad7cd1 | ||
|
|
cbfb92041f | ||
|
|
a506c67cac | ||
|
|
788e0412f6 | ||
|
|
18b37ce3e3 | ||
|
|
a15e6748c7 | ||
|
|
c6d0539fd2 | ||
|
|
3eb3867944 | ||
|
|
810315b0e2 | ||
|
|
b461fc2536 | ||
|
|
7e63977ba0 | ||
|
|
78dec892cf | ||
|
|
9ea6628b5c | ||
|
|
465df2e9be | ||
|
|
61ef926849 | ||
|
|
7fa38c593e | ||
|
|
41c6d1cd9a | ||
|
|
cf3893dc49 | ||
|
|
a2fbe92a25 | ||
|
|
e1754707d8 | ||
|
|
cd380a53b3 | ||
|
|
a8c29fd1a2 | ||
|
|
6b871e7949 | ||
|
|
1b5fdb6645 | ||
|
|
fe9d877cdf | ||
|
|
60e7aa8f03 | ||
|
|
18e2d3e59c | ||
|
|
d68fcb08b2 | ||
|
|
1f6baefdc3 | ||
|
|
71efce32c1 | ||
|
|
3626c9cdc8 | ||
|
|
a23b761304 | ||
|
|
3fd27e4913 | ||
|
|
b3f152b716 | ||
|
|
df381f3a79 | ||
|
|
2dec9db310 | ||
|
|
d50dc4c9f6 | ||
|
|
ed8b563f20 | ||
|
|
2a73aa731d | ||
|
|
4dd1c13bd8 | ||
|
|
c1c9fe22df | ||
|
|
06a6b7a2eb | ||
|
|
b814dd824f | ||
|
|
ce234bdb59 | ||
|
|
13a46a44a8 | ||
|
|
dc78b00c3c | ||
|
|
48ae4bf813 | ||
|
|
a50040e2d5 | ||
|
|
2c9a56a8df | ||
|
|
021320b292 | ||
|
|
9d3662c3ea | ||
|
|
8e580457a5 | ||
|
|
5350658dab | ||
|
|
1ec0ac50a5 | ||
|
|
635bfce198 | ||
|
|
1307d2d7e8 | ||
|
|
d21141fefe | ||
|
|
0ec0e5a9dd | ||
|
|
9415d7c61f | ||
|
|
42188af02b | ||
|
|
e9581bcf15 | ||
|
|
6afe4f51c6 | ||
|
|
f623746d6c | ||
|
|
1ce4d66e74 | ||
|
|
3735d5c537 | ||
|
|
f3b1d2dfb3 | ||
|
|
7f7d2633cd | ||
|
|
afd95e3d5c | ||
|
|
8f72545894 | ||
|
|
d0d447deac | ||
|
|
53a8683788 | ||
|
|
81491a8d03 | ||
|
|
83504754ac | ||
|
|
2068c2c169 | ||
|
|
dbac121a90 | ||
|
|
b974938a33 | ||
|
|
06cb88a1a1 | ||
|
|
a6195d284c | ||
|
|
5b8850e8a3 | ||
|
|
57546a07fc | ||
|
|
d7709490ce | ||
|
|
3e6c6e513b | ||
|
|
30858780cf | ||
|
|
a7ddf4575a | ||
|
|
470231c9d1 | ||
|
|
282e336ac4 | ||
|
|
658829e4ff | ||
|
|
a0ff565220 | ||
|
|
7e48c5dedc | ||
|
|
03436b7f8f | ||
|
|
3f5eedb83d | ||
|
|
234ad4bdd7 | ||
|
|
c7923393be | ||
|
|
d4548b2f9a | ||
|
|
f6e8af186c | ||
|
|
58153635da | ||
|
|
5358509825 | ||
|
|
1ab0232d96 | ||
|
|
66860f1848 | ||
|
|
625f823f46 | ||
|
|
6263ab7e10 | ||
|
|
7db991db9d | ||
|
|
d75782892e | ||
|
|
cb7adc8ced | ||
|
|
7c3ba80270 | ||
|
|
76c39edc8b | ||
|
|
c20a300eea | ||
|
|
de3902a9c9 | ||
|
|
8bca671e9f | ||
|
|
54301a6a17 | ||
|
|
f06b7c0807 | ||
|
|
43c02cf7a7 | ||
|
|
3a1568e884 | ||
|
|
14753a14e7 | ||
|
|
227e8dcc8d | ||
|
|
97fd2b2718 | ||
|
|
f30e36d7bb | ||
|
|
d640bc66f5 | ||
|
|
a2331a2575 | ||
|
|
26a2c3c266 | ||
|
|
ceafcba88f | ||
|
|
d7182b5a6e | ||
|
|
93ec1172fe | ||
|
|
609477a373 | ||
|
|
1d49af10a7 | ||
|
|
327ed837c2 | ||
|
|
d406e2aeab | ||
|
|
0991cc8a6f | ||
|
|
ac6330fac8 | ||
|
|
29f0d229d3 | ||
|
|
3dd11f7b52 | ||
|
|
ddb1a021bb | ||
|
|
565aa405be | ||
|
|
907465f891 | ||
|
|
250485c826 | ||
|
|
3a3f22b4e5 | ||
|
|
a3628d0d49 | ||
|
|
462016e51e | ||
|
|
199b81183b | ||
|
|
342d7f7209 | ||
|
|
9c2d121fc9 | ||
|
|
7728819133 | ||
|
|
a5d5d987d2 | ||
|
|
fae5d80f0a | ||
|
|
2369e371be | ||
|
|
e3b71b647f | ||
|
|
e224ea38b3 | ||
|
|
8ec37bcd44 | ||
|
|
6b7f6f4cc7 | ||
|
|
d767ef31a5 | ||
|
|
fcf776fe07 | ||
|
|
58060dda09 | ||
|
|
8cfc8d66be | ||
|
|
9dcc8fdd0d | ||
|
|
828477b255 | ||
|
|
4eae1fedf7 | ||
|
|
1051e4cf47 | ||
|
|
80a95cb404 | ||
|
|
ab630cfbc6 | ||
|
|
c13e14080c | ||
|
|
4abbee99cc | ||
|
|
45d935eda3 | ||
|
|
b4c5efde50 | ||
|
|
cba9dc1c2c | ||
|
|
938647123c | ||
|
|
9f24f6474e | ||
|
|
814e39644c | ||
|
|
eb51be0732 | ||
|
|
56a717dcc6 | ||
|
|
ee353ccb66 | ||
|
|
33cea0e5b6 | ||
|
|
78a491a976 | ||
|
|
9553be04e4 | ||
|
|
81928f55a2 | ||
|
|
4e56bf07f3 | ||
|
|
9f424f03c3 | ||
|
|
1e14b3bf28 | ||
|
|
51e7a31f48 | ||
|
|
9847fd9d4d | ||
|
|
24ef44291f | ||
|
|
ebb0c5a6d8 | ||
|
|
1dee462175 | ||
|
|
dc82b8b766 | ||
|
|
73888e7669 | ||
|
|
46a010cc8f | ||
|
|
96e4de4594 | ||
|
|
70616b2ec5 | ||
|
|
22ab07d88e | ||
|
|
70045fca29 | ||
|
|
f6ec6c76db | ||
|
|
37b900c56a | ||
|
|
e26ec4d9e0 | ||
|
|
b31778bdd8 | ||
|
|
526c4c9a58 | ||
|
|
3c1dda0c39 | ||
|
|
25c5057171 | ||
|
|
e456155562 | ||
|
|
f2c7e4848e | ||
|
|
b4407963ad | ||
|
|
85a4691229 | ||
|
|
8e9c3cc56a | ||
|
|
7cdaefe3b0 | ||
|
|
aedb25cde6 | ||
|
|
582bc737cb | ||
|
|
c4091584c3 | ||
|
|
22ed1ef50a | ||
|
|
41e2321b93 | ||
|
|
d8add139e1 | ||
|
|
04a8d960a9 | ||
|
|
2c6a8f73fa | ||
|
|
71883d7164 | ||
|
|
d4fe748814 | ||
|
|
369d923532 | ||
|
|
6be4a4a48d | ||
|
|
7442ddd386 | ||
|
|
761ffac127 | ||
|
|
5ca1dc3703 | ||
|
|
a18c6e815c | ||
|
|
022afab1ca | ||
|
|
2cbb7db66d | ||
|
|
e84173be8f | ||
|
|
bd34a3bcd4 | ||
|
|
13076fcef6 | ||
|
|
6f48017761 | ||
|
|
7fb07879bb | ||
|
|
ecbaf52156 | ||
|
|
6a6a84b0c5 | ||
|
|
3486200b2c | ||
|
|
f6b3a8fdca | ||
|
|
058a6fd9c9 | ||
|
|
e1783e3af8 | ||
|
|
e161d3cd3c | ||
|
|
318f935b43 | ||
|
|
3dc245245d |
98
.github/ISSUE_TEMPLATE/bug_report.md
vendored
98
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -2,32 +2,106 @@
|
||||
name: Bug report
|
||||
about: Topgrade is misbehaving
|
||||
title: ''
|
||||
labels: ''
|
||||
type: Bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- If you're here to report about a "No asset found" error, please make sure that an hour has been passed since the last release was made. -->
|
||||
<!--
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
Please make sure to
|
||||
[search for existing issues](https://github.com/topgrade-rs/topgrade/issues)
|
||||
before filing a new one!
|
||||
|
||||
## What did you expect to happen?
|
||||
Questions labeled with `Optional` can be skipped.
|
||||
|
||||
If you're here to report about a "No asset found" error, please make sure that
|
||||
an hour has been passed since the last release was made.
|
||||
-->
|
||||
|
||||
## What actually happened?
|
||||
## Checklist
|
||||
|
||||
- [ ] I have searched the issue tracker for relevant or duplicate issues.
|
||||
|
||||
## Additional Details
|
||||
- Which operating system or Linux distribution are you using?
|
||||
- How did you install Topgrade?
|
||||
- Which version are you running? <!-- Check with `topgrade -V` -->
|
||||
## Erroneous Behavior
|
||||
|
||||
<!--
|
||||
Run `topgrade --dry-run` to see which commands Topgrade is running.
|
||||
If the command seems wrong and you know why please tell us so.
|
||||
If the command seems fine try to run it yourself and tell us if you got a different result from Topgrade.
|
||||
What actually happened?
|
||||
-->
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
<!--
|
||||
Describe the expected behavior.
|
||||
-->
|
||||
|
||||
## Steps to reproduce
|
||||
|
||||
<!--
|
||||
A minimal example to reproduce the issue.
|
||||
-->
|
||||
|
||||
## Possible Cause (Optional)
|
||||
|
||||
<!--
|
||||
If you know the possible cause of the issue, please tell us.
|
||||
-->
|
||||
|
||||
## Problem persists without calling from topgrade
|
||||
|
||||
<!--
|
||||
Execute the erroneous command directly to see if the problem persists.
|
||||
-->
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
## Ran through `Remote Execution`
|
||||
|
||||
<!--
|
||||
Did you run topgrade through `Remote Execution`?
|
||||
-->
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
If yes, does the issue still occur when you run topgrade directly in your
|
||||
remote host?
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
## Configuration file (Optional)
|
||||
|
||||
<!--
|
||||
Paste your configuration file inside the code block if you think this issue is
|
||||
related to configuration.
|
||||
-->
|
||||
|
||||
```toml
|
||||
|
||||
```
|
||||
|
||||
## Additional Details
|
||||
|
||||
- Operation System/Version
|
||||
<!-- For example, Fedora Linux 38 -->
|
||||
|
||||
- Installation
|
||||
<!--
|
||||
How did you install topgrade: build from repo / crates.io (cargo install topgrade)
|
||||
/ package manager (which one) / other (describe)
|
||||
-->
|
||||
|
||||
- Topgrade version (`topgrade -V`)
|
||||
|
||||
## Verbose Output (`topgrade -v`)
|
||||
|
||||
<!--
|
||||
Paste the verbose output into the pre-tags
|
||||
-->
|
||||
|
||||
<details>
|
||||
<!-- Paste the output of the problematic command with `-v` into the pre-tags -->
|
||||
<pre>
|
||||
|
||||
</pre>
|
||||
|
||||
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,21 +1,23 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Can you please support...?
|
||||
name: General feature request
|
||||
about: Suggest a general feature, or feature within an already existing step
|
||||
title: ''
|
||||
labels: ''
|
||||
type: Feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## I want to suggest a new step
|
||||
### Which tool is this about? Where is its repository?
|
||||
### Which operating systems are supported by this tool?
|
||||
### What should Topgrade do to figure out if the tool needs to be invoked?
|
||||
### Which exact commands should Topgrade run?
|
||||
## Checklist
|
||||
|
||||
- [ ] I have searched the issue tracker for relevant or duplicate issues.
|
||||
|
||||
## I want to suggest some general feature
|
||||
|
||||
Topgrade should...
|
||||
|
||||
## More information
|
||||
|
||||
<!-- Assuming that someone else implements the feature,
|
||||
please state if you know how to test it from a side branch of Topgrade. -->
|
||||
|
||||
- [ ] I am able and willing to implement this feature myself
|
||||
|
||||
30
.github/ISSUE_TEMPLATE/step_request.md
vendored
Normal file
30
.github/ISSUE_TEMPLATE/step_request.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: New step request
|
||||
about: Suggest a new step/package manager to update
|
||||
title: ''
|
||||
type: Feature
|
||||
labels: request step
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] I have searched the issue tracker for relevant or duplicate issues.
|
||||
|
||||
## I want to suggest a new step
|
||||
|
||||
* Which tool is this about? Where is its repository?
|
||||
* Which operating systems are supported by this tool?
|
||||
* What should Topgrade do to figure out if the tool needs to be invoked?
|
||||
* Which exact commands should Topgrade run?
|
||||
* Does it have a `--dry-run` option? i.e., print what should be done and exit
|
||||
* Does it need the user to confirm the execution? And does it provide a `--yes`
|
||||
option to skip this?
|
||||
|
||||
## More information
|
||||
|
||||
<!-- Assuming that someone else implements the step,
|
||||
please state if you know how to test it from a side branch of Topgrade. -->
|
||||
|
||||
- [ ] I am able and willing to implement this step myself
|
||||
20
.github/PULL_REQUEST_TEMPLATE.md
vendored
20
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,12 +1,18 @@
|
||||
## Standards checklist:
|
||||
## What does this PR do
|
||||
|
||||
- [ ] The PR title is descriptive.
|
||||
- [ ] The code compiles (`cargo build`)
|
||||
- [ ] The code passes rustfmt (`cargo fmt`)
|
||||
- [ ] The code passes clippy (`cargo clippy`)
|
||||
- [ ] The code passes tests (`cargo test`)
|
||||
## Standards checklist
|
||||
|
||||
- [ ] The PR title is descriptive
|
||||
- [ ] I have read `CONTRIBUTING.md`
|
||||
- [ ] *Optional:* I have tested the code myself
|
||||
- [ ] I also tested that Topgrade skips the step where needed
|
||||
- [ ] If this PR introduces new user-facing messages they are translated
|
||||
|
||||
## For new steps
|
||||
|
||||
- [ ] *Optional:* Topgrade skips this step where needed
|
||||
- [ ] *Optional:* The `--dry-run` option works with this step
|
||||
- [ ] *Optional:* The `--yes` option works with this step if it is supported by
|
||||
the underlying command
|
||||
|
||||
If you developed a feature or a bug fix for someone else and you do not have the
|
||||
means to test it, please tag this person here.
|
||||
|
||||
69
.github/workflows/check-and-lint.yaml
vendored
69
.github/workflows/check-and-lint.yaml
vendored
@@ -1,69 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
|
||||
name: Check and Lint
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest, macos-latest, windows-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
|
||||
fmt:
|
||||
name: Rustfmt
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest, macos-latest, windows-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- run: rustup component add rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
clippy:
|
||||
name: Clippy
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest, macos-latest, windows-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
components: clippy
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
with:
|
||||
command: clippy
|
||||
args: --all-targets --locked -- -D warnings
|
||||
name: Clippy Output
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
with:
|
||||
command: clippy
|
||||
args: --all-targets --locked --all-features -- -D warnings
|
||||
name: Clippy (All features) Output
|
||||
27
.github/workflows/check-semver.yml
vendored
27
.github/workflows/check-semver.yml
vendored
@@ -1,27 +0,0 @@
|
||||
on:
|
||||
release:
|
||||
types: [published, edited]
|
||||
|
||||
name: Check SemVer compliance
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly-2022-08-03
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
semver:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: --git https://github.com/rust-lang/rust-semverver
|
||||
- run: eval "current_version=$(grep -e '^version = .*$' Cargo.toml | cut -d ' ' -f 3)"
|
||||
- run: cargo semver | tee semver_out
|
||||
- run: (head -n 1 semver_out | grep "\-> $current_version") || (echo "versioning mismatch" && return 1)
|
||||
27
.github/workflows/check_config_creation_if_not_exists.yml
vendored
Normal file
27
.github/workflows/check_config_creation_if_not_exists.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Check config file creation if not exists
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
TestConfig:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- run: |
|
||||
CONFIG_PATH=~/.config/topgrade.toml;
|
||||
if [ -f "$CONFIG_PATH" ]; then rm $CONFIG_PATH; fi
|
||||
cargo build;
|
||||
TOPGRADE_SKIP_BRKC_NOTIFY=true ./target/debug/topgrade --dry-run --only system;
|
||||
stat $CONFIG_PATH;
|
||||
27
.github/workflows/check_i18n.yml
vendored
Normal file
27
.github/workflows/check_i18n.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
name: Check i18n
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check_locale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install checker
|
||||
# Build it with the dev profile as this is faster and the checker still works
|
||||
run: |
|
||||
cargo install --git https://github.com/topgrade-rs/topgrade_i18n_locale_checker --profile dev
|
||||
|
||||
- name: Run the checker
|
||||
run: topgrade_i18n_locale_checker --locale-file ./locales/app.yml --rust-src-to-check ./src
|
||||
37
.github/workflows/check_security_vulnerability.yml
vendored
Normal file
37
.github/workflows/check_security_vulnerability.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
|
||||
name: Check Security Vulnerability
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: DevSkim
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run DevSkim scanner
|
||||
uses: microsoft/DevSkim-Action@4b5047945a44163b94642a1cecc0d93a3f428cc6 # v1.0.16
|
||||
|
||||
- name: Upload DevSkim scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
||||
with:
|
||||
sarif_file: devskim-results.sarif
|
||||
165
.github/workflows/ci.yml
vendored
Normal file
165
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,165 @@
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
name: CI
|
||||
|
||||
env:
|
||||
CROSS_VER: '0.2.5'
|
||||
CARGO_NET_RETRY: 3
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
fmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run cargo fmt
|
||||
env:
|
||||
TERM: xterm-256color
|
||||
run: |
|
||||
rustup component add rustfmt
|
||||
cargo fmt --all -- --check
|
||||
|
||||
custom-checks:
|
||||
name: Custom checks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Check if `Step` enum is sorted
|
||||
run: |
|
||||
ENUM_NAME="Step"
|
||||
FILE="src/step.rs"
|
||||
awk "/enum $ENUM_NAME/,/}/" "$FILE" | \
|
||||
grep -E '^\s*[A-Za-z_][A-Za-z0-9_]*\s*,?$' | \
|
||||
sed 's/[, ]//g' > original.txt
|
||||
sort original.txt > sorted.txt
|
||||
diff original.txt sorted.txt
|
||||
|
||||
- name: Check if `Step::run()`'s match is sorted
|
||||
run: |
|
||||
FILE="src/step.rs"
|
||||
awk '/[[:alpha:]] =>/{print $1}' $FILE > original.txt
|
||||
sort original.txt > sorted.txt
|
||||
diff original.txt sorted.txt
|
||||
|
||||
- name: Check if `default_steps` contains every step
|
||||
run: |
|
||||
# Extract all variants from enum Step
|
||||
all_variants=$(sed -n '/^pub enum Step {/,/^}/p' src/step.rs | grep -Po '^\s*\K[A-Z][A-Za-z0-9_]*' | sort)
|
||||
|
||||
# Extract variants used inside default_steps
|
||||
used_variants=$(sed -n '/^pub(crate) fn default_steps()/,/^}/p' src/step.rs | \
|
||||
grep -Po '\b[A-Z][A-Za-z0-9_]*\b' | \
|
||||
grep -Fx -f <(echo "$all_variants") | \
|
||||
sort)
|
||||
|
||||
# Check for missing variants
|
||||
missing=$(comm -23 <(echo "$all_variants") <(echo "$used_variants"))
|
||||
if [[ -z "$missing" ]]; then
|
||||
echo "All variants are used."
|
||||
else
|
||||
echo "Missing variants:"
|
||||
echo "$missing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for duplicates
|
||||
duplicates=$(echo "$used_variants" | uniq -c | awk '$1 > 1 {print $2}')
|
||||
if [[ -z "$duplicates" ]]; then
|
||||
echo "No duplicates found."
|
||||
else
|
||||
echo "Duplicates found:"
|
||||
echo "$duplicates"
|
||||
# We allow duplicates, but lets keep this check for potential future usefulness
|
||||
# exit 1
|
||||
fi
|
||||
|
||||
main:
|
||||
needs: [ fmt, custom-checks ]
|
||||
name: ${{ matrix.target_name }} (check, clippy)
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: x86_64-linux-android
|
||||
target_name: Android
|
||||
use_cross: true
|
||||
os: ubuntu-latest
|
||||
|
||||
- target: x86_64-unknown-freebsd
|
||||
target_name: FreeBSD
|
||||
use_cross: true
|
||||
os: ubuntu-latest
|
||||
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
target_name: Linux
|
||||
os: ubuntu-latest
|
||||
|
||||
- target: x86_64-apple-darwin
|
||||
target_name: macOS-x86_64
|
||||
os: macos-15-intel
|
||||
|
||||
- target: aarch64-apple-darwin
|
||||
target_name: macOS-aarch64
|
||||
os: macos-latest
|
||||
|
||||
- target: x86_64-unknown-netbsd
|
||||
target_name: NetBSD
|
||||
use_cross: true
|
||||
os: ubuntu-latest
|
||||
|
||||
- target: x86_64-pc-windows-msvc
|
||||
target_name: Windows
|
||||
os: windows-latest
|
||||
env:
|
||||
cargo_cmd: ${{ matrix.use_cross == true && 'cross' || 'cargo' }}
|
||||
matrix_target: ${{ matrix.target }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Rust Cache
|
||||
uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
with:
|
||||
prefix-key: ${{ matrix.target }}
|
||||
|
||||
- name: Setup cross
|
||||
if: matrix.use_cross == true
|
||||
run: |
|
||||
curl -fL --retry 3 "https://github.com/cross-rs/cross/releases/download/v${CROSS_VER}/cross-x86_64-unknown-linux-musl.tar.gz" | tar vxz -C /usr/local/bin
|
||||
|
||||
- name: Run cargo/cross check
|
||||
run: |
|
||||
"${cargo_cmd}" check --locked --target "${matrix_target}"
|
||||
|
||||
- name: Run cargo/cross clippy
|
||||
run: |
|
||||
rustup component add clippy
|
||||
"${cargo_cmd}" clippy --locked --target "${matrix_target}" --all-features -- -D warnings
|
||||
|
||||
- name: Run cargo test
|
||||
# ONLY run test with cargo
|
||||
if: matrix.use_cross == false
|
||||
run: |
|
||||
cargo test --locked --target "${matrix_target}"
|
||||
29
.github/workflows/crates-publish.yml
vendored
29
.github/workflows/crates-publish.yml
vendored
@@ -1,29 +0,0 @@
|
||||
on:
|
||||
# workflow_run:
|
||||
# workflows: ["Check SemVer compliance"]
|
||||
# types:
|
||||
# - completed
|
||||
release:
|
||||
types: [published, edited]
|
||||
|
||||
name: Publish to crates.io on release
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: katyo/publish-crates@v1
|
||||
with:
|
||||
dry-run: true
|
||||
check-repo: ${{ github.event_name == 'push' }}
|
||||
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
ignore-unpublished-changes: true
|
||||
303
.github/workflows/create_release_assets.yml
vendored
Normal file
303
.github/workflows/create_release_assets.yml
vendored
Normal file
@@ -0,0 +1,303 @@
|
||||
name: Publish release files for CD native and non-cd-native environments
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [ release-created ]
|
||||
|
||||
permissions:
|
||||
# Write permissions to call the repository dispatch
|
||||
contents: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
# Publish release files for CD native environments
|
||||
native_build:
|
||||
permissions:
|
||||
# Use to sign the release artifacts
|
||||
id-token: write
|
||||
# Used to upload release artifacts
|
||||
contents: write
|
||||
# Used to generate artifact attestations
|
||||
attestations: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Use the Ubuntu 22.04 image to link with a low version of glibc
|
||||
#
|
||||
# https://github.com/topgrade-rs/topgrade/issues/1095
|
||||
platform: [ ubuntu-22.04, macos-latest, macos-15-intel, windows-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
tag: ${{ github.event.client_payload.tag }}
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install needed components
|
||||
run: |
|
||||
rustup component add rustfmt
|
||||
rustup component add clippy
|
||||
|
||||
- name: Install cargo-deb
|
||||
run: cargo install cargo-deb
|
||||
if: ${{ startsWith(matrix.platform, 'ubuntu-') }}
|
||||
shell: bash
|
||||
|
||||
- name: Check format
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
- name: Run clippy
|
||||
run: cargo clippy --all-targets --locked -- -D warnings
|
||||
|
||||
- name: Run clippy (All features)
|
||||
run: cargo clippy --all-targets --locked --all-features -- -D warnings
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test
|
||||
|
||||
# Used `https://github.com/BurntSushi/ripgrep/blob/master/.github/workflows/release.yml`
|
||||
# as a reference.
|
||||
- name: Build debug binary to create release assets
|
||||
shell: bash
|
||||
run: |
|
||||
cargo build --all-features
|
||||
bin="target/debug/topgrade"
|
||||
echo "BIN=$bin" >> $GITHUB_ENV
|
||||
|
||||
- name: Create deployment directory
|
||||
shell: bash
|
||||
run: |
|
||||
dir=deployment/deb
|
||||
mkdir -p "$dir"
|
||||
echo "DEPLOY_DIR=$dir" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate man page and shell completions
|
||||
shell: bash
|
||||
run: |
|
||||
"$BIN" --gen-manpage > "$DEPLOY_DIR/topgrade.1"
|
||||
"$BIN" --gen-completion bash > "$DEPLOY_DIR/topgrade.bash"
|
||||
"$BIN" --gen-completion fish > "$DEPLOY_DIR/topgrade.fish"
|
||||
"$BIN" --gen-completion zsh > "$DEPLOY_DIR/_topgrade"
|
||||
|
||||
- name: Build in Release profile with all features enabled
|
||||
run: cargo build --release --all-features
|
||||
|
||||
- name: Rename Release (Unix)
|
||||
run: |
|
||||
cargo install default-target
|
||||
mkdir -p assets
|
||||
FILENAME=topgrade-${tag}-$(default-target)
|
||||
mv target/release/topgrade assets
|
||||
cd assets
|
||||
tar --format=ustar -czf $FILENAME.tar.gz topgrade
|
||||
rm topgrade
|
||||
ls .
|
||||
if: ${{ matrix.platform != 'windows-latest' }}
|
||||
shell: bash
|
||||
|
||||
- name: Build Debian-based system binary and create package
|
||||
# First remove the binary built by previous steps
|
||||
# because we don't want the auto-update feature,
|
||||
# then build the new binary without auto-updating.
|
||||
run: |
|
||||
rm -rf target/release
|
||||
cargo build --release
|
||||
cargo deb --no-build --no-strip
|
||||
if: ${{ startsWith(matrix.platform, 'ubuntu-') }}
|
||||
shell: bash
|
||||
|
||||
- name: Move Debian-based system package
|
||||
run: |
|
||||
mkdir -p assets
|
||||
mv target/debian/*.deb assets
|
||||
if: ${{ startsWith(matrix.platform, 'ubuntu-') }}
|
||||
shell: bash
|
||||
|
||||
- name: Rename Release (Windows)
|
||||
run: |
|
||||
cargo install default-target
|
||||
mkdir assets
|
||||
FILENAME=topgrade-${tag}-$(default-target)
|
||||
mv target/release/topgrade.exe assets/topgrade.exe
|
||||
cd assets
|
||||
powershell Compress-Archive -Path * -Destination ${FILENAME}.zip
|
||||
rm topgrade.exe
|
||||
ls .
|
||||
if: ${{ matrix.platform == 'windows-latest' }}
|
||||
shell: bash
|
||||
|
||||
- name: Upload assets
|
||||
run: |
|
||||
gh release upload "${tag}" assets/*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Generate artifact attestations
|
||||
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
|
||||
with:
|
||||
subject-path: assets/*
|
||||
|
||||
# Publish release files for non-CD-native environments
|
||||
cross_build:
|
||||
permissions:
|
||||
# Use to sign the release artifacts
|
||||
id-token: write
|
||||
# Used to upload release artifacts
|
||||
contents: write
|
||||
# Used to generate artifact attestations
|
||||
attestations: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
[
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"armv7-unknown-linux-gnueabihf",
|
||||
"x86_64-unknown-linux-musl",
|
||||
"aarch64-unknown-linux-musl",
|
||||
"x86_64-unknown-freebsd",
|
||||
]
|
||||
# Run this one on an older version as well, to limit glibc to 2.34 instead of 2.39.
|
||||
# Even though this is cross-compiled, it links to the libc6-<arch>-cross installed on the host
|
||||
# (see the apt-get install calls below)
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
matrix_target: ${{ matrix.target }}
|
||||
tag: ${{ github.event.client_payload.tag }}
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install needed components
|
||||
run: |
|
||||
rustup component add rustfmt
|
||||
rustup component add clippy
|
||||
|
||||
- name: Install cargo-deb cross compilation dependencies
|
||||
run: sudo apt-get install libc6-arm64-cross libgcc-s1-arm64-cross
|
||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
|
||||
shell: bash
|
||||
|
||||
- name: Install cargo-deb cross compilation dependencies for armv7
|
||||
run: sudo apt-get install libc6-armhf-cross libgcc-s1-armhf-cross
|
||||
if: ${{ matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
||||
shell: bash
|
||||
|
||||
- name: Install cargo-deb
|
||||
run: cargo install cargo-deb
|
||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
||||
shell: bash
|
||||
|
||||
- name: install targets
|
||||
run: rustup target add "${matrix_target}"
|
||||
|
||||
- name: install cross
|
||||
# Install from source to fix `ld: cannot find -lgeom` for freebsd build
|
||||
run: cargo +stable install --git https://github.com/cross-rs/cross cross
|
||||
|
||||
- name: Run clippy
|
||||
run: cross clippy --all-targets --locked --target "${matrix_target}" -- -D warnings
|
||||
|
||||
- name: Run clippy (All features)
|
||||
run: cross clippy --locked --all-features --target "${matrix_target}" -- -D warnings
|
||||
|
||||
- name: Run tests
|
||||
run: cross test --target "${matrix_target}"
|
||||
# Running tests on FreeBSD is impossible; see https://github.com/cross-rs/cross/wiki/FAQ#running-bsd-tests
|
||||
# Not that this is *NOT* the same as the original issue with `ld: cannot find -lgeom`, but a new issue:
|
||||
# error: test failed, to rerun pass `--lib`
|
||||
# Caused by:
|
||||
# could not execute process `/target/x86_64-unknown-freebsd/debug/deps/topgrade-9b1670d87ca863dd` (never executed)
|
||||
# Caused by:
|
||||
# No such file or directory (os error 2)
|
||||
# TODO: I have not tested this in GHA yet, only locally
|
||||
if: ${{ matrix.target != 'x86_64-unknown-freebsd' }}
|
||||
|
||||
# Used `https://github.com/BurntSushi/ripgrep/blob/master/.github/workflows/release.yml`
|
||||
# as a reference.
|
||||
- name: Build debug binary to create release assets
|
||||
shell: bash
|
||||
run: |
|
||||
# This build is not using the target arch since this binary is only needed in CI. It needs
|
||||
# to be the compiled for the runner since it has the run the binary to generate completion
|
||||
# scripts.
|
||||
cargo build --all-features
|
||||
bin="target/debug/topgrade"
|
||||
echo "BIN=$bin" >> $GITHUB_ENV
|
||||
|
||||
- name: Create deployment directory
|
||||
shell: bash
|
||||
run: |
|
||||
dir=deployment/deb
|
||||
mkdir -p "$dir"
|
||||
echo "DEPLOY_DIR=$dir" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate man page and shell completions
|
||||
shell: bash
|
||||
run: |
|
||||
"$BIN" --gen-manpage > "$DEPLOY_DIR/topgrade.1"
|
||||
"$BIN" --gen-completion bash > "$DEPLOY_DIR/topgrade.bash"
|
||||
"$BIN" --gen-completion fish > "$DEPLOY_DIR/topgrade.fish"
|
||||
"$BIN" --gen-completion zsh > "$DEPLOY_DIR/_topgrade"
|
||||
|
||||
- name: Build in Release profile with all features enabled
|
||||
run: cross build --release --all-features --target "${matrix_target}"
|
||||
|
||||
- name: Rename Release
|
||||
run: |
|
||||
mkdir -p assets
|
||||
FILENAME=topgrade-${tag}-${matrix_target}
|
||||
mv "target/${matrix_target}/release/topgrade" assets
|
||||
cd assets
|
||||
tar --format=ustar -czf "$FILENAME.tar.gz" topgrade
|
||||
rm topgrade
|
||||
ls .
|
||||
|
||||
- name: Build Debian-based system package without autoupdate feature
|
||||
# First remove the binary built by previous steps
|
||||
# because we don't want the auto-update feature,
|
||||
# then build the new binary without auto-updating.
|
||||
run: |
|
||||
rm -rf "target/${matrix_target}"
|
||||
cross build --release --target "${matrix_target}"
|
||||
cargo deb --target="${matrix_target}" --no-build --no-strip
|
||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
||||
shell: bash
|
||||
|
||||
- name: Move Debian-based system package
|
||||
run: |
|
||||
mkdir -p assets
|
||||
mv target/"${matrix_target}"/debian/*.deb assets
|
||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
||||
shell: bash
|
||||
|
||||
|
||||
- name: Upload assets
|
||||
run:
|
||||
gh release upload "${tag}" assets/*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Generate artifact attestations
|
||||
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
|
||||
with:
|
||||
subject-path: assets/*
|
||||
|
||||
triggers:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ native_build, cross_build ]
|
||||
env:
|
||||
tag: ${{ github.event.client_payload.tag }}
|
||||
steps:
|
||||
- name: Trigger workflows
|
||||
run: |
|
||||
gh api "repos/${GITHUB_REPOSITORY}/dispatches" \
|
||||
-f "event_type=release-assets-built" \
|
||||
-F "client_payload[tag]=${tag}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
25
.github/workflows/dependency-review.yml
vendored
Normal file
25
.github/workflows/dependency-review.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# Dependency Review Action
|
||||
#
|
||||
# This Action will scan dependency manifest files that change as part of a Pull Request,
|
||||
# surfacing known-vulnerable versions of the packages declared or updated in the PR.
|
||||
# Once installed, if the workflow run is marked as required,
|
||||
# PRs introducing known-vulnerable packages will be blocked from merging.
|
||||
#
|
||||
# Source repository: https://github.com/actions/dependency-review-action
|
||||
name: 'Dependency Review'
|
||||
on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2
|
||||
20
.github/workflows/lint_pr.yml
vendored
Normal file
20
.github/workflows/lint_pr.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: 'Lint PR'
|
||||
|
||||
on:
|
||||
pull_request_target: # zizmor: ignore[dangerous-triggers] this is the only way, and we're not running user code
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- reopened
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Validate PR title
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: read
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
70
.github/workflows/release-cross.yml
vendored
70
.github/workflows/release-cross.yml
vendored
@@ -1,70 +0,0 @@
|
||||
name: Publish release files for non-cd-native environments
|
||||
|
||||
on:
|
||||
# workflow_run:
|
||||
# workflows: ["Check SemVer compliance"]
|
||||
# types:
|
||||
# - completed
|
||||
release:
|
||||
types: [ created ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [ "aarch64-unknown-linux-gnu", "armv7-unknown-linux-gnueabihf", "x86_64-unknown-linux-musl", "aarch64-unknown-linux-musl", "x86_64-unknown-freebsd", ]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
default: true
|
||||
override: true
|
||||
target: ${{ matrix.target }}
|
||||
components: rustfmt, clippy
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Check format
|
||||
with:
|
||||
use-cross: true
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Run clippy
|
||||
with:
|
||||
command: clippy
|
||||
use-cross: true
|
||||
args: --all-targets --locked --target ${{matrix.target}} -- -D warnings
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Run clippy (All features)
|
||||
with:
|
||||
command: clippy
|
||||
use-cross: true
|
||||
args: --locked --all-features --target ${{matrix.target}} -- -D warnings
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Run tests
|
||||
with:
|
||||
command: test
|
||||
use-cross: true
|
||||
args: --target ${{matrix.target}}
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Build
|
||||
with:
|
||||
command: build
|
||||
use-cross: true
|
||||
args: --release --all-features --target ${{matrix.target}}
|
||||
- name: Rename Release
|
||||
run: |
|
||||
mkdir assets
|
||||
FILENAME=topgrade-${{github.event.release.tag_name}}-${{matrix.target}}
|
||||
mv target/${{matrix.target}}/release/topgrade assets
|
||||
cd assets
|
||||
tar --format=ustar -czf $FILENAME.tar.gz topgrade
|
||||
rm topgrade
|
||||
ls .
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: assets/*
|
||||
67
.github/workflows/release-plz.yml
vendored
Normal file
67
.github/workflows/release-plz.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
name: Release-plz
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
|
||||
# Release unpublished packages.
|
||||
release-plz-release:
|
||||
name: Release-plz release
|
||||
runs-on: ubuntu-latest
|
||||
environment: crates_io
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write # For trusted publishing
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Run release-plz
|
||||
id: release-plz
|
||||
uses: release-plz/action@d529f731ae3e89610ada96eda34e5c6ba3b12214 # v0.5
|
||||
with:
|
||||
command: release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Trigger workflows
|
||||
if: steps.release-plz.outputs.releases_created == 'true'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ fromJSON(steps.release-plz.outputs.releases)[0].tag }}
|
||||
run: |
|
||||
gh api "repos/${GITHUB_REPOSITORY}/dispatches" \
|
||||
-f "event_type=release-created" \
|
||||
-F "client_payload[tag]=${tag}"
|
||||
|
||||
# Create a PR with the new versions and changelog, preparing the next release.
|
||||
release-plz-pr:
|
||||
name: Release-plz PR
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
concurrency:
|
||||
group: release-plz-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Run release-plz
|
||||
uses: release-plz/action@d529f731ae3e89610ada96eda34e5c6ba3b12214 # v0.5
|
||||
with:
|
||||
command: release-pr
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
77
.github/workflows/release.yml
vendored
77
.github/workflows/release.yml
vendored
@@ -1,77 +0,0 @@
|
||||
name: Publish release files for CD native environments
|
||||
|
||||
on:
|
||||
# workflow_run:
|
||||
# workflows: ["Check SemVer compliance"]
|
||||
# types:
|
||||
# - completed
|
||||
release:
|
||||
types: [ created ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ ubuntu-latest, macos-latest, windows-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Check format
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Run clippy
|
||||
with:
|
||||
command: clippy
|
||||
args: --all-targets --locked -- -D warnings
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Run clippy (All features)
|
||||
with:
|
||||
command: clippy
|
||||
args: --all-targets --locked --all-features -- -D warnings
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Run tests
|
||||
with:
|
||||
command: test
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Build
|
||||
with:
|
||||
command: build
|
||||
args: --release --all-features
|
||||
- name: Rename Release (Unix)
|
||||
run: |
|
||||
cargo install default-target
|
||||
mkdir assets
|
||||
FILENAME=topgrade-${{github.event.release.tag_name}}-$(default-target)
|
||||
mv target/release/topgrade assets
|
||||
cd assets
|
||||
tar --format=ustar -czf $FILENAME.tar.gz topgrade
|
||||
rm topgrade
|
||||
ls .
|
||||
if: ${{ matrix.platform != 'windows-latest' }}
|
||||
shell: bash
|
||||
- name: Rename Release (Windows)
|
||||
run: |
|
||||
cargo install default-target
|
||||
mkdir assets
|
||||
FILENAME=topgrade-${{github.event.release.tag_name}}-$(default-target)
|
||||
mv target/release/topgrade.exe assets/topgrade.exe
|
||||
cd assets
|
||||
powershell Compress-Archive -Path * -Destination ${FILENAME}.zip
|
||||
rm topgrade.exe
|
||||
ls .
|
||||
if: ${{ matrix.platform == 'windows-latest' }}
|
||||
shell: bash
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: assets/*
|
||||
38
.github/workflows/release_to_aur.yml
vendored
Normal file
38
.github/workflows/release_to_aur.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Publish to AUR
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [ release-assets-built ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
aur-publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Determine version
|
||||
id: determine_version
|
||||
env:
|
||||
tag: ${{ github.event.client_payload.tag }}
|
||||
run: |
|
||||
# tag should be something like "v16.0.4", remove the prefix v here
|
||||
echo "version=${tag#v}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Publish source AUR package
|
||||
uses: varabyte/update-aur-package@572e31b1972fa289a27b1926c06a489eb89c7fd7
|
||||
with:
|
||||
version: ${{ steps.determine_version.outputs.version }}
|
||||
package_name: topgrade
|
||||
commit_username: "Thomas Schönauer"
|
||||
commit_email: t.schoenauer@hgs-wt.at
|
||||
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Publish binary AUR package
|
||||
uses: varabyte/update-aur-package@572e31b1972fa289a27b1926c06a489eb89c7fd7
|
||||
with:
|
||||
version: ${{ steps.determine_version.outputs.version }}
|
||||
package_name: topgrade-bin
|
||||
commit_username: "Thomas Schönauer"
|
||||
commit_email: t.schoenauer@hgs-wt.at
|
||||
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
||||
22
.github/workflows/release_to_homebrew.yml
vendored
Normal file
22
.github/workflows/release_to_homebrew.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Publish to Homebrew
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [ release-created ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
homebrew-publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Bump formulae
|
||||
uses: dawidd6/action-homebrew-bump-formula@c5ddc585e75f0f750a8b4f610688b4bec9e80915 # v6
|
||||
with:
|
||||
# Custom GitHub access token with only the 'public_repo' scope enabled
|
||||
token: ${{secrets.HOMEBREW_ACCESS_TOKEN}}
|
||||
formula: topgrade
|
||||
tag: ${{ github.event.client_payload.tag }}
|
||||
# We cannot use an org because org forks cannot give push access to maintainers, which Homebrew requires.
|
||||
# org: topgrade-rs
|
||||
119
.github/workflows/release_to_pypi.yml
vendored
Normal file
119
.github/workflows/release_to_pypi.yml
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
name: Update PyPi
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [ release-created ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# TODO: make linux/windows/macos/sdist a matrix. See how other workflows do it.
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x86_64, x86, aarch64]
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist
|
||||
manylinux: auto
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: wheels-linux-${{ matrix.target }}
|
||||
path: dist
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x64, x86]
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: wheels-windows-${{ matrix.target }}
|
||||
path: dist
|
||||
|
||||
macos:
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x86_64, aarch64]
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: wheels-macos-${{ matrix.target }}
|
||||
path: dist
|
||||
|
||||
sdist:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Build sdist
|
||||
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
|
||||
with:
|
||||
command: sdist
|
||||
args: --out dist
|
||||
- name: Upload sdist
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: wheels-sdist
|
||||
path: dist
|
||||
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: [linux, windows, macos, sdist]
|
||||
permissions:
|
||||
# Use to sign the release artifacts
|
||||
id-token: write
|
||||
# Used to upload release artifacts
|
||||
contents: write
|
||||
# Used to generate artifact attestation
|
||||
attestations: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
|
||||
- name: Generate artifact attestation
|
||||
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
|
||||
with:
|
||||
subject-path: 'wheels-*/*'
|
||||
|
||||
- name: Publish to PyPI
|
||||
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
|
||||
env:
|
||||
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
||||
with:
|
||||
command: upload
|
||||
args: --non-interactive --skip-existing wheels-*/*
|
||||
19
.github/workflows/release_to_winget.yml
vendored
Normal file
19
.github/workflows/release_to_winget.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: Publish to WinGet
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [ release-created ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: vedantmgoyal2009/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f # main
|
||||
with:
|
||||
release-tag: ${{ github.event.client_payload.tag }}
|
||||
identifier: topgrade-rs.topgrade
|
||||
max-versions-to-keep: 5 # keep only latest 5 versions
|
||||
token: ${{ secrets.WINGET_TOKEN }}
|
||||
76
.github/workflows/scorecards.yml
vendored
Normal file
76
.github/workflows/scorecards.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
# This workflow uses actions that are not certified by GitHub. They are provided
|
||||
# by a third-party and are governed by separate terms of service, privacy
|
||||
# policy, and support documentation.
|
||||
|
||||
name: Scorecard supply-chain security
|
||||
on:
|
||||
# For Branch-Protection check. Only the default branch is supported. See
|
||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
|
||||
branch_protection_rule:
|
||||
# To guarantee Maintained check is occasionally updated. See
|
||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
|
||||
schedule:
|
||||
- cron: '20 7 * * 2'
|
||||
push:
|
||||
branches: ["main"]
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecard analysis
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed to upload the results to code-scanning dashboard.
|
||||
security-events: write
|
||||
# Needed to publish results and get a badge (see publish_results below).
|
||||
id-token: write
|
||||
contents: read
|
||||
actions: read
|
||||
# To allow GraphQL ListCommits to work
|
||||
issues: read
|
||||
pull-requests: read
|
||||
# To detect SAST tools
|
||||
checks: read
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
|
||||
# - you want to enable the Branch-Protection check on a *public* repository, or
|
||||
# - you are installing Scorecards on a *private* repository
|
||||
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
|
||||
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
|
||||
|
||||
# Public repositories:
|
||||
# - Publish results to OpenSSF REST API for easy access by consumers
|
||||
# - Allows the repository to include the Scorecard badge.
|
||||
# - See https://github.com/ossf/scorecard-action#publishing-results.
|
||||
# For private repositories:
|
||||
# - `publish_results` will always be set to `false`, regardless
|
||||
# of the value entered here.
|
||||
publish_results: true
|
||||
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
59
.github/workflows/test.yaml
vendored
59
.github/workflows/test.yaml
vendored
@@ -1,59 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
name: Test with Code Coverage
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
env:
|
||||
PROJECT_NAME_UNDERSCORE: topgrade
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: -Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort
|
||||
RUSTDOCFLAGS: -Cpanic=abort
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-dependencies
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/.crates.toml
|
||||
~/.cargo/.crates2.json
|
||||
~/.cargo/bin
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
target
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('Cargo.lock') }}
|
||||
- name: Generate test result and coverage report
|
||||
run: |
|
||||
cargo install cargo2junit grcov;
|
||||
cargo test $CARGO_OPTIONS -- -Z unstable-options --format json | cargo2junit > results.xml;
|
||||
zip -0 ccov.zip `find . \( -name "$PROJECT_NAME_UNDERSCORE*.gc*" \) -print`;
|
||||
grcov ccov.zip -s . -t lcov --llvm --ignore-not-existing --ignore "/*" --ignore "tests/*" -o lcov.info;
|
||||
- name: Upload test results
|
||||
uses: EnricoMi/publish-unit-test-result-action@v1
|
||||
with:
|
||||
check_name: Test Results
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: results.xml
|
||||
- name: Upload to CodeCov
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
# required for private repositories:
|
||||
# token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./lcov.info
|
||||
fail_ci_if_error: true
|
||||
22
.github/workflows/update_aur.yml
vendored
22
.github/workflows/update_aur.yml
vendored
@@ -1,22 +0,0 @@
|
||||
name: Publish to AUR
|
||||
|
||||
on:
|
||||
# workflow_run:
|
||||
# workflows: ["Check SemVer compliance"]
|
||||
# types:
|
||||
# - completed
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
aur-publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Publish AUR package
|
||||
uses: ATiltedTree/create-aur-release@v1
|
||||
with:
|
||||
package_name: topgrade
|
||||
commit_username: "Thomas Schönauer"
|
||||
commit_email: t.schoenauer@hgs-wt.at
|
||||
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
||||
18
.gitignore
vendored
18
.gitignore
vendored
@@ -1,4 +1,20 @@
|
||||
# JetBrains IDEs
|
||||
.idea/
|
||||
|
||||
/target
|
||||
# Visual Studio
|
||||
.vs/
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/
|
||||
|
||||
# Generic build outputs
|
||||
/build
|
||||
|
||||
# Specific for some languages like Rust
|
||||
/target
|
||||
|
||||
# LLVM profiling output
|
||||
*.profraw
|
||||
|
||||
# Backup files for any .rs files in the project
|
||||
**/*.rs.bk
|
||||
|
||||
25
.pre-commit-config.yaml
Normal file
25
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
repos:
|
||||
- repo: https://github.com/gitleaks/gitleaks
|
||||
rev: v8.29.0
|
||||
hooks:
|
||||
- id: gitleaks
|
||||
|
||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||
rev: v0.11.0.1
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0
|
||||
hooks:
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.39.2
|
||||
hooks:
|
||||
- id: typos
|
||||
|
||||
|
||||
ci:
|
||||
autoupdate_commit_msg: "chore(pre-commit): autoupdate"
|
||||
20
.typos.toml
Normal file
20
.typos.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
# Typos configuration (minimal, conservative)
|
||||
# Exclude locales and OS fingerprint data to avoid false positives
|
||||
# - Recognize a few project-specific proper nouns
|
||||
|
||||
[files]
|
||||
extend-exclude = [
|
||||
"src/steps/os/os_release/**",
|
||||
"locales/**",
|
||||
# Include only English locale files - TODO: Split locales/app.yml into a Separate english File
|
||||
# "!locales/en/**"
|
||||
]
|
||||
|
||||
[default]
|
||||
# Mark specific words as always valid by mapping them to themselves
|
||||
check-file = true
|
||||
check-filename = true
|
||||
|
||||
[default.extend-words]
|
||||
# Add project-specific terms that should not be flagged as typos
|
||||
# Example: topgrade = "topgrade"
|
||||
38
.vscode/launch.json
vendored
38
.vscode/launch.json
vendored
@@ -1,38 +0,0 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Topgrade",
|
||||
"console": "integratedTerminal",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--bin=topgrade-rs",
|
||||
"--package=topgrade-rs"
|
||||
],
|
||||
"filter": {
|
||||
"name": "topgrade-rs",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [
|
||||
"--only",
|
||||
"${input:step}",
|
||||
"-v"
|
||||
],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "promptString",
|
||||
"id": "step",
|
||||
"description": "step name",
|
||||
}
|
||||
]
|
||||
}
|
||||
14
.vscode/tasks.json
vendored
14
.vscode/tasks.json
vendored
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "cargo",
|
||||
"command": "clippy",
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
"group": "test",
|
||||
"label": "rust: cargo clippy"
|
||||
}
|
||||
]
|
||||
}
|
||||
50
.vscode/topgrade.code-snippets
vendored
50
.vscode/topgrade.code-snippets
vendored
@@ -1,50 +0,0 @@
|
||||
{
|
||||
// Place your topgrade workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
|
||||
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
|
||||
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
|
||||
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
|
||||
// Placeholders with the same ids are connected.
|
||||
// Example:
|
||||
// "Print to console": {
|
||||
// "scope": "javascript,typescript",
|
||||
// "prefix": "log",
|
||||
// "body": [
|
||||
// "console.log('$1');",
|
||||
// "$2"
|
||||
// ],
|
||||
// "description": "Log output to console"
|
||||
// }
|
||||
"Skip Step": {
|
||||
"scope": "rust",
|
||||
"prefix": "skipstep",
|
||||
"body": [
|
||||
"return Err(SkipStep(format!(\"$1\")).into());"
|
||||
]
|
||||
},
|
||||
"Step": {
|
||||
"scope": "rust",
|
||||
"prefix": "step",
|
||||
"body": [
|
||||
"pub fn $1(ctx: &ExecutionContext) -> Result<()> {",
|
||||
" $0",
|
||||
" Ok(())",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"Require Binary": {
|
||||
"scope": "rust",
|
||||
"prefix": "req",
|
||||
"description": "Require a binary to be installed",
|
||||
"body": [
|
||||
"let ${1:binary} = require(\"${1:binary}\")?;"
|
||||
]
|
||||
},
|
||||
"macos": {
|
||||
"scope": "rust",
|
||||
"prefix": "macos",
|
||||
"body": [
|
||||
"#[cfg(target_os = \"macos\")]"
|
||||
]
|
||||
}
|
||||
}
|
||||
0
BREAKINGCHANGES.md
Normal file
0
BREAKINGCHANGES.md
Normal file
3
BREAKINGCHANGES_dev.md
Normal file
3
BREAKINGCHANGES_dev.md
Normal file
@@ -0,0 +1,3 @@
|
||||
1. The `jet_brains_toolbox` step was renamed to `jetbrains_toolbox`. If you're
|
||||
using the old name in your configuration file in the `disable` or `only`
|
||||
fields, simply change it to `jetbrains_toolbox`.
|
||||
296
CHANGELOG.md
Normal file
296
CHANGELOG.md
Normal file
@@ -0,0 +1,296 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [16.4.2](https://github.com/topgrade-rs/topgrade/compare/v16.4.1...v16.4.2) - 2025-11-20
|
||||
|
||||
### Other
|
||||
|
||||
- *(deps)* update dawidd6/action-homebrew-bump-formula action to v6 ([#1543](https://github.com/topgrade-rs/topgrade/pull/1543))
|
||||
|
||||
## [16.4.1](https://github.com/topgrade-rs/topgrade/compare/v16.4.0...v16.4.1) - 2025-11-20
|
||||
|
||||
### Other
|
||||
|
||||
- refactor run_containers error handling ([#1541](https://github.com/topgrade-rs/topgrade/pull/1541))
|
||||
|
||||
## [16.4.0](https://github.com/topgrade-rs/topgrade/compare/v16.3.0...v16.4.0) - 2025-11-20
|
||||
|
||||
### Added
|
||||
|
||||
- *(os)* add Origami Linux support ([#1530](https://github.com/topgrade-rs/topgrade/pull/1530))
|
||||
- *(containers)* add option to run `system prune` ([#1523](https://github.com/topgrade-rs/topgrade/pull/1523))
|
||||
|
||||
### Fixed
|
||||
|
||||
- *(deps)* restore custom commands order ([#1535](https://github.com/topgrade-rs/topgrade/pull/1535))
|
||||
|
||||
### Other
|
||||
|
||||
- *(deps)* update clap, clap_builder, clap_complete ([#1540](https://github.com/topgrade-rs/topgrade/pull/1540))
|
||||
- *(deps)* update github/codeql-action action to v4.31.4 ([#1531](https://github.com/topgrade-rs/topgrade/pull/1531))
|
||||
- *(config)* add custom commands order test ([#1536](https://github.com/topgrade-rs/topgrade/pull/1536))
|
||||
- make Config methods more consistent by utilizing `#[derive(Default)]` ([#1534](https://github.com/topgrade-rs/topgrade/pull/1534))
|
||||
- *(issue templates)* use issue types ([#1533](https://github.com/topgrade-rs/topgrade/pull/1533))
|
||||
- *(deps)* lock file maintenance ([#1505](https://github.com/topgrade-rs/topgrade/pull/1505))
|
||||
- *(deps)* update actions/checkout digest to 93cb6ef ([#1526](https://github.com/topgrade-rs/topgrade/pull/1526))
|
||||
- *(deps)* update actions/checkout action to v5.0.1 ([#1527](https://github.com/topgrade-rs/topgrade/pull/1527))
|
||||
|
||||
## [16.3.0](https://github.com/topgrade-rs/topgrade/compare/v16.2.1...v16.3.0) - 2025-11-16
|
||||
|
||||
### Added
|
||||
|
||||
- print summary and run post commands when (q)uit is used ([#1254](https://github.com/topgrade-rs/topgrade/pull/1254))
|
||||
- run pre_sudo before pre_commands ([#1469](https://github.com/topgrade-rs/topgrade/pull/1469))
|
||||
- *(chezmoi)* add `exclude_encrypted` config ([#1453](https://github.com/topgrade-rs/topgrade/pull/1453))
|
||||
|
||||
### Fixed
|
||||
|
||||
- *(elan)* skip running elan update on elan >=4.0.0 ([#1507](https://github.com/topgrade-rs/topgrade/pull/1507))
|
||||
- *(deps)* Fix non-locked install on older version of Rust ([#1485](https://github.com/topgrade-rs/topgrade/pull/1485))
|
||||
- *(deps)* Fix non-locked install on older version of Rust ([#1482](https://github.com/topgrade-rs/topgrade/pull/1482))
|
||||
- *(bun)* skip self-update if not installed via official script ([#1476](https://github.com/topgrade-rs/topgrade/pull/1476))
|
||||
- *(openbsd)* fix compilation on OpenBSD ([#1473](https://github.com/topgrade-rs/topgrade/pull/1473))
|
||||
|
||||
### Other
|
||||
|
||||
- *(license)* switch license variant to GPL-3.0-or-later ([#1518](https://github.com/topgrade-rs/topgrade/pull/1518))
|
||||
- *(deps)* update some dependencies ([#1512](https://github.com/topgrade-rs/topgrade/pull/1512))
|
||||
- *(deps)* update github/codeql-action action to v4.31.3 ([#1483](https://github.com/topgrade-rs/topgrade/pull/1483))
|
||||
- remove unnecessary cfg-if dependency ([#1509](https://github.com/topgrade-rs/topgrade/pull/1509))
|
||||
- *(lint_pr)* run on synchronize, and add zizmor ignore ([#1508](https://github.com/topgrade-rs/topgrade/pull/1508))
|
||||
- *(pre-commit)* autoupdate ([#1464](https://github.com/topgrade-rs/topgrade/pull/1464))
|
||||
- improve issue templates ([#1235](https://github.com/topgrade-rs/topgrade/pull/1235))
|
||||
- *(deps)* bump mac-notification-sys, use main branch temporarily ([#1506](https://github.com/topgrade-rs/topgrade/pull/1506))
|
||||
- *(deps)* lock file maintenance ([#1481](https://github.com/topgrade-rs/topgrade/pull/1481))
|
||||
- *(deps)* pin dependencies ([#1478](https://github.com/topgrade-rs/topgrade/pull/1478))
|
||||
- *(deps)* update actions/dependency-review-action action to v4.8.2 ([#1479](https://github.com/topgrade-rs/topgrade/pull/1479))
|
||||
- Add Renovate ([#1477](https://github.com/topgrade-rs/topgrade/pull/1477))
|
||||
- Replace main's self update with a proper step call ([#1470](https://github.com/topgrade-rs/topgrade/pull/1470))
|
||||
- *(release)* Fix homebrew releases ([#1468](https://github.com/topgrade-rs/topgrade/pull/1468))
|
||||
|
||||
## [16.2.1](https://github.com/topgrade-rs/topgrade/compare/v16.2.0...v16.2.1) - 2025-11-10
|
||||
|
||||
### Fixed
|
||||
|
||||
- *(release)* Use bash in Windows to fix powershell issues ([#1461](https://github.com/topgrade-rs/topgrade/pull/1461))
|
||||
- *(release)* Fix .deb distribution ([#1460](https://github.com/topgrade-rs/topgrade/pull/1460))
|
||||
- *(release)* Fix .deb distribution ([#1458](https://github.com/topgrade-rs/topgrade/pull/1458))
|
||||
|
||||
## [16.2.0](https://github.com/topgrade-rs/topgrade/compare/v16.1.2...v16.2.0) - 2025-11-10
|
||||
|
||||
### Added
|
||||
|
||||
- *(mise)* run `mise self-update` ([#1450](https://github.com/topgrade-rs/topgrade/pull/1450))
|
||||
- *(falconf)* add falconf step ([#1219](https://github.com/topgrade-rs/topgrade/pull/1219))
|
||||
- *(hyprpm)* add hyprpm step ([#1213](https://github.com/topgrade-rs/topgrade/pull/1213))
|
||||
- *(doom)* add doom.aot option ([#1214](https://github.com/topgrade-rs/topgrade/pull/1214))
|
||||
- add show_distribution_summary config option ([#1259](https://github.com/topgrade-rs/topgrade/pull/1259))
|
||||
- *(rustup)* add rustup.channels config ([#1206](https://github.com/topgrade-rs/topgrade/pull/1206))
|
||||
- *(os)* add AOSC OS support ([#1424](https://github.com/topgrade-rs/topgrade/pull/1424))
|
||||
- add damp run type ([#1217](https://github.com/topgrade-rs/topgrade/pull/1217))
|
||||
|
||||
### Fixed
|
||||
|
||||
- *(release)* fix homebrew releases by migrating to dawidd6/action-homebrew-bump-formula ([#1457](https://github.com/topgrade-rs/topgrade/pull/1457))
|
||||
- *(mise)* fix mise self-update failing when installed via a package manager ([#1456](https://github.com/topgrade-rs/topgrade/pull/1456))
|
||||
- *(release)* Add man page to .deb distribution ([#1455](https://github.com/topgrade-rs/topgrade/pull/1455))
|
||||
- *(self-update)* fix windows self-update reporting failure on successful self-update ([#1452](https://github.com/topgrade-rs/topgrade/pull/1452))
|
||||
- *(pkgfile)* make pkgfile opt-in ([#1449](https://github.com/topgrade-rs/topgrade/pull/1449))
|
||||
- *(vcpkg)* fix permission denied when updating vcpkg if it's installed as root ([#1447](https://github.com/topgrade-rs/topgrade/pull/1447))
|
||||
- *(zh_TW)* fixed zh_TW strings ([#1446](https://github.com/topgrade-rs/topgrade/pull/1446))
|
||||
- *(git)* fix shellexpand::tilde in git_repos in topgrade.d/* ([#1223](https://github.com/topgrade-rs/topgrade/pull/1223))
|
||||
- *(auto-cpufreq)* skip when install script is not used ([#1215](https://github.com/topgrade-rs/topgrade/pull/1215))
|
||||
- *(vim)* change nvimrc base_dir for windows ([#1433](https://github.com/topgrade-rs/topgrade/pull/1433))
|
||||
- *(guix)* fix overcomplicated Guix step ([#1290](https://github.com/topgrade-rs/topgrade/pull/1290))
|
||||
- *(gem)* fix incorrectly placed debug message in `gem` step ([#1212](https://github.com/topgrade-rs/topgrade/pull/1212))
|
||||
- *(conda)* replace deprecated `auto_activate_base` ([#1158](https://github.com/topgrade-rs/topgrade/pull/1158))
|
||||
- *(containers)* fix panic in `containers` step ([#1150](https://github.com/topgrade-rs/topgrade/pull/1150))
|
||||
- *(jetbrains-toolbox)* fix step not dry running ([#1253](https://github.com/topgrade-rs/topgrade/pull/1253))
|
||||
|
||||
### Other
|
||||
|
||||
- comment run_config_update ([#1448](https://github.com/topgrade-rs/topgrade/pull/1448))
|
||||
- Expand LLM guidelines in CONTRIBUTING.md ([#1445](https://github.com/topgrade-rs/topgrade/pull/1445))
|
||||
- Add AI guidelines to CONTRIBUTING.md ([#1444](https://github.com/topgrade-rs/topgrade/pull/1444))
|
||||
- add comments to Config::allowed_steps ([#1291](https://github.com/topgrade-rs/topgrade/pull/1291))
|
||||
- *(nix)* Deduplicate run_nix and run_nix_self_upgrade nix --version checking ([#1376](https://github.com/topgrade-rs/topgrade/pull/1376))
|
||||
- remove commented-out library code and unnecessary bin declaration ([#1373](https://github.com/topgrade-rs/topgrade/pull/1373))
|
||||
- Simplify target cfgs ([#1346](https://github.com/topgrade-rs/topgrade/pull/1346))
|
||||
- tidy up binary-conflict code ([#1329](https://github.com/topgrade-rs/topgrade/pull/1329))
|
||||
- Improve installation section ([#1442](https://github.com/topgrade-rs/topgrade/pull/1442))
|
||||
- *(deps)* Update jetbrains-toolbox-updater ([#1438](https://github.com/topgrade-rs/topgrade/pull/1438))
|
||||
- remove template expansion in code contexts ([#1434](https://github.com/topgrade-rs/topgrade/pull/1434))
|
||||
- *(deps)* bump github/codeql-action from 4.31.0 to 4.31.2 ([#1427](https://github.com/topgrade-rs/topgrade/pull/1427))
|
||||
- don't persist credentials in actions/checkout ([#1422](https://github.com/topgrade-rs/topgrade/pull/1422))
|
||||
- Improve CONTRIBUTING.md ([#1420](https://github.com/topgrade-rs/topgrade/pull/1420))
|
||||
- Update SECURITY.md ([#1421](https://github.com/topgrade-rs/topgrade/pull/1421))
|
||||
- Enforce conventional commits in PR titles ([#1418](https://github.com/topgrade-rs/topgrade/pull/1418))
|
||||
- Improve contributing section
|
||||
- Remove roadmap
|
||||
- Reformat README.md
|
||||
- Update installation methods
|
||||
- *(release)* Fix dispatch error in create_release_assets.yml ([#1406](https://github.com/topgrade-rs/topgrade/pull/1406))
|
||||
|
||||
## [16.1.2](https://github.com/topgrade-rs/topgrade/compare/v16.1.1...v16.1.2) - 2025-11-01
|
||||
|
||||
### Fixed
|
||||
|
||||
- *(release)* Fix cross-compilation for arm requiring glibc>=2.39 ([#1405](https://github.com/topgrade-rs/topgrade/pull/1405))
|
||||
- *(release)* Fix FreeBSD build ([#1404](https://github.com/topgrade-rs/topgrade/pull/1404))
|
||||
- *(release)* Fix FreeBSD build ([#1402](https://github.com/topgrade-rs/topgrade/pull/1402))
|
||||
- *(release)* Fix manual workflow trigger ([#1401](https://github.com/topgrade-rs/topgrade/pull/1401))
|
||||
- *(release)* Fix FreeBSD build and add manual workflow trigger ([#1399](https://github.com/topgrade-rs/topgrade/pull/1399))
|
||||
|
||||
### Other
|
||||
|
||||
- *(release)* Fix cross trying to fmt ([#1403](https://github.com/topgrade-rs/topgrade/pull/1403))
|
||||
|
||||
## [16.1.1](https://github.com/topgrade-rs/topgrade/compare/v16.1.0...v16.1.1) - 2025-11-01
|
||||
|
||||
### Fixed
|
||||
|
||||
- *(typst)* Skip typst when self-update is disabled ([#1397](https://github.com/topgrade-rs/topgrade/pull/1397))
|
||||
- *(release)* Fix winget release workflow ([#1395](https://github.com/topgrade-rs/topgrade/pull/1395))
|
||||
- *(release)* Fix FreeBSD release ([#1393](https://github.com/topgrade-rs/topgrade/pull/1393))
|
||||
- *(release)* Fix FreeBSD release ([#1391](https://github.com/topgrade-rs/topgrade/pull/1391))
|
||||
|
||||
### Other
|
||||
|
||||
- Update from deprecated macos-13 to macos-15-intel ([#1394](https://github.com/topgrade-rs/topgrade/pull/1394))
|
||||
|
||||
## [16.1.0](https://github.com/topgrade-rs/topgrade/compare/v16.0.4...v16.1.0) - 2025-10-31
|
||||
|
||||
### Added
|
||||
|
||||
- *(deb-get)* Skip non-deb-get packages by passing --dg-only ([#1386](https://github.com/topgrade-rs/topgrade/pull/1386))
|
||||
- *(typst)* add typst step ([#1374](https://github.com/topgrade-rs/topgrade/pull/1374))
|
||||
- *(step)* Add atuin step ([#1367](https://github.com/topgrade-rs/topgrade/pull/1367))
|
||||
- *(nix)* support upgrading Determinate Nix ([#1366](https://github.com/topgrade-rs/topgrade/pull/1366))
|
||||
- *(sudo)* print warning if Windows Sudo is misconfigured
|
||||
- *(sudo)* print warning if steps were skipped due to missing sudo
|
||||
- *(sudo)* add SudoKind::Null
|
||||
- detect and warn if running as root
|
||||
- add `--no-tmux` flag ([#1328](https://github.com/topgrade-rs/topgrade/pull/1328))
|
||||
- add step for mandb - user and system (update man entries) ([#1319](https://github.com/topgrade-rs/topgrade/pull/1319))
|
||||
- support for pkgfile ([#1306](https://github.com/topgrade-rs/topgrade/pull/1306))
|
||||
- add "show_skipped" option in config file #1280 ([#1286](https://github.com/topgrade-rs/topgrade/pull/1286))
|
||||
- fix typos ([#1221](https://github.com/topgrade-rs/topgrade/pull/1221))
|
||||
- *(conda)* allow configuring additional envs to update ([#1048](https://github.com/topgrade-rs/topgrade/pull/1048))
|
||||
- *(step)* nix-helper ([#1045](https://github.com/topgrade-rs/topgrade/pull/1045))
|
||||
- *(winget)* winget uses sudo when `[windows] winget_use_sudo = true` ([#1061](https://github.com/topgrade-rs/topgrade/pull/1061))
|
||||
- suppress pixi release notes by default ([#1225](https://github.com/topgrade-rs/topgrade/pull/1225))
|
||||
|
||||
### Fixed
|
||||
|
||||
- *(freshclam)* run with sudo when running without sudo fails ([#1118](https://github.com/topgrade-rs/topgrade/pull/1118))
|
||||
- *(tldr)* move tldr to be a generic step ([#1370](https://github.com/topgrade-rs/topgrade/pull/1370))
|
||||
- *(nix)* fix nix upgrade command selection for profiles in XDG_STATE_HOME ([#1354](https://github.com/topgrade-rs/topgrade/pull/1354))
|
||||
- *(containers)* Docker update fails on M Macs due to platform / ([#1360](https://github.com/topgrade-rs/topgrade/pull/1360))
|
||||
- *(sudo)* reorder require_sudo() after print_separator()
|
||||
- *(sudo)* use require_sudo for windows commands
|
||||
- *(sudo)* prevent sudo_command = "sudo" finding gsudo
|
||||
- *(sudo)* set sudo flags depending on kind
|
||||
- skip gcloud update step if component manager is disabled ([#1237](https://github.com/topgrade-rs/topgrade/pull/1237))
|
||||
- *(i18n)* use double-quotes for translations with newlines
|
||||
- *(powershell)* run microsoft_store command directly
|
||||
- *(powershell)* remove mentions of USOClient
|
||||
- *(powershell)* execution policy check breaks when run in pwsh
|
||||
- *(powershell)* don't use sudo with Update-Module for pwsh
|
||||
- *(powershell)* add -Command to module update cmdline
|
||||
- *(tmux)* support all default `tpm` locations (xdg and both hardcoded locations) ([#1146](https://github.com/topgrade-rs/topgrade/pull/1146))
|
||||
- fixed the German translation for "y/n/s/q" ([#1220](https://github.com/topgrade-rs/topgrade/pull/1220))
|
||||
|
||||
### Other
|
||||
|
||||
- *(release)* switch to release-plz ([#1333](https://github.com/topgrade-rs/topgrade/pull/1333))
|
||||
- *(pre-commit)* Make pre-commit.ci use conventional commits ([#1388](https://github.com/topgrade-rs/topgrade/pull/1388))
|
||||
- *(pre-commit)* pre-commit autoupdate ([#1383](https://github.com/topgrade-rs/topgrade/pull/1383))
|
||||
- *(deps)* bump actions/upload-artifact from 4.6.2 to 5.0.0 ([#1382](https://github.com/topgrade-rs/topgrade/pull/1382))
|
||||
- *(deps)* bump github/codeql-action from 4.30.9 to 4.31.0 ([#1379](https://github.com/topgrade-rs/topgrade/pull/1379))
|
||||
- *(deps)* bump actions/download-artifact from 5.0.0 to 6.0.0 ([#1380](https://github.com/topgrade-rs/topgrade/pull/1380))
|
||||
- *(deps)* bump taiki-e/install-action from 2.62.33 to 2.62.38 ([#1381](https://github.com/topgrade-rs/topgrade/pull/1381))
|
||||
- *(pre-commit)* Fix pre-commit-config.yaml ([#1378](https://github.com/topgrade-rs/topgrade/pull/1378))
|
||||
- *(release)* Add .deb auto completion script ([#1353](https://github.com/topgrade-rs/topgrade/pull/1353))
|
||||
- *(deps)* bump github/codeql-action from 4.30.8 to 4.30.9 ([#1369](https://github.com/topgrade-rs/topgrade/pull/1369))
|
||||
- *(deps)* bump taiki-e/install-action from 2.62.28 to 2.62.33 ([#1368](https://github.com/topgrade-rs/topgrade/pull/1368))
|
||||
- *(deps)* bump actions/dependency-review-action from 4.8.0 to 4.8.1 ([#1362](https://github.com/topgrade-rs/topgrade/pull/1362))
|
||||
- *(deps)* bump softprops/action-gh-release from 2.3.4 to 2.4.1 ([#1364](https://github.com/topgrade-rs/topgrade/pull/1364))
|
||||
- *(deps)* bump taiki-e/install-action from 2.62.21 to 2.62.28 ([#1363](https://github.com/topgrade-rs/topgrade/pull/1363))
|
||||
- *(deps)* bump github/codeql-action from 3.30.6 to 4.30.8 ([#1365](https://github.com/topgrade-rs/topgrade/pull/1365))
|
||||
- *(deps)* bump github/codeql-action from 3.30.5 to 3.30.6 ([#1355](https://github.com/topgrade-rs/topgrade/pull/1355))
|
||||
- *(deps)* bump softprops/action-gh-release from 2.3.3 to 2.3.4 ([#1356](https://github.com/topgrade-rs/topgrade/pull/1356))
|
||||
- *(deps)* bump taiki-e/install-action from 2.62.13 to 2.62.21 ([#1357](https://github.com/topgrade-rs/topgrade/pull/1357))
|
||||
- *(deps)* bump ossf/scorecard-action from 2.4.2 to 2.4.3 ([#1358](https://github.com/topgrade-rs/topgrade/pull/1358))
|
||||
- *(deps)* bump actions/dependency-review-action from 4.7.3 to 4.8.0 ([#1350](https://github.com/topgrade-rs/topgrade/pull/1350))
|
||||
- *(deps)* bump github/codeql-action from 3.30.3 to 3.30.5 ([#1349](https://github.com/topgrade-rs/topgrade/pull/1349))
|
||||
- *(deps)* bump taiki-e/install-action from 2.62.1 to 2.62.13 ([#1351](https://github.com/topgrade-rs/topgrade/pull/1351))
|
||||
- *(deps)* bump actions/cache from 4.2.4 to 4.3.0 ([#1352](https://github.com/topgrade-rs/topgrade/pull/1352))
|
||||
- Fix WSL distribution name cleanup ([#1348](https://github.com/topgrade-rs/topgrade/pull/1348))
|
||||
- *(pyproject)* mark version as dynamic ([#1347](https://github.com/topgrade-rs/topgrade/pull/1347))
|
||||
- *(deps)* replace winapi with windows
|
||||
- *(sudo)* rename interactive to login_shell
|
||||
- Fix "WSL already reported" panic ([#1344](https://github.com/topgrade-rs/topgrade/pull/1344))
|
||||
- Move step logic out of Powershell struct ([#1345](https://github.com/topgrade-rs/topgrade/pull/1345))
|
||||
- *(deps)* bump taiki-e/install-action from 2.61.5 to 2.62.1 ([#1335](https://github.com/topgrade-rs/topgrade/pull/1335))
|
||||
- *(deps)* bump Swatinem/rust-cache from 2.8.0 to 2.8.1 ([#1336](https://github.com/topgrade-rs/topgrade/pull/1336))
|
||||
- Fixes for #1188; custom_commands broken ([#1332](https://github.com/topgrade-rs/topgrade/pull/1332))
|
||||
- use login shell when executing topgrade ([#1327](https://github.com/topgrade-rs/topgrade/pull/1327))
|
||||
- *(deps)* bump taiki-e/install-action from 2.60.0 to 2.61.5 ([#1325](https://github.com/topgrade-rs/topgrade/pull/1325))
|
||||
- *(deps)* bump github/codeql-action from 3.30.1 to 3.30.3 ([#1324](https://github.com/topgrade-rs/topgrade/pull/1324))
|
||||
- *(pre-commit)* add typos with conservative excludes; no content changes ([#1317](https://github.com/topgrade-rs/topgrade/pull/1317))
|
||||
- fix simple typos in code and comments (split var, whether, Extensions) ([#1318](https://github.com/topgrade-rs/topgrade/pull/1318))
|
||||
- *(deps)* bump github/codeql-action from 3.29.11 to 3.30.1 ([#1301](https://github.com/topgrade-rs/topgrade/pull/1301))
|
||||
- *(deps)* bump softprops/action-gh-release from 2.3.2 to 2.3.3 ([#1302](https://github.com/topgrade-rs/topgrade/pull/1302))
|
||||
- *(deps)* bump taiki-e/install-action from 2.58.21 to 2.60.0 ([#1303](https://github.com/topgrade-rs/topgrade/pull/1303))
|
||||
- *(deps)* bump actions/dependency-review-action from 4.7.2 to 4.7.3 ([#1304](https://github.com/topgrade-rs/topgrade/pull/1304))
|
||||
- *(deps)* bump actions/attest-build-provenance from 2.4.0 to 3.0.0 ([#1305](https://github.com/topgrade-rs/topgrade/pull/1305))
|
||||
- update tracing-subscriber to ~0.3.20 (ANSI escape injection fix, GHSA-xwfj-jgwm-7wp5) ([#1288](https://github.com/topgrade-rs/topgrade/pull/1288))
|
||||
- *(deps)* bump github/codeql-action from 3.29.8 to 3.29.11 ([#1281](https://github.com/topgrade-rs/topgrade/pull/1281))
|
||||
- *(deps)* bump actions/dependency-review-action from 4.7.1 to 4.7.2 ([#1282](https://github.com/topgrade-rs/topgrade/pull/1282))
|
||||
- *(deps)* bump taiki-e/install-action from 2.58.9 to 2.58.21 ([#1283](https://github.com/topgrade-rs/topgrade/pull/1283))
|
||||
- *(deps)* bump PyO3/maturin-action from 1.49.3 to 1.49.4 ([#1285](https://github.com/topgrade-rs/topgrade/pull/1285))
|
||||
- *(deps)* bump actions/cache from 4.2.3 to 4.2.4 ([#1284](https://github.com/topgrade-rs/topgrade/pull/1284))
|
||||
- Support "Insiders" versions of VSCode and VSCodium ([#1279](https://github.com/topgrade-rs/topgrade/pull/1279))
|
||||
- Sudo preserve env list argument is `--preserve-env` ([#1276](https://github.com/topgrade-rs/topgrade/pull/1276))
|
||||
- Clippy fixes from rust 1.91 nightly ([#1267](https://github.com/topgrade-rs/topgrade/pull/1267))
|
||||
- *(deps)* bump actions/checkout from 4.2.2 to 5.0.0 ([#1264](https://github.com/topgrade-rs/topgrade/pull/1264))
|
||||
- *(deps)* bump actions/download-artifact from 4.3.0 to 5.0.0 ([#1263](https://github.com/topgrade-rs/topgrade/pull/1263))
|
||||
- *(deps)* bump taiki-e/install-action from 2.58.0 to 2.58.9 ([#1261](https://github.com/topgrade-rs/topgrade/pull/1261))
|
||||
- *(deps)* bump ossf/scorecard-action from 2.4.0 to 2.4.2 ([#1262](https://github.com/topgrade-rs/topgrade/pull/1262))
|
||||
- *(deps)* bump github/codeql-action from 3.29.5 to 3.29.8 ([#1265](https://github.com/topgrade-rs/topgrade/pull/1265))
|
||||
- *(ci)* Dependabot, workflow security ([#1257](https://github.com/topgrade-rs/topgrade/pull/1257))
|
||||
- replace once_cell crate with std equivalent ([#1260](https://github.com/topgrade-rs/topgrade/pull/1260))
|
||||
- *(deps)* bump tokio from 1.38 to 1.47 ([#1256](https://github.com/topgrade-rs/topgrade/pull/1256))
|
||||
- *(app.yml)* fix fr language #1248
|
||||
- *(sudo)* add SudoKind::WinSudo
|
||||
- *(sudo)* add SudoExecuteOpts builder functions and preserve_env enum
|
||||
- *(yarn)* remove unnecessary Yarn::yarn field
|
||||
- *(apt)* extract detect_apt() function
|
||||
- route sudo usage through Sudo::execute*
|
||||
- move RunType::execute to ExecutionContext
|
||||
- *(powershell)* store powershell path directly
|
||||
- *(powershell)* cleanup and simplify code
|
||||
- Move step running into enum for dynamic ordering ([#1188](https://github.com/topgrade-rs/topgrade/pull/1188))
|
||||
- Generate artifact attestations for release assets ([#1216](https://github.com/topgrade-rs/topgrade/pull/1216))
|
||||
- windows update, use explicit reboot policy ([#1143](https://github.com/topgrade-rs/topgrade/pull/1143))
|
||||
- add Discord invite link to README ([#1203](https://github.com/topgrade-rs/topgrade/pull/1203))
|
||||
- Catch secondary uv self-update error ([#1201](https://github.com/topgrade-rs/topgrade/pull/1201))
|
||||
- Handle another format change in asdf version ([#1194](https://github.com/topgrade-rs/topgrade/pull/1194))
|
||||
- Preserve custom command order from config instead of sorting alphabetically ([#1182](https://github.com/topgrade-rs/topgrade/pull/1182))
|
||||
- Add support for multiple binary names and idea having multiple binaries ([#1167](https://github.com/topgrade-rs/topgrade/pull/1167))
|
||||
- fix the invalid action version ([#1185](https://github.com/topgrade-rs/topgrade/pull/1185))
|
||||
- allow us to re-run AUR CI ([#1184](https://github.com/topgrade-rs/topgrade/pull/1184))
|
||||
- Update Yazi upgrade step to use ya pkg. ([#1163](https://github.com/topgrade-rs/topgrade/pull/1163))
|
||||
- use the new tag name and specify shell to bash ([#1183](https://github.com/topgrade-rs/topgrade/pull/1183))
|
||||
- allow specifying tag when manually run 'create_release_assets.yml' ([#1180](https://github.com/topgrade-rs/topgrade/pull/1180))
|
||||
- fix homebrew ci, remove duplicate trigger event ([#1179](https://github.com/topgrade-rs/topgrade/pull/1179))
|
||||
- fix PyPI pipeline duplicate wheel name ([#1178](https://github.com/topgrade-rs/topgrade/pull/1178))
|
||||
- add event workflow_dispatch to release pipelines ([#1177](https://github.com/topgrade-rs/topgrade/pull/1177))
|
||||
- fix pipeline release to PyPI ([#1176](https://github.com/topgrade-rs/topgrade/pull/1176))
|
||||
- Install rustfmt and clippy where necessary ([#1171](https://github.com/topgrade-rs/topgrade/pull/1171))
|
||||
182
CONTRIBUTING.md
Normal file
182
CONTRIBUTING.md
Normal file
@@ -0,0 +1,182 @@
|
||||
## Contributing to `topgrade`
|
||||
|
||||
Thank you for your interest in contributing to `topgrade`!
|
||||
We welcome and encourage contributions of all kinds, such as:
|
||||
|
||||
1. Issue reports or feature requests
|
||||
2. Documentation improvements
|
||||
3. Code (PR or PR Review)
|
||||
|
||||
### LLM/AI guidelines
|
||||
|
||||
You may use LLMs (AI tools) for:
|
||||
|
||||
* Inspiration, problem solving, help with Rust, translation, etc.
|
||||
* Generating small and self-contained snippets of code (e.g., shell scripts or utility functions)
|
||||
|
||||
Do **not** use LLMs to:
|
||||
|
||||
* Generate ("vibe code") entire pull requests
|
||||
* Write or generate issue or pull request descriptions
|
||||
|
||||
### General guidelines
|
||||
|
||||
**Please use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) for your PR title**.
|
||||
|
||||
We use [pre-commit](https://github.com/pre-commit/pre-commit). It runs in CI, but you can optionally install the hook
|
||||
locally with `pre-commit install`. If you don't want to use pre-commit, make sure the following pass before submitting
|
||||
your PR:
|
||||
|
||||
```shell
|
||||
$ cargo fmt
|
||||
$ cargo clippy
|
||||
$ cargo test
|
||||
```
|
||||
|
||||
### Adding a new step
|
||||
|
||||
In `topgrade`'s terms, a package manager (or something else that can be upgraded) is called a step.
|
||||
To add a new step to `topgrade`:
|
||||
|
||||
1. Add a new variant to
|
||||
[`enum Step`](https://github.com/topgrade-rs/topgrade/blob/main/src/step.rs)
|
||||
|
||||
```rust
|
||||
pub enum Step {
|
||||
// Existing steps
|
||||
// ...
|
||||
|
||||
// Your new step here!
|
||||
// Make sure it stays sorted alphabetically because that looks great :)
|
||||
Xxx,
|
||||
}
|
||||
```
|
||||
|
||||
2. Implement the update function
|
||||
|
||||
You need to find the appropriate location where this update function goes, it should be
|
||||
a file under [`src/steps`](https://github.com/topgrade-rs/topgrade/tree/main/src/steps),
|
||||
the file names are self-explanatory, for example, steps related to `zsh` are
|
||||
placed in [`steps/zsh.rs`](https://github.com/topgrade-rs/topgrade/blob/main/src/steps/zsh.rs), and steps that run on
|
||||
Linux only are placed in [`steps/linux.rs`](https://github.com/topgrade-rs/topgrade/blob/main/src/steps/linux.rs).
|
||||
|
||||
Then you implement the update function, and put it in the file where it belongs.
|
||||
|
||||
```rust
|
||||
pub fn run_xxx(ctx: &ExecutionContext) -> Result<()> {
|
||||
// Check if this step is installed, if not, then this update will be skipped.
|
||||
let xxx = require("xxx")?;
|
||||
|
||||
// Print the separator
|
||||
print_separator("xxx");
|
||||
|
||||
// Invoke the new step to get things updated!
|
||||
ctx.execute(xxx)
|
||||
.arg(/* args required by this step */)
|
||||
.status_checked()
|
||||
}
|
||||
```
|
||||
|
||||
Such an update function would be conventionally named `run_xxx()`, where `xxx`
|
||||
is the name of the new step, and it should take an argument of type
|
||||
`&ExecutionContext`.
|
||||
|
||||
The update function should usually do 3 things:
|
||||
1. Check if the step is installed
|
||||
2. Output the separator
|
||||
3. Execute commands
|
||||
|
||||
This is sufficient for most tools, but you may need some extra stuff
|
||||
for complicated steps.
|
||||
|
||||
3. Add a match arm to `Step::run()`
|
||||
|
||||
```rust
|
||||
Xxx => runner.execute(*self, "xxx", || ItsModule::run_xxx(ctx))?
|
||||
```
|
||||
|
||||
We use [conditional compilation](https://doc.rust-lang.org/reference/conditional-compilation.html)
|
||||
to separate the steps. For example, for steps that are Linux-only, it goes
|
||||
like this:
|
||||
|
||||
```rust
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// Xxx is Linux-only
|
||||
runner.execute(Step::Xxx, "xxx", || ItsModule::run_xxx(&ctx))?;
|
||||
}
|
||||
```
|
||||
|
||||
4. Finally, add the step to `default_steps()` in `step.rs`
|
||||
```rust
|
||||
steps.push(Xxx)
|
||||
```
|
||||
Keep the conditional compilation the same as in the above step 3.
|
||||
|
||||
Congrats, you just added a new step :)
|
||||
|
||||
### Modification to the configuration entries
|
||||
|
||||
If your PR has the configuration options
|
||||
(in [`src/config.rs`](https://github.com/topgrade-rs/topgrade/blob/main/src/config.rs))
|
||||
modified:
|
||||
|
||||
1. Adding new options
|
||||
2. Changing the existing options
|
||||
|
||||
Be sure to apply your changes to
|
||||
[`config.example.toml`](https://github.com/topgrade-rs/topgrade/blob/main/config.example.toml),
|
||||
and have some basic documentations guiding user how to use these options.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
If your PR introduces a breaking change, document it in [`BREAKINGCHANGES_dev.md`][bc_dev].
|
||||
It should be written in Markdown and wrapped at 80, for example:
|
||||
|
||||
```md
|
||||
1. The configuration location has been updated to x.
|
||||
|
||||
2. The step x has been removed.
|
||||
|
||||
3. ...
|
||||
```
|
||||
|
||||
[bc_dev]: https://github.com/topgrade-rs/topgrade/blob/main/BREAKINGCHANGES_dev.md
|
||||
|
||||
### I18n
|
||||
|
||||
If your PR introduces user-facing messages, we need to ensure they are translated.
|
||||
Please add the translations to [`locales/app.yml`][app_yml]. For simple messages
|
||||
without arguments (e.g., "hello world"), we can simply translate them according
|
||||
(Tip: LLMs are good at translation). If a message contains
|
||||
arguments, e.g., "hello <NAME>", please follow this convention:
|
||||
|
||||
```yml
|
||||
"hello {name}": # key
|
||||
en: "hello %{name}" # translation
|
||||
```
|
||||
|
||||
Arguments in the key should be in format `{argument_name}`, and they will have
|
||||
a preceding `%` when used in translations.
|
||||
|
||||
[app_yml]: https://github.com/topgrade-rs/topgrade/blob/main/locales/app.yml
|
||||
|
||||
### Locales
|
||||
|
||||
Some steps respect locale, which means their output can be in language other
|
||||
than English. In those cases, we cannot rely on the output of a command.
|
||||
|
||||
For example, one may want to check if a tool works by doing this:
|
||||
|
||||
```rust
|
||||
let output = Command::new("xxx").arg("--help").output().unwrap();
|
||||
let stdout = from_utf8(output.stdout).expect("Assume it is UTF-8 encoded");
|
||||
|
||||
if stdout.contains("help") {
|
||||
// xxx works
|
||||
}
|
||||
```
|
||||
|
||||
If `xxx` respects locale, then the above code should work on English system,
|
||||
on a system that does not use English, e.g., it uses Chinese, that `"help"` may be
|
||||
translated to `"帮助"`, and the above code won't work.
|
||||
3690
Cargo.lock
generated
3690
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
105
Cargo.toml
105
Cargo.toml
@@ -3,61 +3,92 @@ name = "topgrade"
|
||||
description = "Upgrade all the things"
|
||||
categories = ["os"]
|
||||
keywords = ["upgrade", "update"]
|
||||
license-file = "LICENSE"
|
||||
license = "GPL-3.0-or-later"
|
||||
repository = "https://github.com/topgrade-rs/topgrade"
|
||||
version = "10.1.0"
|
||||
rust-version = "1.87.0"
|
||||
version = "16.4.2"
|
||||
authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"]
|
||||
exclude = ["doc/screenshot.gif"]
|
||||
edition = "2021"
|
||||
exclude = ["doc/screenshot.gif", "BREAKINGCHANGES_dev.md"]
|
||||
edition = "2024"
|
||||
|
||||
readme = "README.md"
|
||||
|
||||
[[bin]]
|
||||
name = "topgrade"
|
||||
path = "src/main.rs"
|
||||
|
||||
##[lib]
|
||||
##name = "topgrade_lib"
|
||||
|
||||
[dependencies]
|
||||
home = "~0.5"
|
||||
directories = "~4.0"
|
||||
etcetera = "~0.11.0"
|
||||
serde = { version = "~1.0", features = ["derive"] }
|
||||
toml = "0.5"
|
||||
which_crate = { version = "~4.1", package = "which" }
|
||||
shellexpand = "~2.1"
|
||||
clap = { version = "~3.1", features = ["cargo", "derive"] }
|
||||
log = "~0.4"
|
||||
walkdir = "~2.3"
|
||||
console = "~0.15"
|
||||
lazy_static = "~1.4"
|
||||
toml = { version = "~0.9.8", features = ["preserve_order"] }
|
||||
which_crate = { version = "~8.0", package = "which" }
|
||||
shellexpand = "~3.1"
|
||||
clap = { version = "~4.5", features = ["cargo", "derive"] }
|
||||
clap_complete = "~4.5"
|
||||
clap_mangen = "~0.2"
|
||||
walkdir = "~2.5"
|
||||
console = "~0.16"
|
||||
chrono = "~0.4"
|
||||
pretty_env_logger = "~0.4"
|
||||
glob = "~0.3"
|
||||
strum = { version = "~0.24", features = ["derive"] }
|
||||
thiserror = "~1.0"
|
||||
anyhow = "~1.0"
|
||||
tempfile = "~3.2"
|
||||
cfg-if = "~1.0"
|
||||
tokio = { version = "~1.5", features = ["process", "rt-multi-thread"] }
|
||||
strum = { version = "~0.27", features = ["derive"] }
|
||||
thiserror = "~2.0"
|
||||
tempfile = "~3.23"
|
||||
tokio = { version = "~1.48", features = ["process", "rt-multi-thread"] }
|
||||
futures = "~0.3"
|
||||
regex = "~1.5"
|
||||
sys-info = "~0.9"
|
||||
regex = "~1.12"
|
||||
semver = "~1.0"
|
||||
shell-words = "~1.1"
|
||||
color-eyre = "~0.6"
|
||||
tracing = { version = "~0.1", features = ["attributes", "log"] }
|
||||
tracing-subscriber = { version = "~0.3.20", features = ["env-filter", "time"] }
|
||||
merge = "~0.1"
|
||||
regex-split = "~0.1"
|
||||
notify-rust = "~4.11"
|
||||
wildmatch = "2.3.0"
|
||||
rust-i18n = "3.0.1"
|
||||
sys-locale = "0.3.1"
|
||||
jetbrains-toolbox-updater = "5.0.0"
|
||||
indexmap = { version = "2.9.0", features = ["serde"] }
|
||||
serde_json = "1.0.145"
|
||||
clap-cargo = "0.18.0"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
notify-rust = "~4.5"
|
||||
[patch.crates-io]
|
||||
mac-notification-sys = { git = "https://github.com/h4llow3En/mac-notification-sys" }
|
||||
|
||||
[package.metadata.generate-rpm]
|
||||
assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }]
|
||||
|
||||
[package.metadata.generate-rpm.requires]
|
||||
git = "*"
|
||||
|
||||
[package.metadata.deb]
|
||||
name = "topgrade"
|
||||
maintainer = "Chris Gelatt <kreeblah@gmail.com>"
|
||||
copyright = "2025, Topgrade Team"
|
||||
license-file = ["LICENSE", "0"]
|
||||
depends = "$auto"
|
||||
extended-description = "Keeping your system up to date usually involves invoking multiple package managers. This results in big, non-portable shell one-liners saved in your shell. To remedy this, Topgrade detects which tools you use and runs the appropriate commands to update them."
|
||||
section = "utils"
|
||||
priority = "optional"
|
||||
default-features = true
|
||||
assets = [
|
||||
["target/release/topgrade", "usr/bin/", "755"],
|
||||
["README.md", "usr/share/doc/topgrade/README.md", "644"],
|
||||
# The man page and shell completions are automatically generated by topgrade's build process in CI,
|
||||
# so these files aren't actually committed.
|
||||
["deployment/deb/topgrade.1", "usr/share/man/man1/topgrade.1", "644"],
|
||||
["deployment/deb/topgrade.bash", "usr/share/bash-completion/completions/topgrade", "644"],
|
||||
["deployment/deb/topgrade.fish", "usr/share/fish/vendor_completions.d/topgrade.fish", "644"],
|
||||
["deployment/deb/_topgrade", "usr/share/zsh/vendor-completions/", "644"],
|
||||
]
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = "~0.24"
|
||||
rust-ini = "~0.18"
|
||||
self_update_crate = { version = "~0.30", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] }
|
||||
nix = { version = "~0.30", features = ["hostname", "signal", "user"] }
|
||||
rust-ini = "~0.21"
|
||||
self_update_crate = { version = "~0.42", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
self_update_crate = { version = "~0.30", default-features = false, optional = true, package = "self_update", features = ["archive-zip", "compression-zip-deflate", "rustls"] }
|
||||
winapi = "~0.3"
|
||||
is_elevated = "~0.1"
|
||||
parselnk = "~0.1"
|
||||
self_update_crate = { version = "~0.42", default-features = false, optional = true, package = "self_update", features = ["archive-zip", "compression-zip-deflate", "rustls"] }
|
||||
windows = { version = "~0.62", features = ["Win32_System_Console"] }
|
||||
windows-registry = "~0.6"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
124
README.md
124
README.md
@@ -1,16 +1,16 @@
|
||||
<div align="center">
|
||||
<img alt="Topgrade" src="doc/topgrade.png" width="850px">
|
||||
|
||||
<!--
|
||||
<a href="https://github.com/topgrade-rs/topgrade/releases"><img alt="GitHub Release" src="https://img.shields.io/github/release/r-darwish/topgrade.svg"></a>
|
||||
<a href="https://crates.io/crates/topgrade"><img alt="crates.io" src="https://img.shields.io/crates/v/topgrade.svg"></a>
|
||||
<a href="https://aur.archlinux.org/packages/topgrade"><img alt="AUR" src="https://img.shields.io/aur/version/topgrade.svg"></a>
|
||||
<a href="https://formulae.brew.sh/formula/topgrade"><img alt="Homebrew" src="https://img.shields.io/homebrew/v/topgrade.svg"></a>
|
||||
-->
|
||||
<h1>
|
||||
<img alt="Topgrade" src="doc/topgrade_transparent.png" width="850px">
|
||||
</h1>
|
||||
|
||||
<img alt="Demo" src="doc/screenshot.gif" width="550px">
|
||||
<a href="https://github.com/topgrade-rs/topgrade/releases"><img alt="GitHub Release" src="https://img.shields.io/github/release/topgrade-rs/topgrade.svg"></a>
|
||||
<a href="https://crates.io/crates/topgrade"><img alt="crates.io" src="https://img.shields.io/crates/v/topgrade.svg"></a>
|
||||
<a href="https://aur.archlinux.org/packages/topgrade"><img alt="AUR" src="https://img.shields.io/aur/version/topgrade.svg"></a>
|
||||
<a href="https://formulae.brew.sh/formula/topgrade"><img alt="Homebrew" src="https://img.shields.io/homebrew/v/topgrade.svg"></a>
|
||||
|
||||
<img alt="Demo" src="doc/topgrade_demo.gif">
|
||||
</div>
|
||||
|
||||
|
||||
## Introduction
|
||||
|
||||
> **Note**
|
||||
@@ -22,68 +22,104 @@ To remedy this, **Topgrade** detects which tools you use and runs the appropriat
|
||||
|
||||
## Installation
|
||||
|
||||
- Arch Linux: [AUR](https://aur.archlinux.org/packages/topgrade) package.
|
||||
- NixOS: _topgrade_ package in `nixpkgs`.
|
||||
- macOS: [Homebrew](https://formulae.brew.sh/formula/topgrade) or [MacPorts](https://ports.macports.org/port/topgrade/).
|
||||
[](https://repology.org/project/topgrade/versions)
|
||||
|
||||
Other systems users can either use `cargo install` or use the compiled binaries from the release page.
|
||||
The compiled binaries contain a self-upgrading feature.
|
||||
### Official
|
||||
|
||||
Topgrade requires Rust 1.51 or above.
|
||||
- Self-updating binary (all platforms): [releases](https://github.com/topgrade-rs/topgrade/releases)
|
||||
- Install from source (all platforms): [`cargo install topgrade`](https://crates.io/crates/topgrade)
|
||||
- Debian/Ubuntu ([deb-get](https://github.com/wimpysworld/deb-get)):
|
||||
[`deb-get install topgrade`](https://github.com/wimpysworld/deb-get/blob/main/01-main/packages/topgrade)
|
||||
- Arch Linux (AUR): [topgrade](https://aur.archlinux.org/packages/topgrade)
|
||||
or [topgrade-bin](https://aur.archlinux.org/packages/topgrade-bin)
|
||||
- [PyPi](https://pypi.org/): `pip`, `pipx`, or `uv tool` [
|
||||
`install topgrade`](https://pypi.org/project/topgrade/)
|
||||
- Windows ([Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/)): [
|
||||
`winget install --id=topgrade-rs.topgrade -e`](https://winstall.app/apps/topgrade-rs.topgrade)
|
||||
- macOS or Linux ([Homebrew](https://brew.sh/)): [`brew install topgrade`](https://formulae.brew.sh/formula/topgrade)
|
||||
- Fedora/RHEL/AlmaLinux/CentOS-Stream ([Copr](https://copr.fedorainfracloud.org/)): [
|
||||
`sudo dnf copr enable lilay/topgrade && sudo dnf install topgrade`](https://copr.fedorainfracloud.org/coprs/lilay/topgrade/)
|
||||
|
||||
## Documentation
|
||||
### Community-maintained
|
||||
|
||||
> **Warning**
|
||||
> Work in Progress
|
||||
|
||||
You can visit the documentation at [topgrade-rs.github.io](https://topgrade-rs.github.io/) .
|
||||
- Windows ([Chocolatey](https://chocolatey.org/)): [
|
||||
`choco install topgrade`](https://community.chocolatey.org/packages/topgrade)
|
||||
- Windows ([Scoop](https://scoop.sh/)): [
|
||||
`scoop bucket add main && scoop install main/topgrade`](https://scoop.sh/#/apps?q=topgrade)
|
||||
- macOS ([MacPorts](https://www.macports.org/)): [
|
||||
`sudo port install topgrade`](https://ports.macports.org/port/topgrade/)
|
||||
- NixOS or Nix (nixpkgs): [topgrade](https://search.nixos.org/packages?show=topgrade)
|
||||
- Void Linux: [`sudo xbps-install -S topgrade`](https://voidlinux.org/packages/?arch=x86_64&q=topgrade)
|
||||
|
||||
## Usage
|
||||
|
||||
Just run `topgrade`.
|
||||
See [the documentation](https://topgrade-rs.github.io/) for the list of things Topgrade supports.
|
||||
|
||||
## Customization
|
||||
## Configuration
|
||||
|
||||
See `config.example.toml` for an example configuration file.
|
||||
|
||||
### Configuration path
|
||||
## Migration and Breaking Changes
|
||||
|
||||
The configuration should be placed in the following paths depending by the operating system:
|
||||
Whenever there is a **breaking change**, the major version number will be bumped,
|
||||
and we will document these changes in the release note, please take a look at
|
||||
it when updated to a major release.
|
||||
|
||||
- **Windows** - `%APPDATA%/topgrade.toml`
|
||||
- **macOS** and **other Unix systems** - `${XDG_CONFIG_HOME:-~/.config}/topgrade.toml`
|
||||
> Got a question? Feel free to open an issue or discussion!
|
||||
|
||||
## Contribution
|
||||
### Configuration Path
|
||||
|
||||
### Problems or missing features?
|
||||
#### `CONFIG_DIR` on each platform
|
||||
|
||||
Open a new Issue describing your problem and if possible with a possible solution.
|
||||
- **Windows**: `%APPDATA%`
|
||||
- **macOS** and **other Unix systems**: `${XDG_CONFIG_HOME:-~/.config}`
|
||||
|
||||
### Missing a feature or found an unsupported tool/distro?
|
||||
`topgrade` will look for the configuration file in the following places, in order of priority:
|
||||
|
||||
Just let us now what you are missing by opening an issue.
|
||||
For tools please open an Issue describing the tool, which platforms it supports and if possible, give us an example of its usage.
|
||||
1. `CONFIG_DIR/topgrade.toml`
|
||||
2. `CONFIG_DIR/topgrade/topgrade.toml`
|
||||
|
||||
### Want to contribute to the code?
|
||||
If the file with higher priority is present, no matter it is valid or not, the other configuration files will be
|
||||
ignored.
|
||||
|
||||
Just fork the repository and start coding.
|
||||
On the first run(no configuration file exists), `topgrade` will create a configuration file at
|
||||
`CONFIG_DIR/topgrade.toml` for you.
|
||||
|
||||
### Contribution Guidelines
|
||||
### Custom Commands
|
||||
|
||||
- Check if your code passes `cargo fmt` and `cargo clippy`.
|
||||
- Check if your code is self explanatory, if not it should be documented by comments.
|
||||
- Make a Pull Request to the dev branch for new features or to the bug-fixes branch for bug fixes.
|
||||
Custom commands can be defined in the config file which can be run before, during, or after the inbuilt commands, as
|
||||
required.
|
||||
By default, the custom commands are run using a new shell according to the `$SHELL` environment variable on unix (falls
|
||||
back to `sh`) or `pwsh` on windows (falls back to `powershell`).
|
||||
|
||||
## Remote execution
|
||||
On unix, if you want to run your command using an interactive shell, for example to source your shell's rc files, you
|
||||
can add `-i` at the start of your custom command.
|
||||
But note that this requires the command to exit the shell correctly or else the shell will hang indefinitely.
|
||||
|
||||
## Remote Execution
|
||||
|
||||
You can specify a key called `remote_topgrades` in the configuration file.
|
||||
This key should contain a list of hostnames that have Topgrade installed on them.
|
||||
Topgrade will use `ssh` to run `topgrade` on remote hosts before acting locally.
|
||||
To limit the execution only to specific hosts use the `--remote-host-limit` parameter.
|
||||
|
||||
## ToDo
|
||||
## Contribution
|
||||
|
||||
- [ ] Add a proper testing framework to the code base.
|
||||
- [ ] Add unit tests for package managers.
|
||||
- [ ] Split up code into more maintainable parts, eg. putting every linux package manager in a own submodule of linux.rs.
|
||||
### Problems or missing features?
|
||||
|
||||
Open a new issue describing your problem and if possible provide a solution.
|
||||
|
||||
### Missing a feature or found an unsupported tool/distro?
|
||||
|
||||
Just let us now what you are missing by opening an issue.
|
||||
For tools, please open an issue describing the tool, which platforms it supports and if possible, give us an example of
|
||||
its usage.
|
||||
|
||||
### Want to contribute?
|
||||
|
||||
See [CONTRIBUTING.md](https://github.com/topgrade-rs/topgrade/blob/master/CONTRIBUTING.md)
|
||||
|
||||
## Discord server
|
||||
|
||||
Welcome to [join](https://discord.gg/Q8HGGWundY) our Discord server if you want
|
||||
to discuss Topgrade!
|
||||
|
||||
21
RELEASE_PROCEDURE.md
Normal file
21
RELEASE_PROCEDURE.md
Normal file
@@ -0,0 +1,21 @@
|
||||
Non-major versions go via release-plz.
|
||||
|
||||
1. bumps the version number.
|
||||
|
||||
> If there are breaking changes, the major version number should be increased.
|
||||
|
||||
2. If the major versioin number gets bumped, update [SECURITY.md][SECURITY_file_link].
|
||||
|
||||
[SECURITY_file_link]: https://github.com/topgrade-rs/topgrade/blob/main/SECURITY.md
|
||||
|
||||
3. Overwrite [`BREAKINGCHANGES`][breaking_changes] with
|
||||
[`BREAKINGCHANGES_dev`][breaking_changes_dev], and create a new dev file:
|
||||
|
||||
```sh'
|
||||
$ cd topgrade
|
||||
$ mv BREAKINGCHANGES_dev.md BREAKINGCHANGES.md
|
||||
$ touch BREAKINGCHANGES_dev.md
|
||||
```
|
||||
|
||||
[breaking_changes_dev]: https://github.com/topgrade-rs/topgrade/blob/main/BREAKINGCHANGES_dev.md
|
||||
[breaking_changes]: https://github.com/topgrade-rs/topgrade/blob/main/BREAKINGCHANGES.md
|
||||
9
SECURITY.md
Normal file
9
SECURITY.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
To report a security vulnerability, go to [the security tab](https://github.com/topgrade-rs/topgrade/security) and click "Report a vulnerability".
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We only support the latest version of Topgrade. Fixes are not backported.
|
||||
35
build-all.sh
35
build-all.sh
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/usr/bin/env bash
|
||||
|
||||
build_function() {
|
||||
rustup update
|
||||
cargo install cross
|
||||
@@ -20,7 +21,7 @@ build_function() {
|
||||
|
||||
package_function() {
|
||||
|
||||
cd build
|
||||
cd build || exit 1
|
||||
mkdir x86_64-unknown-linux-gnu/
|
||||
mkdir x86_64-unknown-linux-musl/
|
||||
mkdir x86_64-unknown-freebsd/
|
||||
@@ -35,28 +36,28 @@ package_function() {
|
||||
cp ../target/aarch64-unknown-linux-musl/release/topgrade aarch64-unknown-linux-musl/topgrade
|
||||
cp ../target/x86_64-pc-windows-gnu/release/topgrade.exe x86_64-pc-windows-gnu/topgrade.exe
|
||||
|
||||
cd x86_64-unknown-linux-gnu/
|
||||
tar -czf ../topgrade-${ans}-x86_64-linux-gnu.tar.gz topgrade
|
||||
cd x86_64-unknown-linux-gnu/ || exit 1
|
||||
tar -czf "../topgrade-${ans}-x86_64-linux-gnu.tar.gz" topgrade
|
||||
cd ..
|
||||
|
||||
cd x86_64-unknown-linux-musl
|
||||
tar -czf ../topgrade-${ans}-x86_64-linux-musl.tar.gz topgrade
|
||||
cd x86_64-unknown-linux-musl/ || exit 1
|
||||
tar -czf "../topgrade-${ans}-x86_64-linux-musl.tar.gz" topgrade
|
||||
cd ..
|
||||
|
||||
cd x86_64-unknown-freebsd/
|
||||
tar -czf ../topgrade-${ans}-x86_64-freebsd.tar.gz topgrade
|
||||
cd x86_64-unknown-freebsd/ || exit 1
|
||||
tar -czf "../topgrade-${ans}-x86_64-freebsd.tar.gz" topgrade
|
||||
cd ..
|
||||
|
||||
cd aarch64-unknown-linux-gnu/
|
||||
tar -czf ../topgrade-${ans}-aarch64-linux-gnu.tar.gz topgrade
|
||||
cd aarch64-unknown-linux-gnu/ || exit 1
|
||||
tar -czf "../topgrade-${ans}-aarch64-linux-gnu.tar.gz" topgrade
|
||||
cd ..
|
||||
|
||||
cd aarch64-unknown-linux-musl/
|
||||
tar -czf ../topgrade-${ans}-aarch64-linux-musl.tar.gz topgrade
|
||||
cd aarch64-unknown-linux-musl/ || exit 1
|
||||
tar -czf "../topgrade-${ans}-aarch64-linux-musl.tar.gz" topgrade
|
||||
cd ..
|
||||
|
||||
cd x86_64-pc-windows-gnu/
|
||||
zip -q ../topgrade-${ans}-x86_64-windows.zip topgrade.exe
|
||||
cd x86_64-pc-windows-gnu/ || exit 1
|
||||
zip -q "../topgrade-${ans}-x86_64-windows.zip" topgrade.exe
|
||||
cd ..
|
||||
cd ..
|
||||
|
||||
@@ -65,17 +66,19 @@ package_function() {
|
||||
print_checksums() {
|
||||
|
||||
|
||||
cd build/
|
||||
sha256sum topgrade-${ans}-*
|
||||
cd build/ || exit 1
|
||||
sha256sum topgrade-"${ans}"-*
|
||||
cd ../
|
||||
}
|
||||
|
||||
while true; do
|
||||
|
||||
echo "You should always have a look on scripts you download from the internet."
|
||||
# shellcheck disable=SC2162
|
||||
read -p "Do you still want to proceed? (y/n) " yn
|
||||
|
||||
echo -n "Input version number: "
|
||||
# shellcheck disable=SC2162
|
||||
read ans
|
||||
mkdir build
|
||||
|
||||
|
||||
5
clippy.toml
Normal file
5
clippy.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
disallowed-methods = [
|
||||
{ path = "std::process::Command::output", reason = "Use `output_checked[_with][_utf8]`" },
|
||||
{ path = "std::process::Command::spawn", reason = "Use `spawn_checked`" },
|
||||
{ path = "std::process::Command::status", reason = "Use `status_checked`" },
|
||||
]
|
||||
@@ -1,113 +1,424 @@
|
||||
# Don't ask for confirmations
|
||||
#assume_yes = true
|
||||
# Include any additional configuration file(s)
|
||||
# [include] sections are processed in the order you write them
|
||||
# Files in $CONFIG_DIR/topgrade.d/ are automatically included before this file
|
||||
[include]
|
||||
# paths = ["/etc/topgrade.toml"]
|
||||
|
||||
|
||||
[misc]
|
||||
# On Unix systems, Topgrade should not be run as root, it
|
||||
# will run commands with sudo or equivalent where needed.
|
||||
# Set this to true to suppress the warning and confirmation
|
||||
# prompt if Topgrade detects it is being run as root.
|
||||
# (default: false)
|
||||
# allow_root = false
|
||||
|
||||
# Run `sudo -v` to cache credentials at the start of the run
|
||||
# This avoids a blocking password prompt in the middle of an unattended run
|
||||
# (default: false)
|
||||
# pre_sudo = false
|
||||
|
||||
# Sudo command to be used
|
||||
# sudo_command = "sudo"
|
||||
|
||||
# Disable specific steps - same options as the command line flag
|
||||
#disable = ["system", "emacs"]
|
||||
# disable = ["system", "emacs"]
|
||||
|
||||
# Ignore failures for these steps
|
||||
#ignore_failures = ["powershell"]
|
||||
|
||||
# Run specific steps - same options as the command line flag
|
||||
#only = ["system", "emacs"]
|
||||
|
||||
# Do not ask to retry failed steps (default: false)
|
||||
#no_retry = true
|
||||
|
||||
# Run inside tmux
|
||||
#run_in_tmux = true
|
||||
# ignore_failures = ["powershell"]
|
||||
|
||||
# List of remote machines with Topgrade installed on them
|
||||
#remote_topgrades = ["toothless", "pi", "parnas"]
|
||||
|
||||
# Arguments to pass SSH when upgrading remote systems
|
||||
#ssh_arguments = "-o ConnectTimeout=2"
|
||||
# remote_topgrades = ["toothless", "pi", "parnas"]
|
||||
|
||||
# Path to Topgrade executable on remote machines
|
||||
#remote_topgrade_path = ".cargo/bin/topgrade"
|
||||
# remote_topgrade_path = ".cargo/bin/topgrade"
|
||||
|
||||
# Arguments to pass to SSH when upgrading remote systems
|
||||
# ssh_arguments = "-o ConnectTimeout=2"
|
||||
|
||||
# Arguments to pass tmux when pulling Repositories
|
||||
#tmux_arguments = "-S /var/tmux.sock"
|
||||
# tmux_arguments = "-S /var/tmux.sock"
|
||||
|
||||
# Do not set the terminal title
|
||||
#set_title = false
|
||||
# Do not set the terminal title (default: true)
|
||||
# set_title = true
|
||||
|
||||
# Display the time in step titles
|
||||
# Display the time in step titles (default: true)
|
||||
# display_time = true
|
||||
|
||||
# Cleanup temporary or old files
|
||||
#cleanup = true
|
||||
# Don't ask for confirmations (no default value)
|
||||
# assume_yes = true
|
||||
|
||||
# Skip sending a notification at the end of a run
|
||||
#skip_notify = true
|
||||
# Do not ask to retry failed steps (default: false)
|
||||
# no_retry = true
|
||||
|
||||
[git]
|
||||
#max_concurrency = 5
|
||||
# Additional git repositories to pull
|
||||
#repos = [
|
||||
# "~/src/*/",
|
||||
# "~/.config/something"
|
||||
#]
|
||||
# Show the reason for skipped steps (default: false)
|
||||
# This has no effect if the "only" option is specified
|
||||
# show_skipped = true
|
||||
|
||||
# Don't pull the predefined git repos
|
||||
#pull_predefined = false
|
||||
# Run inside tmux (default: false)
|
||||
# run_in_tmux = true
|
||||
|
||||
# Arguments to pass Git when pulling Repositories
|
||||
#arguments = "--rebase --autostash"
|
||||
# Changes the way topgrade interacts with
|
||||
# the tmux session, creating the session
|
||||
# and only attaching to it if not inside tmux
|
||||
# (default: "attach_if_not_in_session", allowed values: "attach_if_not_in_session", "attach_always")
|
||||
# tmux_session_mode = "attach_if_not_in_session"
|
||||
|
||||
# Cleanup temporary or old files (default: false)
|
||||
# cleanup = true
|
||||
|
||||
# Send a notification for every step (default: false)
|
||||
# notify_each_step = false
|
||||
|
||||
# Skip sending a notification at the end of a run (default: false)
|
||||
# skip_notify = true
|
||||
|
||||
# The Bash-it branch to update (default: "stable")
|
||||
# bashit_branch = "stable"
|
||||
|
||||
# Run specific steps - same options as the command line flag
|
||||
# only = ["system", "emacs"]
|
||||
|
||||
# Whether to self update
|
||||
#
|
||||
# this will be ignored if the binary is built without self update support
|
||||
#
|
||||
# available also via setting the environment variable TOPGRADE_NO_SELF_UPGRADE)
|
||||
# no_self_update = true
|
||||
|
||||
# Extra tracing filter directives
|
||||
# These are prepended to the `--log-filter` argument
|
||||
# See: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
|
||||
# log_filters = ["topgrade::command=debug", "warn"]
|
||||
|
||||
# Whether to show a distribution-specific summary if applicable, e.g. listing
|
||||
# Pacman backup configuration files (*.pacsave and *.pacnew)
|
||||
# (default: true)
|
||||
# show_distribution_summary = false
|
||||
|
||||
[composer]
|
||||
#self_update = true
|
||||
|
||||
# Commands to run before anything
|
||||
[pre_commands]
|
||||
#"Emacs Snapshot" = "rm -rf ~/.emacs.d/elpa.bak && cp -rl ~/.emacs.d/elpa ~/.emacs.d/elpa.bak"
|
||||
# "Emacs Snapshot" = "rm -rf ~/.emacs.d/elpa.bak && cp -rl ~/.emacs.d/elpa ~/.emacs.d/elpa.bak"
|
||||
|
||||
|
||||
# Commands to run after anything
|
||||
[post_commands]
|
||||
# "Emacs Snapshot" = "rm -rf ~/.emacs.d/elpa.bak && cp -rl ~/.emacs.d/elpa ~/.emacs.d/elpa.bak"
|
||||
|
||||
|
||||
# Custom commands
|
||||
[commands]
|
||||
#"Python Environment" = "~/dev/.env/bin/pip install -i https://pypi.python.org/simple -U --upgrade-strategy eager jupyter"
|
||||
# "Python Environment" = "~/dev/.env/bin/pip install -i https://pypi.python.org/simple -U --upgrade-strategy eager jupyter"
|
||||
# "Custom command using interactive shell (unix)" = "-i vim_upgrade"
|
||||
|
||||
|
||||
[python]
|
||||
# enable_pip_review = true ###disabled by default
|
||||
# enable_pip_review_local = true ###disabled by default
|
||||
# enable_pipupgrade = true ###disabled by default
|
||||
# pipupgrade_arguments = "-y -u --pip-path pip" ###disabled by default
|
||||
|
||||
# For the poetry step, by default, Topgrade skips its update if poetry is not
|
||||
# installed with the official script. This configuration entry forces Topgrade
|
||||
# to run the update in this case.
|
||||
#
|
||||
# (default: false)
|
||||
# poetry_force_self_update = true
|
||||
|
||||
|
||||
[conda]
|
||||
# Additional named conda environments to update (`conda env update -n env_name`)
|
||||
# env_names = [
|
||||
# "Toolbox",
|
||||
# "PyTorch"
|
||||
# ]
|
||||
# Additional conda environment paths to update (`conda env update -p env_path`)
|
||||
# env_paths = [
|
||||
# "~/webserver/.conda/",
|
||||
# "~/experiments/.conda/"
|
||||
# ]
|
||||
|
||||
|
||||
[composer]
|
||||
# self_update = true
|
||||
|
||||
|
||||
[brew]
|
||||
#greedy_cask = true
|
||||
#autoremove = true
|
||||
# For the BrewCask step
|
||||
# If `Repo Cask Upgrade` exists, then use the `-a` option.
|
||||
# Otherwise, use the `--greedy` option.
|
||||
# greedy_cask = true
|
||||
|
||||
# For the BrewCask step
|
||||
# If `Repo Cask Upgrade` does not exist, then use the `--greedy_latest` option.
|
||||
# NOTE: the above entry `greedy_cask` contains this entry, though you can enable
|
||||
# both of them, they won't clash with each other.
|
||||
# greedy_latest = true
|
||||
|
||||
# For the BrewCask step
|
||||
# If `Repo Cask Upgrade` does not exist, then use the `--greedy_auto_updates` option.
|
||||
# NOTE: the above entry `greedy_cask` contains this entry, though you can enable
|
||||
# both of them, they won't clash with each other.
|
||||
# greedy_auto_updates = true
|
||||
|
||||
# For the BrewFormula step
|
||||
# Execute `brew autoremove` after the step.
|
||||
# autoremove = true
|
||||
|
||||
# For the BrewFormula step
|
||||
# Upgrade formulae built from the HEAD branch; `brew upgrade --fetch-HEAD`
|
||||
# fetch_head = true
|
||||
|
||||
|
||||
[linux]
|
||||
# Arch Package Manager to use. Allowed values: autodetect, trizen, aura, paru, yay, pikaur, pacman, pamac.
|
||||
#arch_package_manager = "pacman"
|
||||
# Arch Package Manager to use.
|
||||
# Allowed values:
|
||||
# autodetect, aura, garuda_update, pacman, pamac, paru, pikaur, trizen, yay
|
||||
# arch_package_manager = "pacman"
|
||||
|
||||
# Arguments to pass yay (or paru) when updating packages
|
||||
#yay_arguments = "--nodevel"
|
||||
#aura_aur_arguments = "-kx"
|
||||
#aura_pacman_arguments = ""
|
||||
#show_arch_news = true
|
||||
#trizen_arguments = "--devel"
|
||||
#pikaur_arguments = ""
|
||||
#pamac_arguments = "--no-devel"
|
||||
#enable_tlmgr = true
|
||||
#emerge_sync_flags = "-q"
|
||||
#emerge_update_flags = "-uDNa --with-bdeps=y world"
|
||||
#redhat_distro_sync = false
|
||||
#rpm_ostree = false
|
||||
# yay_arguments = "--nodevel"
|
||||
|
||||
# Arguments to pass dnf when updating packages
|
||||
# dnf_arguments = "--refresh"
|
||||
|
||||
# aura_aur_arguments = "-kx"
|
||||
|
||||
# aura_pacman_arguments = ""
|
||||
# garuda_update_arguments = ""
|
||||
|
||||
# show_arch_news = true
|
||||
|
||||
# trizen_arguments = "--devel"
|
||||
|
||||
# pikaur_arguments = ""
|
||||
|
||||
# pamac_arguments = "--no-devel"
|
||||
|
||||
# enable_tlmgr = true
|
||||
|
||||
# emerge_sync_flags = "-q"
|
||||
|
||||
# emerge_update_flags = "-uDNa --with-bdeps=y world"
|
||||
|
||||
# redhat_distro_sync = false
|
||||
|
||||
# suse_dup = false
|
||||
|
||||
# rpm_ostree = false
|
||||
|
||||
# For Fedora/CentOS/RHEL Atomic variants, if `bootc` is available and this configuration entry is set to true, use
|
||||
# it to do the update - Will also supersede rpm-ostree if enabled
|
||||
# (default: false)
|
||||
# bootc = false
|
||||
|
||||
# nix_arguments = "--flake"
|
||||
|
||||
# nix_env_arguments = "--prebuilt-only"
|
||||
|
||||
# Extra Home Manager arguments
|
||||
# home_manager_arguments = ["--flake", "file"]
|
||||
|
||||
|
||||
[mandb]
|
||||
# Enable the mandb step (to update manual entries).
|
||||
# Mandb is updated in the background by a service on most systems by default.
|
||||
# (default: false)
|
||||
# enable = true
|
||||
|
||||
|
||||
[pkgfile]
|
||||
# Enable the pkgfile step (to update the pkgfile database).
|
||||
# Pkgfile is sometimes installed by default, but often not used and heavy to update.
|
||||
# (default: false)
|
||||
# enable = true
|
||||
|
||||
|
||||
[git]
|
||||
# How many repos to pull at max in parallel
|
||||
# max_concurrency = 5
|
||||
|
||||
# Additional git repositories to pull
|
||||
# repos = [
|
||||
# "~/src/*/",
|
||||
# "~/.config/something"
|
||||
# ]
|
||||
|
||||
# Don't pull the predefined git repos
|
||||
# pull_predefined = false
|
||||
|
||||
# Arguments to pass Git when pulling Repositories
|
||||
# arguments = "--rebase --autostash"
|
||||
|
||||
|
||||
[windows]
|
||||
# Manually select Windows updates
|
||||
#accept_all_updates = false
|
||||
#open_remotes_in_new_terminal = true
|
||||
# accept_all_updates = false
|
||||
|
||||
# Controls whether to automatically reboot the computer when updates are
|
||||
# installed that request it. (default: "no", allowed values: "yes", "no", "ask")
|
||||
# updates_auto_reboot = "yes"
|
||||
|
||||
# open_remotes_in_new_terminal = true
|
||||
|
||||
# wsl_update_pre_release = true
|
||||
|
||||
# wsl_update_use_web_download = true
|
||||
|
||||
# The default for winget_install_silently is true,
|
||||
# this example turns off silent install.
|
||||
# winget_install_silently = false
|
||||
|
||||
# Causes Topgrade to rename itself during the run to allow package managers
|
||||
# to upgrade it. Use this only if you installed Topgrade by using a package
|
||||
# manager such as Scoop or Cargo
|
||||
#self_rename = true
|
||||
# self_rename = true
|
||||
|
||||
# Use sudo to elevate privileges for the Windows Package Manager (winget)
|
||||
# Only use this option if you want to run the Winget step in sudo-mode.
|
||||
# Running winget in sudo-mode is generally not recommended, as not every
|
||||
# package supports installing / upgrading in sudo-mode and it may cause issues
|
||||
# with some packages or may even cause the Winget-step to fail.
|
||||
# If any problems occur, please try running Topgrade without this option first
|
||||
# before reporting an issue.
|
||||
# (default: false)
|
||||
# winget_use_sudo = true
|
||||
|
||||
|
||||
[chezmoi]
|
||||
# Exclude encrypted files from update
|
||||
# (default: false)
|
||||
# exclude_encrypted = false
|
||||
|
||||
[mise]
|
||||
# Upgrades to the latest version available, bumping the version in mise.toml
|
||||
# (default: false)
|
||||
# bump = false
|
||||
|
||||
# Number of jobs to run in parallel
|
||||
# (default: 4)
|
||||
# jobs = 4
|
||||
|
||||
# Run interactively
|
||||
# (default: false)
|
||||
# interactive = false
|
||||
|
||||
|
||||
[npm]
|
||||
# Use sudo if the NPM directory isn't owned by the current user
|
||||
#use_sudo = true
|
||||
# use_sudo = true
|
||||
|
||||
|
||||
[yarn]
|
||||
# Run `yarn global upgrade` with `sudo`
|
||||
# use_sudo = true
|
||||
|
||||
|
||||
[deno]
|
||||
# Upgrade deno executable to the given version.
|
||||
# version = "stable"
|
||||
|
||||
|
||||
[vim]
|
||||
# For `vim-plug`, execute `PlugUpdate!` instead of `PlugUpdate`
|
||||
# force_plug_update = true
|
||||
|
||||
|
||||
[firmware]
|
||||
# Offer to update firmware; if false just check for and display available updates
|
||||
#upgrade = true
|
||||
# upgrade = true
|
||||
|
||||
|
||||
[vagrant]
|
||||
# Vagrant directories
|
||||
# directories = []
|
||||
|
||||
# power on vagrant boxes if needed
|
||||
# power_on = true
|
||||
|
||||
# Always suspend vagrant boxes instead of powering off
|
||||
# always_suspend = true
|
||||
|
||||
|
||||
[flatpak]
|
||||
# Use sudo for updating the system-wide installation
|
||||
#use_sudo = true
|
||||
# use_sudo = true
|
||||
|
||||
|
||||
[distrobox]
|
||||
#use_root = false
|
||||
#containers = ["archlinux-latest"]
|
||||
# use_root = false
|
||||
|
||||
# containers = ["archlinux-latest"]
|
||||
[containers]
|
||||
# Specify the containers to ignore while updating (Wildcard supported)
|
||||
# ignored_containers = ["ghcr.io/rancher-sandbox/rancher-desktop/rdx-proxy:latest", "docker.io*"]
|
||||
# Specify the runtime to use for containers (default: "docker", allowed values: "docker", "podman")
|
||||
# runtime = "podman"
|
||||
|
||||
# Run 'docker system prune' to clean up unused containers, networks, and build cache
|
||||
# (default: false)
|
||||
# system_prune = false
|
||||
|
||||
[lensfun]
|
||||
# If disabled, Topgrade invokes `lensfun‑update‑data` without root privilege,
|
||||
# then the update will be only available to you. Otherwise, `sudo` is required,
|
||||
# and the update will be installed system-wide, i.e., available to all users.
|
||||
# (default: false)
|
||||
# use_sudo = false
|
||||
|
||||
[julia]
|
||||
# If disabled, Topgrade invokes julia with the --startup-file=no CLI option.
|
||||
#
|
||||
# This may be desirable to avoid loading outdated packages with "using" directives
|
||||
# in the startup file, which might cause the update run to fail.
|
||||
# (default: true)
|
||||
# startup_file = true
|
||||
|
||||
[zigup]
|
||||
# Version strings passed to zigup.
|
||||
# These may be pinned versions such as "0.13.0" or branches such as "master".
|
||||
# Each one will be updated in its own zigup invocation.
|
||||
# (default: ["master"])
|
||||
# target_versions = ["master", "0.13.0"]
|
||||
|
||||
# Specifies the directory that the zig files will be installed to.
|
||||
# If defined, passed with the --install-dir command line flag.
|
||||
# If not defined, zigup will use its default behaviour.
|
||||
# (default: not defined)
|
||||
# install_dir = "~/.zig"
|
||||
|
||||
# Specifies the path of the symlink which will be set to point at the default compiler version.
|
||||
# If defined, passed with the --path-link command line flag.
|
||||
# If not defined, zigup will use its default behaviour.
|
||||
# This is not meaningful if set_default is not enabled.
|
||||
# (default: not defined)
|
||||
# path_link = "~/.bin/zig"
|
||||
|
||||
# If enabled, run `zigup clean` after updating all versions.
|
||||
# If enabled, each updated version above will be marked with `zigup keep`.
|
||||
# (default: false)
|
||||
# cleanup = false
|
||||
|
||||
[vscode]
|
||||
# If this is set and is a non-empty string, it specifies the profile the
|
||||
# extensions should be updated for.
|
||||
# (default: this won't be set by default)
|
||||
# profile = ""
|
||||
|
||||
[pixi]
|
||||
# Show the release notes of the latest pixi release
|
||||
# during the pixi step
|
||||
# (default: false)
|
||||
# include_release_notes = false
|
||||
|
||||
[doom]
|
||||
# If this is set to true, the `--aot` flag is added to `doom upgrade`,
|
||||
# which enables ahead-of-time native compilation of packages.
|
||||
# (default: false)
|
||||
# aot = true
|
||||
|
||||
[rustup]
|
||||
# If set, updates only these channels.
|
||||
# (default: [] (all channels))
|
||||
# channels = ["stable"]
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 718 KiB |
BIN
doc/topgrade_demo.gif
Normal file
BIN
doc/topgrade_demo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 MiB |
BIN
doc/topgrade_transparent.png
Normal file
BIN
doc/topgrade_transparent.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
1420
locales/app.yml
Normal file
1420
locales/app.yml
Normal file
File diff suppressed because it is too large
Load Diff
24
pyproject.toml
Normal file
24
pyproject.toml
Normal file
@@ -0,0 +1,24 @@
|
||||
[build-system]
|
||||
requires = ["maturin>=1.0,<2.0"]
|
||||
build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "topgrade"
|
||||
dynamic = ["version"]
|
||||
description = "Upgrade all the things"
|
||||
readme = "README.md"
|
||||
license = "GPL-3.0-or-later"
|
||||
requires-python = ">=3.7"
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||
"Programming Language :: Rust",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
]
|
||||
|
||||
urls.bugs = "https://github.com/topgrade-rs/topgrade/issues"
|
||||
urls.homepage = "https://github.com/topgrade-rs/topgrade"
|
||||
|
||||
|
||||
[tool.maturin]
|
||||
bindings = "bin"
|
||||
10
renovate.json
Normal file
10
renovate.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:best-practices",
|
||||
":semanticCommits"
|
||||
],
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "1.87.0"
|
||||
171
src/breaking_changes.rs
Normal file
171
src/breaking_changes.rs
Normal file
@@ -0,0 +1,171 @@
|
||||
//! Inform the users of the breaking changes introduced in this major release.
|
||||
//!
|
||||
//! Print the breaking changes and possibly a migration guide when:
|
||||
//! 1. The Topgrade being executed is a new major release
|
||||
//! 2. This is the first launch of that major release
|
||||
|
||||
#[cfg(windows)]
|
||||
use crate::WINDOWS_DIRS;
|
||||
#[cfg(unix)]
|
||||
use crate::XDG_DIRS;
|
||||
use crate::terminal::print_separator;
|
||||
use color_eyre::eyre::Result;
|
||||
use etcetera::base_strategy::BaseStrategy;
|
||||
use rust_i18n::t;
|
||||
use std::{
|
||||
env::var,
|
||||
fs::{OpenOptions, read_to_string},
|
||||
io::Write,
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
/// Version string x.y.z
|
||||
static VERSION_STR: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
/// Version info
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Version {
|
||||
_major: u64,
|
||||
minor: u64,
|
||||
patch: u64,
|
||||
}
|
||||
|
||||
impl FromStr for Version {
|
||||
type Err = std::convert::Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
const NOT_SEMVER: &str = "Topgrade version is not semantic";
|
||||
const NOT_NUMBER: &str = "Topgrade version is not dot-separated numbers";
|
||||
|
||||
let mut iter = s.split('.').take(3);
|
||||
let major = iter.next().expect(NOT_SEMVER).parse().expect(NOT_NUMBER);
|
||||
let minor = iter.next().expect(NOT_SEMVER).parse().expect(NOT_NUMBER);
|
||||
let patch = iter.next().expect(NOT_SEMVER).parse().expect(NOT_NUMBER);
|
||||
|
||||
// They cannot be all 0s
|
||||
assert!(
|
||||
!(major == 0 && minor == 0 && patch == 0),
|
||||
"Version numbers cannot be all 0s"
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
_major: major,
|
||||
minor,
|
||||
patch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Version {
|
||||
/// True if this version is a new major release.
|
||||
pub(crate) fn is_new_major_release(&self) -> bool {
|
||||
// We have already checked that they cannot all be zeros, so `self.major`
|
||||
// is guaranteed to be non-zero.
|
||||
self.minor == 0 && self.patch == 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Topgrade's breaking changes
|
||||
///
|
||||
/// We store them in the compiled binary.
|
||||
pub(crate) static BREAKINGCHANGES: &str = include_str!("../BREAKINGCHANGES.md");
|
||||
|
||||
/// Return platform's data directory.
|
||||
fn data_dir() -> PathBuf {
|
||||
#[cfg(unix)]
|
||||
return XDG_DIRS.data_dir();
|
||||
|
||||
#[cfg(windows)]
|
||||
return WINDOWS_DIRS.data_dir();
|
||||
}
|
||||
|
||||
/// Return Topgrade's keep file path.
|
||||
///
|
||||
/// keep file is a file under the data directory containing a major version
|
||||
/// number, it will be created on first run and is used to check if an execution
|
||||
/// of Topgrade is the first run of a major release, for more details, see
|
||||
/// `first_run_of_major_release()`.
|
||||
fn keep_file_path() -> PathBuf {
|
||||
let keep_file = "topgrade_keep";
|
||||
data_dir().join(keep_file)
|
||||
}
|
||||
|
||||
/// If environment variable `TOPGRADE_SKIP_BRKC_NOTIFY` is set to `true`, then
|
||||
/// we won't notify the user of the breaking changes.
|
||||
pub(crate) fn should_skip() -> bool {
|
||||
if let Ok(var) = var("TOPGRADE_SKIP_BRKC_NOTIFY") {
|
||||
return var.as_str() == "true";
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// True if this is the first execution of a major release.
|
||||
pub(crate) fn first_run_of_major_release() -> Result<bool> {
|
||||
let version = VERSION_STR.parse::<Version>().expect("should be a valid version");
|
||||
let keep_file = keep_file_path();
|
||||
|
||||
// disable this lint here as the current code has better readability
|
||||
#[allow(clippy::collapsible_if)]
|
||||
if version.is_new_major_release() {
|
||||
if !keep_file.exists() || read_to_string(&keep_file)? != VERSION_STR {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Print breaking changes to the user.
|
||||
pub(crate) fn print_breaking_changes() {
|
||||
let header = format!(
|
||||
"{}",
|
||||
t!("Topgrade {version_str} Breaking Changes", version_str = VERSION_STR)
|
||||
);
|
||||
print_separator(header);
|
||||
let contents = if BREAKINGCHANGES.is_empty() {
|
||||
t!("No Breaking changes").to_string()
|
||||
} else {
|
||||
BREAKINGCHANGES.to_string()
|
||||
};
|
||||
println!("{contents}\n");
|
||||
}
|
||||
|
||||
/// This function will be ONLY executed when the user has confirmed the breaking
|
||||
/// changes, once confirmed, we write the keep file, which means the first run
|
||||
/// of this major release is finished.
|
||||
pub(crate) fn write_keep_file() -> Result<()> {
|
||||
std::fs::create_dir_all(data_dir())?;
|
||||
let keep_file = keep_file_path();
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(keep_file)?;
|
||||
let _ = file.write(VERSION_STR.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn is_new_major_release_works() {
|
||||
let first_major_release: Version = "1.0.0".parse().unwrap();
|
||||
let under_dev: Version = "0.1.0".parse().unwrap();
|
||||
|
||||
assert!(first_major_release.is_new_major_release());
|
||||
assert!(!under_dev.is_new_major_release());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Version numbers cannot be all 0s")]
|
||||
fn invalid_version() {
|
||||
let all_0 = "0.0.0";
|
||||
all_0.parse::<Version>().unwrap();
|
||||
}
|
||||
}
|
||||
250
src/command.rs
Normal file
250
src/command.rs
Normal file
@@ -0,0 +1,250 @@
|
||||
//! Utilities for running commands and providing user-friendly error messages.
|
||||
|
||||
use std::fmt::Display;
|
||||
use std::process::Child;
|
||||
use std::process::{Command, ExitStatus, Output};
|
||||
|
||||
use color_eyre::eyre;
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::eyre;
|
||||
|
||||
use crate::error::TopgradeError;
|
||||
|
||||
use tracing::debug;
|
||||
|
||||
/// Like [`Output`], but UTF-8 decoded.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Utf8Output {
|
||||
pub status: ExitStatus,
|
||||
pub stdout: String,
|
||||
pub stderr: String,
|
||||
}
|
||||
|
||||
impl TryFrom<Output> for Utf8Output {
|
||||
type Error = eyre::Error;
|
||||
|
||||
fn try_from(Output { status, stdout, stderr }: Output) -> Result<Self, Self::Error> {
|
||||
let stdout = String::from_utf8(stdout).map_err(|err| {
|
||||
eyre!(
|
||||
"Stdout contained invalid UTF-8: {}",
|
||||
String::from_utf8_lossy(err.as_bytes())
|
||||
)
|
||||
})?;
|
||||
let stderr = String::from_utf8(stderr).map_err(|err| {
|
||||
eyre!(
|
||||
"Stderr contained invalid UTF-8: {}",
|
||||
String::from_utf8_lossy(err.as_bytes())
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(Utf8Output { status, stdout, stderr })
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Output> for Utf8Output {
|
||||
type Error = eyre::Error;
|
||||
|
||||
fn try_from(Output { status, stdout, stderr }: &Output) -> Result<Self, Self::Error> {
|
||||
let stdout = String::from_utf8(stdout.clone()).map_err(|err| {
|
||||
eyre!(
|
||||
"Stdout contained invalid UTF-8: {}",
|
||||
String::from_utf8_lossy(err.as_bytes())
|
||||
)
|
||||
})?;
|
||||
let stderr = String::from_utf8(stderr.clone()).map_err(|err| {
|
||||
eyre!(
|
||||
"Stderr contained invalid UTF-8: {}",
|
||||
String::from_utf8_lossy(err.as_bytes())
|
||||
)
|
||||
})?;
|
||||
let status = *status;
|
||||
|
||||
Ok(Utf8Output { status, stdout, stderr })
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Utf8Output {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.stdout)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait for [`Command`], adding helpers to gather output while checking the exit
|
||||
/// status.
|
||||
///
|
||||
/// These also give us significantly better error messages, which include:
|
||||
///
|
||||
/// 1. The command and arguments that were executed, escaped with familiar `sh` syntax.
|
||||
/// 2. The exit status of the command or the signal that killed it.
|
||||
/// 3. If we were capturing the output of the command, rather than forwarding it to the user's
|
||||
/// stdout/stderr, the error message includes the command's stdout and stderr output.
|
||||
///
|
||||
/// Additionally, executing commands with these methods will log the command at debug-level,
|
||||
/// useful when gathering error reports.
|
||||
pub trait CommandExt {
|
||||
type Child;
|
||||
|
||||
/// Like [`Command::output`], but checks the exit status and provides nice error messages.
|
||||
///
|
||||
/// Returns an `Err` if the command failed to execute or returned a non-zero exit code.
|
||||
#[track_caller]
|
||||
fn output_checked(&mut self) -> eyre::Result<Output> {
|
||||
self.output_checked_with(|output: &Output| if output.status.success() { Ok(()) } else { Err(()) })
|
||||
}
|
||||
|
||||
/// Like [`output_checked`], but also decodes Stdout and Stderr as UTF-8.
|
||||
///
|
||||
/// Returns an `Err` if the command failed to execute, returned a non-zero exit code, or if the
|
||||
/// output contains invalid UTF-8.
|
||||
#[track_caller]
|
||||
fn output_checked_utf8(&mut self) -> eyre::Result<Utf8Output> {
|
||||
let output = self.output_checked()?;
|
||||
output.try_into()
|
||||
}
|
||||
|
||||
/// Like [`output_checked`] but a closure determines if the command failed instead of
|
||||
/// [`ExitStatus::success`].
|
||||
///
|
||||
/// Returns an `Err` if the command failed to execute or if `succeeded` returns an `Err`.
|
||||
/// (This lets the caller substitute their own notion of "success" instead of assuming
|
||||
/// non-zero exit codes indicate success.)
|
||||
#[track_caller]
|
||||
fn output_checked_with(&mut self, succeeded: impl Fn(&Output) -> Result<(), ()>) -> eyre::Result<Output>;
|
||||
|
||||
/// Like [`output_checked_with`], but also decodes Stdout and Stderr as UTF-8.
|
||||
///
|
||||
/// Returns an `Err` if the command failed to execute, if `succeeded` returns an `Err`, or if
|
||||
/// the output contains invalid UTF-8.
|
||||
// This function is currently unused, but is useful and makes sense with `output_checked_with`
|
||||
// and `output_checked_utf8` existing.
|
||||
#[allow(dead_code)]
|
||||
#[track_caller]
|
||||
fn output_checked_with_utf8(
|
||||
&mut self,
|
||||
succeeded: impl Fn(&Utf8Output) -> Result<(), ()>,
|
||||
) -> eyre::Result<Utf8Output> {
|
||||
// This decodes the Stdout and Stderr as UTF-8 twice...
|
||||
let output =
|
||||
self.output_checked_with(|output| output.try_into().map_err(|_| ()).and_then(|o| succeeded(&o)))?;
|
||||
output.try_into()
|
||||
}
|
||||
|
||||
/// Like [`Command::status`], but gives a nice error message if the status is unsuccessful
|
||||
/// rather than returning the [`ExitStatus`].
|
||||
///
|
||||
/// Returns `Ok` if the command executes successfully, returns `Err` if the command fails to
|
||||
/// execute or returns a non-zero exit code.
|
||||
#[track_caller]
|
||||
fn status_checked(&mut self) -> eyre::Result<()> {
|
||||
self.status_checked_with(|status| if status.success() { Ok(()) } else { Err(()) })
|
||||
}
|
||||
|
||||
/// Like [`status_checked`], but gives a nice error message if the status is unsuccessful
|
||||
/// rather than returning the [`ExitStatus`].
|
||||
///
|
||||
/// Returns `Ok` if the command executes successfully, returns `Err` if the command fails to
|
||||
/// execute or if `succeeded` returns an `Err`.
|
||||
/// (This lets the caller substitute their own notion of "success" instead of assuming
|
||||
/// non-zero exit codes indicate success.)
|
||||
#[track_caller]
|
||||
fn status_checked_with(&mut self, succeeded: impl Fn(ExitStatus) -> Result<(), ()>) -> eyre::Result<()>;
|
||||
|
||||
/// Like [`Command::spawn`], but gives a nice error message if the command fails to
|
||||
/// execute.
|
||||
#[track_caller]
|
||||
#[allow(dead_code)]
|
||||
fn spawn_checked(&mut self) -> eyre::Result<Self::Child>;
|
||||
}
|
||||
|
||||
impl CommandExt for Command {
|
||||
type Child = Child;
|
||||
|
||||
fn output_checked_with(&mut self, succeeded: impl Fn(&Output) -> Result<(), ()>) -> eyre::Result<Output> {
|
||||
let command = log(self);
|
||||
|
||||
// This is where we implement `output_checked`, which is what we prefer to use instead of
|
||||
// `output`, so we allow `Command::output` here.
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let output = self
|
||||
.output()
|
||||
.with_context(|| format!("Failed to execute `{command}`"))?;
|
||||
|
||||
if succeeded(&output).is_ok() {
|
||||
Ok(output)
|
||||
} else {
|
||||
let mut message = format!("Command failed: `{command}`");
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
let stdout_trimmed = stdout.trim();
|
||||
if !stdout_trimmed.is_empty() {
|
||||
message.push_str(&format!("\n\nStdout:\n{stdout_trimmed}"));
|
||||
}
|
||||
let stderr_trimmed = stderr.trim();
|
||||
if !stderr_trimmed.is_empty() {
|
||||
message.push_str(&format!("\n\nStderr:\n{stderr_trimmed}"));
|
||||
}
|
||||
|
||||
let (program, _) = get_program_and_args(self);
|
||||
let err = TopgradeError::ProcessFailedWithOutput(program, output.status, stderr.into_owned());
|
||||
|
||||
let ret = Err(err).with_context(|| message);
|
||||
debug!("Command failed: {ret:?}");
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
fn status_checked_with(&mut self, succeeded: impl Fn(ExitStatus) -> Result<(), ()>) -> eyre::Result<()> {
|
||||
let command = log(self);
|
||||
let message = format!("Failed to execute `{command}`");
|
||||
|
||||
// This is where we implement `status_checked`, which is what we prefer to use instead of
|
||||
// `status`, so we allow `Command::status` here.
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let status = self.status().with_context(|| message.clone())?;
|
||||
|
||||
if succeeded(status).is_ok() {
|
||||
Ok(())
|
||||
} else {
|
||||
let (program, _) = get_program_and_args(self);
|
||||
let err = TopgradeError::ProcessFailed(program, status);
|
||||
let ret = Err(err).with_context(|| format!("Command failed: `{command}`"));
|
||||
debug!("Command failed: {ret:?}");
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_checked(&mut self) -> eyre::Result<Self::Child> {
|
||||
let command = log(self);
|
||||
let message = format!("Failed to execute `{command}`");
|
||||
|
||||
// This is where we implement `spawn_checked`, which is what we prefer to use instead of
|
||||
// `spawn`, so we allow `Command::spawn` here.
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
{
|
||||
self.spawn().with_context(|| message.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_program_and_args(cmd: &Command) -> (String, String) {
|
||||
// We're not doing anything weird with commands that are invalid UTF-8 so this is fine.
|
||||
let program = cmd.get_program().to_string_lossy().into_owned();
|
||||
let args = shell_words::join(cmd.get_args().map(|arg| arg.to_string_lossy()));
|
||||
(program, args)
|
||||
}
|
||||
|
||||
fn format_program_and_args(cmd: &Command) -> String {
|
||||
let (program, args) = get_program_and_args(cmd);
|
||||
if args.is_empty() {
|
||||
program
|
||||
} else {
|
||||
format!("{program} {args}")
|
||||
}
|
||||
}
|
||||
|
||||
fn log(cmd: &Command) -> String {
|
||||
let command = format_program_and_args(cmd);
|
||||
debug!("Executing command `{command}`");
|
||||
command
|
||||
}
|
||||
1554
src/config.rs
1554
src/config.rs
File diff suppressed because it is too large
Load Diff
@@ -11,9 +11,9 @@ pub fn interrupted() -> bool {
|
||||
/// Clears the interrupted flag
|
||||
pub fn unset_interrupted() {
|
||||
debug_assert!(INTERRUPTED.load(Ordering::SeqCst));
|
||||
INTERRUPTED.store(false, Ordering::SeqCst)
|
||||
INTERRUPTED.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub fn set_interrupted() {
|
||||
INTERRUPTED.store(true, Ordering::SeqCst)
|
||||
INTERRUPTED.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
//! SIGINT handling in Unix systems.
|
||||
use crate::ctrlc::interrupted::set_interrupted;
|
||||
use nix::sys::signal;
|
||||
use nix::sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal, sigaction};
|
||||
|
||||
/// Handle SIGINT. Set the interruption flag.
|
||||
extern "C" fn handle_sigint(_: i32) {
|
||||
set_interrupted()
|
||||
set_interrupted();
|
||||
}
|
||||
|
||||
/// Set the necessary signal handlers.
|
||||
/// The function panics on failure.
|
||||
pub fn set_handler() {
|
||||
let sig_action = signal::SigAction::new(
|
||||
signal::SigHandler::Handler(handle_sigint),
|
||||
signal::SaFlags::empty(),
|
||||
signal::SigSet::empty(),
|
||||
);
|
||||
let sig_action = SigAction::new(SigHandler::Handler(handle_sigint), SaFlags::empty(), SigSet::empty());
|
||||
unsafe {
|
||||
signal::sigaction(signal::SIGINT, &sig_action).unwrap();
|
||||
sigaction(Signal::SIGINT, &sig_action).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
//! A stub for Ctrl + C handling.
|
||||
use crate::ctrlc::interrupted::set_interrupted;
|
||||
use winapi::shared::minwindef::{BOOL, DWORD, FALSE, TRUE};
|
||||
use winapi::um::consoleapi::SetConsoleCtrlHandler;
|
||||
use winapi::um::wincon::CTRL_C_EVENT;
|
||||
use tracing::error;
|
||||
use windows::Win32::System::Console::{CTRL_C_EVENT, SetConsoleCtrlHandler};
|
||||
use windows::core::BOOL;
|
||||
|
||||
extern "system" fn handler(ctrl_type: DWORD) -> BOOL {
|
||||
extern "system" fn handler(ctrl_type: u32) -> BOOL {
|
||||
match ctrl_type {
|
||||
CTRL_C_EVENT => {
|
||||
set_interrupted();
|
||||
TRUE
|
||||
true.into()
|
||||
}
|
||||
_ => FALSE,
|
||||
_ => false.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_handler() {
|
||||
if 0 == unsafe { SetConsoleCtrlHandler(Some(handler), TRUE) } {
|
||||
log::error!("Cannot set a control C handler")
|
||||
if let Err(e) = unsafe { SetConsoleCtrlHandler(Some(handler), true) } {
|
||||
error!("Cannot set a control C handler: {e}")
|
||||
}
|
||||
}
|
||||
|
||||
113
src/error.rs
113
src/error.rs
@@ -1,41 +1,118 @@
|
||||
use std::process::ExitStatus;
|
||||
use std::{fmt::Display, process::ExitStatus};
|
||||
|
||||
use rust_i18n::t;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::sudo::SudoKind;
|
||||
|
||||
#[derive(Error, Debug, PartialEq, Eq)]
|
||||
pub enum TopgradeError {
|
||||
#[error("{0}")]
|
||||
ProcessFailed(ExitStatus),
|
||||
ProcessFailed(String, ExitStatus),
|
||||
|
||||
#[error("{0}: {1}")]
|
||||
ProcessFailedWithOutput(ExitStatus, String),
|
||||
ProcessFailedWithOutput(String, ExitStatus, String),
|
||||
|
||||
#[error("Sudo is required for this step")]
|
||||
#[allow(dead_code)]
|
||||
SudoRequired,
|
||||
|
||||
#[error("Unknown Linux Distribution")]
|
||||
#[cfg(target_os = "linux")]
|
||||
UnknownLinuxDistribution,
|
||||
|
||||
#[error("Failed getting the system package manager")]
|
||||
#[cfg(target_os = "linux")]
|
||||
EmptyOSReleaseFile,
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
FailedGettingPackageManager,
|
||||
}
|
||||
|
||||
impl Display for TopgradeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
TopgradeError::ProcessFailed(process, exit_status) => {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
t!(
|
||||
"`{process}` failed: {exit_status}",
|
||||
process = process,
|
||||
exit_status = exit_status
|
||||
)
|
||||
)
|
||||
}
|
||||
TopgradeError::ProcessFailedWithOutput(process, exit_status, output) => {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
t!(
|
||||
"`{process}` failed: {exit_status} with {output}",
|
||||
process = process,
|
||||
exit_status = exit_status,
|
||||
output = output
|
||||
)
|
||||
)
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
TopgradeError::UnknownLinuxDistribution => write!(f, "{}", t!("Unknown Linux Distribution")),
|
||||
#[cfg(target_os = "linux")]
|
||||
TopgradeError::EmptyOSReleaseFile => {
|
||||
write!(f, "{}", t!("File \"/etc/os-release\" does not exist or is empty"))
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
TopgradeError::FailedGettingPackageManager => {
|
||||
write!(f, "{}", t!("Failed getting the system package manager"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("A step failed")]
|
||||
pub struct StepFailed;
|
||||
|
||||
impl Display for StepFailed {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", t!("A step failed"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub struct UnsupportedSudo<'a> {
|
||||
pub sudo_kind: SudoKind,
|
||||
pub option: &'a str,
|
||||
}
|
||||
|
||||
impl Display for UnsupportedSudo<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
t!(
|
||||
"{sudo_kind} does not support the {option} option",
|
||||
sudo_kind = self.sudo_kind,
|
||||
option = self.option
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub struct MissingSudo();
|
||||
|
||||
impl Display for MissingSudo {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", t!("Could not find sudo"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Dry running")]
|
||||
pub struct DryRun();
|
||||
|
||||
impl Display for DryRun {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", t!("Dry running"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{0}")]
|
||||
pub struct SkipStep(pub String);
|
||||
|
||||
#[cfg(all(windows, feature = "self-update"))]
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Topgrade Upgraded")]
|
||||
pub struct Upgraded(pub ExitStatus);
|
||||
impl Display for SkipStep {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +1,135 @@
|
||||
#![allow(dead_code)]
|
||||
use crate::executor::RunType;
|
||||
use crate::git::Git;
|
||||
use std::env::var;
|
||||
use std::ffi::OsStr;
|
||||
use std::process::Command;
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
|
||||
use clap::ValueEnum;
|
||||
use color_eyre::eyre::Result;
|
||||
use rust_i18n::t;
|
||||
use serde::Deserialize;
|
||||
use strum::EnumString;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::MissingSudo;
|
||||
use crate::executor::{DryCommand, Executor};
|
||||
use crate::powershell::Powershell;
|
||||
#[cfg(target_os = "linux")]
|
||||
use crate::steps::linux::Distribution;
|
||||
use crate::sudo::Sudo;
|
||||
use crate::utils::require_option;
|
||||
use crate::{config::Config, executor::Executor};
|
||||
use anyhow::Result;
|
||||
use directories::BaseDirs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// An enum telling whether Topgrade should perform dry runs or actually perform the steps.
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Default, EnumString, ValueEnum)]
|
||||
pub enum RunType {
|
||||
/// Executing commands will just print the command with its argument.
|
||||
Dry,
|
||||
|
||||
/// Executing commands will perform actual execution.
|
||||
#[default]
|
||||
Wet,
|
||||
|
||||
/// Executing commands will print the command and perform actual execution.
|
||||
Damp,
|
||||
}
|
||||
|
||||
impl RunType {
|
||||
/// Tells whether we're performing a dry run.
|
||||
pub fn dry(self) -> bool {
|
||||
match self {
|
||||
RunType::Dry => true,
|
||||
RunType::Wet => false,
|
||||
RunType::Damp => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExecutionContext<'a> {
|
||||
run_type: RunType,
|
||||
sudo: &'a Option<PathBuf>,
|
||||
git: &'a Git,
|
||||
sudo: Option<Sudo>,
|
||||
config: &'a Config,
|
||||
base_dirs: &'a BaseDirs,
|
||||
/// Name of a tmux session to execute commands in, if any.
|
||||
/// This is used in `./steps/remote/ssh.rs`, where we want to run `topgrade` in a new
|
||||
/// tmux window for each remote.
|
||||
tmux_session: Mutex<Option<String>>,
|
||||
/// True if topgrade is running under ssh.
|
||||
under_ssh: bool,
|
||||
#[cfg(target_os = "linux")]
|
||||
distribution: &'a Result<Distribution>,
|
||||
powershell: LazyLock<Option<Powershell>>,
|
||||
}
|
||||
|
||||
impl<'a> ExecutionContext<'a> {
|
||||
pub fn new(
|
||||
run_type: RunType,
|
||||
sudo: &'a Option<PathBuf>,
|
||||
git: &'a Git,
|
||||
sudo: Option<Sudo>,
|
||||
config: &'a Config,
|
||||
base_dirs: &'a BaseDirs,
|
||||
) -> ExecutionContext<'a> {
|
||||
ExecutionContext {
|
||||
#[cfg(target_os = "linux")] distribution: &'a Result<Distribution>,
|
||||
) -> Self {
|
||||
let under_ssh = var("SSH_CLIENT").is_ok() || var("SSH_TTY").is_ok();
|
||||
Self {
|
||||
run_type,
|
||||
sudo,
|
||||
git,
|
||||
config,
|
||||
base_dirs,
|
||||
tmux_session: Mutex::new(None),
|
||||
under_ssh,
|
||||
#[cfg(target_os = "linux")]
|
||||
distribution,
|
||||
powershell: LazyLock::new(Powershell::new),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute_elevated(&self, command: &Path, interactive: bool) -> Result<Executor> {
|
||||
let sudo = require_option(self.sudo.clone(), "Sudo is required for this operation".into())?;
|
||||
let mut cmd = self.run_type.execute(&sudo);
|
||||
|
||||
if sudo.ends_with("sudo") {
|
||||
cmd.arg("--preserve-env=DIFFPROG");
|
||||
/// Create an instance of `Executor` that should run `program`.
|
||||
pub fn execute<S: AsRef<OsStr>>(&self, program: S) -> Executor {
|
||||
match self.run_type {
|
||||
RunType::Dry => Executor::Dry(DryCommand::new(program)),
|
||||
RunType::Wet => Executor::Wet(Command::new(program)),
|
||||
RunType::Damp => Executor::Damp(Command::new(program)),
|
||||
}
|
||||
|
||||
if interactive {
|
||||
cmd.arg("-i");
|
||||
}
|
||||
|
||||
cmd.arg(command);
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
pub fn run_type(&self) -> RunType {
|
||||
self.run_type
|
||||
}
|
||||
|
||||
pub fn git(&self) -> &Git {
|
||||
self.git
|
||||
pub fn sudo(&self) -> &Option<Sudo> {
|
||||
&self.sudo
|
||||
}
|
||||
|
||||
pub fn sudo(&self) -> &Option<PathBuf> {
|
||||
self.sudo
|
||||
pub fn require_sudo(&self) -> Result<&Sudo> {
|
||||
if let Some(value) = self.sudo() {
|
||||
Ok(value)
|
||||
} else {
|
||||
Err(MissingSudo().into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config(&self) -> &Config {
|
||||
self.config
|
||||
}
|
||||
|
||||
pub fn base_dirs(&self) -> &BaseDirs {
|
||||
self.base_dirs
|
||||
pub fn under_ssh(&self) -> bool {
|
||||
self.under_ssh
|
||||
}
|
||||
|
||||
pub fn set_tmux_session(&self, session_name: String) {
|
||||
self.tmux_session.lock().unwrap().replace(session_name);
|
||||
}
|
||||
|
||||
pub fn get_tmux_session(&self) -> Option<String> {
|
||||
self.tmux_session.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn distribution(&self) -> &Result<Distribution> {
|
||||
self.distribution
|
||||
}
|
||||
|
||||
pub fn powershell(&self) -> &Option<Powershell> {
|
||||
&self.powershell
|
||||
}
|
||||
|
||||
pub fn require_powershell(&self) -> Result<&Powershell> {
|
||||
require_option(self.powershell.as_ref(), t!("Powershell is not installed").to_string())
|
||||
}
|
||||
}
|
||||
|
||||
295
src/executor.rs
295
src/executor.rs
@@ -1,65 +1,41 @@
|
||||
//! Utilities for command execution
|
||||
use crate::error::{DryRun, TopgradeError};
|
||||
use crate::utils::{Check, CheckWithCodes};
|
||||
use anyhow::Result;
|
||||
use log::{debug, trace};
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fmt::Debug;
|
||||
use std::iter;
|
||||
use std::path::Path;
|
||||
use std::process::{Child, Command, ExitStatus};
|
||||
use std::process::{Child, Command, ExitStatus, Output};
|
||||
|
||||
/// An enum telling whether Topgrade should perform dry runs or actually perform the steps.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum RunType {
|
||||
/// Executing commands will just print the command with its argument.
|
||||
Dry,
|
||||
use color_eyre::eyre::Result;
|
||||
use rust_i18n::t;
|
||||
use tracing::{Level, debug, enabled};
|
||||
|
||||
/// Executing commands will perform actual execution.
|
||||
Wet,
|
||||
}
|
||||
|
||||
impl RunType {
|
||||
/// Create a new instance from a boolean telling whether to dry run.
|
||||
pub fn new(dry_run: bool) -> Self {
|
||||
if dry_run {
|
||||
RunType::Dry
|
||||
} else {
|
||||
RunType::Wet
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an instance of `Executor` that should run `program`.
|
||||
pub fn execute<S: AsRef<OsStr>>(self, program: S) -> Executor {
|
||||
match self {
|
||||
RunType::Dry => Executor::Dry(DryCommand {
|
||||
program: program.as_ref().into(),
|
||||
..Default::default()
|
||||
}),
|
||||
RunType::Wet => Executor::Wet(Command::new(program)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tells whether we're performing a dry run.
|
||||
pub fn dry(self) -> bool {
|
||||
match self {
|
||||
RunType::Dry => true,
|
||||
RunType::Wet => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
use crate::command::CommandExt;
|
||||
use crate::error::DryRun;
|
||||
|
||||
/// An enum providing a similar interface to `std::process::Command`.
|
||||
/// If the enum is set to `Wet`, execution will be performed with `std::process::Command`.
|
||||
/// If the enum is set to `Dry`, execution will just print the command with its arguments.
|
||||
pub enum Executor {
|
||||
Wet(Command),
|
||||
Damp(Command),
|
||||
Dry(DryCommand),
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
/// Get the name of the program being run.
|
||||
///
|
||||
/// Will give weird results for non-UTF-8 programs; see `to_string_lossy()`.
|
||||
pub fn get_program(&self) -> String {
|
||||
match self {
|
||||
Executor::Wet(c) | Executor::Damp(c) => c.get_program().to_string_lossy().into_owned(),
|
||||
Executor::Dry(c) => c.program.to_string_lossy().into_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
/// See `std::process::Command::arg`
|
||||
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Executor {
|
||||
match self {
|
||||
Executor::Wet(c) => {
|
||||
Executor::Wet(c) | Executor::Damp(c) => {
|
||||
c.arg(arg);
|
||||
}
|
||||
Executor::Dry(c) => {
|
||||
@@ -77,7 +53,7 @@ impl Executor {
|
||||
S: AsRef<OsStr>,
|
||||
{
|
||||
match self {
|
||||
Executor::Wet(c) => {
|
||||
Executor::Wet(c) | Executor::Damp(c) => {
|
||||
c.args(args);
|
||||
}
|
||||
Executor::Dry(c) => {
|
||||
@@ -92,7 +68,7 @@ impl Executor {
|
||||
/// See `std::process::Command::current_dir`
|
||||
pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Executor {
|
||||
match self {
|
||||
Executor::Wet(c) => {
|
||||
Executor::Wet(c) | Executor::Damp(c) => {
|
||||
c.current_dir(dir);
|
||||
}
|
||||
Executor::Dry(c) => c.directory = Some(dir.as_ref().into()),
|
||||
@@ -108,7 +84,7 @@ impl Executor {
|
||||
K: AsRef<OsStr>,
|
||||
{
|
||||
match self {
|
||||
Executor::Wet(c) => {
|
||||
Executor::Wet(c) | Executor::Damp(c) => {
|
||||
c.env_remove(key);
|
||||
}
|
||||
Executor::Dry(_) => (),
|
||||
@@ -125,7 +101,7 @@ impl Executor {
|
||||
V: AsRef<OsStr>,
|
||||
{
|
||||
match self {
|
||||
Executor::Wet(c) => {
|
||||
Executor::Wet(c) | Executor::Damp(c) => {
|
||||
c.env(key, val);
|
||||
}
|
||||
Executor::Dry(_) => (),
|
||||
@@ -136,15 +112,16 @@ impl Executor {
|
||||
|
||||
/// See `std::process::Command::spawn`
|
||||
pub fn spawn(&mut self) -> Result<ExecutorChild> {
|
||||
self.log_command();
|
||||
let result = match self {
|
||||
Executor::Wet(c) => {
|
||||
Executor::Wet(c) | Executor::Damp(c) => {
|
||||
debug!("Running {:?}", c);
|
||||
// We should use `spawn()` here rather than `spawn_checked()` since
|
||||
// their semantics and behaviors are different.
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
c.spawn().map(ExecutorChild::Wet)?
|
||||
}
|
||||
Executor::Dry(c) => {
|
||||
c.dry_run();
|
||||
ExecutorChild::Dry
|
||||
}
|
||||
Executor::Dry(_) => ExecutorChild::Dry,
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
@@ -152,37 +129,64 @@ impl Executor {
|
||||
|
||||
/// See `std::process::Command::output`
|
||||
pub fn output(&mut self) -> Result<ExecutorOutput> {
|
||||
self.log_command();
|
||||
match self {
|
||||
Executor::Wet(c) => Ok(ExecutorOutput::Wet(c.output()?)),
|
||||
Executor::Dry(c) => {
|
||||
c.dry_run();
|
||||
Ok(ExecutorOutput::Dry)
|
||||
Executor::Wet(c) | Executor::Damp(c) => {
|
||||
// We should use `output()` here rather than `output_checked()` since
|
||||
// their semantics and behaviors are different.
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
Ok(ExecutorOutput::Wet(c.output()?))
|
||||
}
|
||||
Executor::Dry(_) => Ok(ExecutorOutput::Dry),
|
||||
}
|
||||
}
|
||||
|
||||
/// A convinence method for `spawn().wait().check()`.
|
||||
/// Returns an error if something went wrong during the execution or if the
|
||||
/// process exited with failure.
|
||||
pub fn check_run(&mut self) -> Result<()> {
|
||||
self.spawn()?.wait()?.check()
|
||||
}
|
||||
|
||||
/// An extension of `check_run` that allows you to set a sequence of codes
|
||||
/// An extension of `status_checked` that allows you to set a sequence of codes
|
||||
/// that can indicate success of a script
|
||||
#[allow(dead_code)]
|
||||
pub fn check_run_with_codes(&mut self, codes: &[i32]) -> Result<()> {
|
||||
self.spawn()?.wait()?.check_with_codes(codes)
|
||||
pub fn status_checked_with_codes(&mut self, codes: &[i32]) -> Result<()> {
|
||||
self.log_command();
|
||||
match self {
|
||||
Executor::Wet(c) | Executor::Damp(c) => c.status_checked_with(|status| {
|
||||
if status.success() || status.code().as_ref().is_some_and(|c| codes.contains(c)) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}),
|
||||
Executor::Dry(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn log_command(&self) {
|
||||
match self {
|
||||
Executor::Wet(_) => (),
|
||||
Executor::Damp(c) => {
|
||||
log_command(
|
||||
"Executing: {program_name} {arguments}",
|
||||
c.get_program(),
|
||||
c.get_args(),
|
||||
c.get_envs(),
|
||||
c.get_current_dir(),
|
||||
);
|
||||
}
|
||||
Executor::Dry(c) => log_command(
|
||||
"Dry running: {program_name} {arguments}",
|
||||
&c.program,
|
||||
&c.args,
|
||||
iter::empty(),
|
||||
c.directory.as_ref(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ExecutorOutput {
|
||||
Wet(std::process::Output),
|
||||
Wet(Output),
|
||||
Dry,
|
||||
}
|
||||
|
||||
/// A struct represending a command. Trying to execute it will just print its arguments.
|
||||
#[derive(Default)]
|
||||
/// A struct representing a command. Trying to execute it will just print its arguments.
|
||||
pub struct DryCommand {
|
||||
program: OsString,
|
||||
args: Vec<OsString>,
|
||||
@@ -190,102 +194,85 @@ pub struct DryCommand {
|
||||
}
|
||||
|
||||
impl DryCommand {
|
||||
fn dry_run(&self) {
|
||||
print!(
|
||||
"Dry running: {} {}",
|
||||
self.program.to_string_lossy(),
|
||||
shell_words::join(
|
||||
self.args
|
||||
.iter()
|
||||
.map(|a| String::from(a.to_string_lossy()))
|
||||
.collect::<Vec<String>>()
|
||||
)
|
||||
);
|
||||
match &self.directory {
|
||||
Some(dir) => println!(" in {}", dir.to_string_lossy()),
|
||||
None => println!(),
|
||||
};
|
||||
pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
|
||||
Self {
|
||||
program: program.as_ref().to_os_string(),
|
||||
args: Vec::new(),
|
||||
directory: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The Result of spawn. Contains an actual `std::process::Child` if executed by a wet command.
|
||||
pub enum ExecutorChild {
|
||||
// Both RunType::Wet and RunType::Damp use this variant
|
||||
#[allow(unused)] // this type has not been used
|
||||
Wet(Child),
|
||||
Dry,
|
||||
}
|
||||
|
||||
impl ExecutorChild {
|
||||
/// See `std::process::Child::wait`
|
||||
pub fn wait(&mut self) -> Result<ExecutorExitStatus> {
|
||||
let result = match self {
|
||||
ExecutorChild::Wet(c) => c.wait().map(ExecutorExitStatus::Wet)?,
|
||||
ExecutorChild::Dry => ExecutorExitStatus::Dry,
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// The Result of wait. Contains an actual `std::process::ExitStatus` if executed by a wet command.
|
||||
pub enum ExecutorExitStatus {
|
||||
Wet(ExitStatus),
|
||||
Dry,
|
||||
}
|
||||
|
||||
impl CheckWithCodes for ExecutorExitStatus {
|
||||
fn check_with_codes(self, codes: &[i32]) -> Result<()> {
|
||||
match self {
|
||||
ExecutorExitStatus::Wet(e) => e.check_with_codes(codes),
|
||||
ExecutorExitStatus::Dry => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension methods for `std::process::Command`
|
||||
pub trait CommandExt {
|
||||
/// Run the command, wait for it to complete, check the return code and decode the output as UTF-8.
|
||||
fn check_output(&mut self) -> Result<String>;
|
||||
fn string_output(&mut self) -> Result<String>;
|
||||
}
|
||||
|
||||
impl CommandExt for Command {
|
||||
fn check_output(&mut self) -> Result<String> {
|
||||
let output = self.output()?;
|
||||
trace!("Output of {:?}: {:?}", self, output);
|
||||
let status = output.status;
|
||||
if !status.success() {
|
||||
let stderr = String::from_utf8(output.stderr).unwrap_or_default();
|
||||
return Err(TopgradeError::ProcessFailedWithOutput(status, stderr).into());
|
||||
}
|
||||
Ok(String::from_utf8(output.stdout)?)
|
||||
}
|
||||
|
||||
fn string_output(&mut self) -> Result<String> {
|
||||
let output = self.output()?;
|
||||
trace!("Output of {:?}: {:?}", self, output);
|
||||
Ok(String::from_utf8(output.stdout)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandExt for Executor {
|
||||
fn check_output(&mut self) -> Result<String> {
|
||||
let output = match self.output()? {
|
||||
ExecutorOutput::Wet(output) => output,
|
||||
ExecutorOutput::Dry => return Err(DryRun().into()),
|
||||
};
|
||||
let status = output.status;
|
||||
if !status.success() {
|
||||
let stderr = String::from_utf8(output.stderr).unwrap_or_default();
|
||||
return Err(TopgradeError::ProcessFailedWithOutput(status, stderr).into());
|
||||
type Child = ExecutorChild;
|
||||
|
||||
// TODO: It might be nice to make `output_checked_with` return something that has a
|
||||
// variant for wet/dry runs.
|
||||
|
||||
fn output_checked_with(&mut self, succeeded: impl Fn(&Output) -> Result<(), ()>) -> Result<Output> {
|
||||
self.log_command();
|
||||
match self {
|
||||
Executor::Wet(c) | Executor::Damp(c) => c.output_checked_with(succeeded),
|
||||
Executor::Dry(_) => Err(DryRun().into()),
|
||||
}
|
||||
Ok(String::from_utf8(output.stdout)?)
|
||||
}
|
||||
|
||||
fn string_output(&mut self) -> Result<String> {
|
||||
let output = match self.output()? {
|
||||
ExecutorOutput::Wet(output) => output,
|
||||
ExecutorOutput::Dry => return Err(DryRun().into()),
|
||||
};
|
||||
Ok(String::from_utf8(output.stdout)?)
|
||||
fn status_checked_with(&mut self, succeeded: impl Fn(ExitStatus) -> Result<(), ()>) -> Result<()> {
|
||||
self.log_command();
|
||||
match self {
|
||||
Executor::Wet(c) | Executor::Damp(c) => c.status_checked_with(succeeded),
|
||||
Executor::Dry(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_checked(&mut self) -> Result<Self::Child> {
|
||||
self.spawn()
|
||||
}
|
||||
}
|
||||
|
||||
fn log_command<
|
||||
'a,
|
||||
I: ExactSizeIterator<Item = (&'a (impl Debug + 'a + ?Sized), Option<&'a (impl Debug + 'a + ?Sized)>)>,
|
||||
>(
|
||||
prefix: &str,
|
||||
exec: &OsStr,
|
||||
args: impl IntoIterator<Item = &'a (impl AsRef<OsStr> + ?Sized + 'a)>,
|
||||
env: impl IntoIterator<Item = (&'a OsStr, Option<&'a OsStr>), IntoIter = I>,
|
||||
dir: Option<&'a (impl AsRef<Path> + ?Sized)>,
|
||||
) {
|
||||
println!(
|
||||
"{}",
|
||||
t!(
|
||||
prefix,
|
||||
program_name = exec.to_string_lossy(),
|
||||
arguments = shell_words::join(args.into_iter().map(|s| s.as_ref().to_string_lossy()))
|
||||
)
|
||||
);
|
||||
|
||||
let env_iter = env.into_iter();
|
||||
if env_iter.len() != 0 && enabled!(Level::DEBUG) {
|
||||
println!(
|
||||
" {}",
|
||||
t!(
|
||||
"with env: {env}",
|
||||
env = env_iter
|
||||
.filter(|(_, val)| val.is_some())
|
||||
.map(|(key, val)| format!("{:?}={:?}", key, val.unwrap()))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if let Some(d) = dir {
|
||||
println!(" {}", t!("in {directory}", directory = d.as_ref().display()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
//pub mod steps;
|
||||
//pub mod utils;
|
||||
626
src/main.rs
626
src/main.rs
@@ -1,115 +1,194 @@
|
||||
#![allow(clippy::cognitive_complexity)]
|
||||
|
||||
use std::env;
|
||||
use std::env::home_dir;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use clap::{crate_version, Parser};
|
||||
use crate::breaking_changes::{first_run_of_major_release, print_breaking_changes, should_skip, write_keep_file};
|
||||
use clap::CommandFactory;
|
||||
use clap::{Parser, crate_version};
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::Result;
|
||||
use console::Key;
|
||||
use log::debug;
|
||||
use log::LevelFilter;
|
||||
use pretty_env_logger::formatted_timed_builder;
|
||||
#[cfg(windows)]
|
||||
use etcetera::base_strategy::Windows;
|
||||
#[cfg(unix)]
|
||||
use etcetera::base_strategy::Xdg;
|
||||
use rust_i18n::{i18n, t};
|
||||
use std::sync::LazyLock;
|
||||
use tracing::debug;
|
||||
|
||||
use self::config::{CommandLineArgs, Config, Step};
|
||||
use self::config::{CommandLineArgs, Config};
|
||||
use self::error::StepFailed;
|
||||
#[cfg(all(windows, feature = "self-update"))]
|
||||
use self::error::Upgraded;
|
||||
use self::runner::StepResult;
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use self::steps::{remote::*, *};
|
||||
use self::sudo::{Sudo, SudoCreateError, SudoKind};
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use self::terminal::*;
|
||||
use self::utils::{install_color_eyre, install_tracing, is_elevated, update_tracing};
|
||||
|
||||
mod breaking_changes;
|
||||
mod command;
|
||||
mod config;
|
||||
mod ctrlc;
|
||||
mod error;
|
||||
mod execution_context;
|
||||
mod executor;
|
||||
mod report;
|
||||
mod runner;
|
||||
#[cfg(windows)]
|
||||
mod self_renamer;
|
||||
#[cfg(feature = "self-update")]
|
||||
mod self_update;
|
||||
mod step;
|
||||
mod steps;
|
||||
mod sudo;
|
||||
mod terminal;
|
||||
mod utils;
|
||||
|
||||
pub(crate) static HOME_DIR: LazyLock<PathBuf> = LazyLock::new(|| home_dir().expect("No home directory"));
|
||||
#[cfg(unix)]
|
||||
pub(crate) static XDG_DIRS: LazyLock<Xdg> = LazyLock::new(|| Xdg::new().expect("No home directory"));
|
||||
|
||||
#[cfg(windows)]
|
||||
pub(crate) static WINDOWS_DIRS: LazyLock<Windows> = LazyLock::new(|| Windows::new().expect("No home directory"));
|
||||
|
||||
// Init and load the i18n files
|
||||
i18n!("locales", fallback = "en");
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn run() -> Result<()> {
|
||||
install_color_eyre()?;
|
||||
ctrlc::set_handler();
|
||||
|
||||
let base_dirs = directories::BaseDirs::new().ok_or_else(|| anyhow!("No base directories"))?;
|
||||
|
||||
let opt = CommandLineArgs::parse();
|
||||
// Set up the logger with the filter directives from:
|
||||
// 1. CLI option `--log-filter`
|
||||
// 2. `debug` if the `--verbose` option is present
|
||||
// We do this because we need our logger to work while loading the
|
||||
// configuration file.
|
||||
//
|
||||
// When the configuration file is loaded, update the logger with the full
|
||||
// filter directives.
|
||||
//
|
||||
// For more info, see the comments in `CommandLineArgs::tracing_filter_directives()`
|
||||
// and `Config::tracing_filter_directives()`.
|
||||
let reload_handle = install_tracing(&opt.tracing_filter_directives())?;
|
||||
|
||||
// Get current system locale and set it as the default locale
|
||||
let system_locale = sys_locale::get_locale().unwrap_or("en".to_string());
|
||||
rust_i18n::set_locale(&system_locale);
|
||||
debug!("Current system locale is {system_locale}");
|
||||
|
||||
if let Some(shell) = opt.gen_completion {
|
||||
let cmd = &mut CommandLineArgs::command();
|
||||
clap_complete::generate(shell, cmd, clap::crate_name!(), &mut io::stdout());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if opt.gen_manpage {
|
||||
let man = clap_mangen::Man::new(CommandLineArgs::command());
|
||||
man.render(&mut io::stdout())?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for env in opt.env_variables() {
|
||||
let mut splitted = env.split('=');
|
||||
let var = splitted.next().unwrap();
|
||||
let value = splitted.next().unwrap();
|
||||
env::set_var(var, value);
|
||||
let mut parts = env.split('=');
|
||||
let var = parts.next().unwrap();
|
||||
let value = parts.next().unwrap();
|
||||
unsafe { env::set_var(var, value) };
|
||||
}
|
||||
|
||||
let mut builder = formatted_timed_builder();
|
||||
|
||||
if opt.verbose {
|
||||
builder.filter(Some("topgrade"), LevelFilter::Trace);
|
||||
}
|
||||
|
||||
builder.init();
|
||||
|
||||
if opt.edit_config() {
|
||||
Config::edit(&base_dirs)?;
|
||||
Config::edit()?;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if opt.show_config_reference() {
|
||||
print!("{}", crate::config::EXAMPLE_CONFIG);
|
||||
print!("{}", config::EXAMPLE_CONFIG);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let config = Config::load(&base_dirs, opt)?;
|
||||
terminal::set_title(config.set_title());
|
||||
terminal::display_time(config.display_time());
|
||||
terminal::set_desktop_notifications(config.notify_each_step());
|
||||
let config = Config::load(opt)?;
|
||||
// Update the logger with the full filter directives.
|
||||
update_tracing(&reload_handle, &config.tracing_filter_directives())?;
|
||||
set_title(config.set_title());
|
||||
display_time(config.display_time());
|
||||
set_desktop_notifications(config.notify_each_step());
|
||||
|
||||
debug!("Version: {}", crate_version!());
|
||||
debug!("OS: {}", env!("TARGET"));
|
||||
debug!("{:?}", std::env::args());
|
||||
debug!("Binary path: {:?}", std::env::current_exe());
|
||||
debug!("Self Update: {:?}", cfg!(feature = "self-update"));
|
||||
debug!("{:?}", env::args());
|
||||
debug!("Binary path: {:?}", env::current_exe());
|
||||
debug!("self-update Feature Enabled: {:?}", cfg!(feature = "self-update"));
|
||||
debug!("Configuration: {:?}", config);
|
||||
|
||||
if config.run_in_tmux() && env::var("TOPGRADE_INSIDE_TMUX").is_err() {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
tmux::run_in_tmux(config.tmux_arguments()?);
|
||||
tmux::run_in_tmux(config.tmux_config()?)?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let git = git::Git::new();
|
||||
let mut git_repos = git::Repositories::new(&git);
|
||||
let elevated = is_elevated();
|
||||
|
||||
let sudo = utils::sudo();
|
||||
let run_type = executor::RunType::new(config.dry_run());
|
||||
#[cfg(unix)]
|
||||
if !config.allow_root() && elevated {
|
||||
print_warning(t!(
|
||||
"Topgrade should not be run as root, it will run commands with sudo or equivalent where needed."
|
||||
));
|
||||
if !prompt_yesno(&t!("Continue?"))? {
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
let ctx = execution_context::ExecutionContext::new(run_type, &sudo, &git, &config, &base_dirs);
|
||||
let sudo = match config.sudo_command() {
|
||||
Some(kind) => Sudo::new(kind),
|
||||
None if elevated => Sudo::new(SudoKind::Null),
|
||||
None => Sudo::detect(),
|
||||
};
|
||||
debug!("Sudo: {:?}", sudo);
|
||||
|
||||
let (sudo, sudo_err) = match sudo {
|
||||
Ok(sudo) => (Some(sudo), None),
|
||||
Err(e) => (None, Some(e)),
|
||||
};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
let distribution = linux::Distribution::detect();
|
||||
|
||||
let run_type = config.run_type();
|
||||
let ctx = execution_context::ExecutionContext::new(
|
||||
run_type,
|
||||
sudo,
|
||||
&config,
|
||||
#[cfg(target_os = "linux")]
|
||||
&distribution,
|
||||
);
|
||||
let mut runner = runner::Runner::new(&ctx);
|
||||
|
||||
#[cfg(feature = "self-update")]
|
||||
{
|
||||
if !run_type.dry() && env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() {
|
||||
let result = self_update::self_update();
|
||||
// If
|
||||
//
|
||||
// 1. the breaking changes notification shouldn't be skipped
|
||||
// 2. this is the first execution of a major release
|
||||
//
|
||||
// inform user of breaking changes
|
||||
if !should_skip() && first_run_of_major_release()? {
|
||||
print_breaking_changes();
|
||||
|
||||
if let Err(e) = &result {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if e.downcast_ref::<Upgraded>().is_some() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
print_warning(format!("Self update error: {}", e));
|
||||
}
|
||||
if prompt_yesno(&t!("Continue?"))? {
|
||||
write_keep_file()?;
|
||||
} else {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
step::Step::SelfUpdate.run(&mut runner, &ctx)?;
|
||||
|
||||
#[cfg(windows)]
|
||||
let _self_rename = if config.self_rename() {
|
||||
Some(crate::self_renamer::SelfRenamer::create()?)
|
||||
@@ -117,366 +196,113 @@ fn run() -> Result<()> {
|
||||
None
|
||||
};
|
||||
|
||||
if config.pre_sudo() {
|
||||
if let Some(sudo) = ctx.sudo() {
|
||||
sudo.elevate(&ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(commands) = config.pre_commands() {
|
||||
for (name, command) in commands {
|
||||
generic::run_custom_command(name, command, &ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
let powershell = powershell::Powershell::new();
|
||||
let should_run_powershell = powershell.profile().is_some() && config.should_run(Step::Powershell);
|
||||
|
||||
#[cfg(windows)]
|
||||
runner.execute(Step::Wsl, "WSL", || windows::run_wsl_topgrade(&ctx))?;
|
||||
|
||||
if let Some(topgrades) = config.remote_topgrades() {
|
||||
for remote_topgrade in topgrades.iter().filter(|t| config.should_execute_remote(t)) {
|
||||
runner.execute(Step::Remotes, format!("Remote ({})", remote_topgrade), || {
|
||||
remote::ssh::ssh_step(&ctx, remote_topgrade)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
let distribution = linux::Distribution::detect();
|
||||
|
||||
#[cfg(target_os = r#"linux"#)]
|
||||
{
|
||||
match &distribution {
|
||||
Ok(distribution) => {
|
||||
runner.execute(Step::System, "System update", || distribution.upgrade(&ctx))?;
|
||||
for step in step::default_steps() {
|
||||
match step.run(&mut runner, &ctx) {
|
||||
Ok(()) => (),
|
||||
Err(error)
|
||||
if error
|
||||
.downcast_ref::<io::Error>()
|
||||
.is_some_and(|e| e.kind() == io::ErrorKind::Interrupted) =>
|
||||
{
|
||||
println!();
|
||||
debug!("Interrupted (possibly with 'q' during retry prompt). Printing summary.");
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error detecting current distribution: {}", e);
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
let mut failed = false;
|
||||
|
||||
let report = runner.report();
|
||||
if !report.is_empty() {
|
||||
print_separator(t!("Summary"));
|
||||
|
||||
let mut skipped_missing_sudo = false;
|
||||
|
||||
for (key, result) in report {
|
||||
if !failed && result.failed() {
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
runner.execute(Step::ConfigUpdate, "config-update", || linux::run_config_update(&ctx))?;
|
||||
|
||||
runner.execute(Step::BrewFormula, "Brew", || {
|
||||
unix::run_brew_formula(&ctx, unix::BrewVariant::Path)
|
||||
})?;
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
runner.execute(Step::Chocolatey, "Chocolatey", || windows::run_chocolatey(&ctx))?;
|
||||
runner.execute(Step::Scoop, "Scoop", || windows::run_scoop(config.cleanup(), run_type))?;
|
||||
runner.execute(Step::Winget, "Winget", || windows::run_winget(&ctx))?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
runner.execute(Step::BrewFormula, "Brew (ARM)", || {
|
||||
unix::run_brew_formula(&ctx, unix::BrewVariant::MacArm)
|
||||
})?;
|
||||
runner.execute(Step::BrewFormula, "Brew (Intel)", || {
|
||||
unix::run_brew_formula(&ctx, unix::BrewVariant::MacIntel)
|
||||
})?;
|
||||
runner.execute(Step::BrewFormula, "Brew", || {
|
||||
unix::run_brew_formula(&ctx, unix::BrewVariant::Path)
|
||||
})?;
|
||||
runner.execute(Step::BrewCask, "Brew Cask (ARM)", || {
|
||||
unix::run_brew_cask(&ctx, unix::BrewVariant::MacArm)
|
||||
})?;
|
||||
runner.execute(Step::BrewCask, "Brew Cask (Intel)", || {
|
||||
unix::run_brew_cask(&ctx, unix::BrewVariant::MacIntel)
|
||||
})?;
|
||||
runner.execute(Step::BrewCask, "Brew Cask", || {
|
||||
unix::run_brew_cask(&ctx, unix::BrewVariant::Path)
|
||||
})?;
|
||||
runner.execute(Step::Macports, "MacPorts", || macos::run_macports(&ctx))?;
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
runner.execute(Step::Yadm, "yadm", || unix::run_yadm(&ctx))?;
|
||||
runner.execute(Step::Nix, "nix", || unix::run_nix(&ctx))?;
|
||||
runner.execute(Step::Guix, "guix", || unix::run_guix(&ctx))?;
|
||||
|
||||
runner.execute(Step::HomeManager, "home-manager", || unix::run_home_manager(run_type))?;
|
||||
runner.execute(Step::Asdf, "asdf", || unix::run_asdf(run_type))?;
|
||||
runner.execute(Step::Pkgin, "pkgin", || unix::run_pkgin(&ctx))?;
|
||||
runner.execute(Step::Bun, "bun", || unix::run_bun(&ctx))?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "dragonfly")]
|
||||
runner.execute(Step::Pkg, "DragonFly BSD Packages", || {
|
||||
dragonfly::upgrade_packages(sudo.as_ref(), run_type)
|
||||
})?;
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
runner.execute(Step::Pkg, "FreeBSD Packages", || {
|
||||
freebsd::upgrade_packages(sudo.as_ref(), run_type)
|
||||
})?;
|
||||
|
||||
#[cfg(target_os = "openbsd")]
|
||||
runner.execute(Step::Pkg, "OpenBSD Packages", || {
|
||||
openbsd::upgrade_packages(sudo.as_ref(), run_type)
|
||||
})?;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
runner.execute(Step::Pkg, "Termux Packages", || android::upgrade_packages(&ctx))?;
|
||||
|
||||
let emacs = emacs::Emacs::new(&base_dirs);
|
||||
if config.use_predefined_git_repos() {
|
||||
if config.should_run(Step::Emacs) {
|
||||
if !emacs.is_doom() {
|
||||
if let Some(directory) = emacs.directory() {
|
||||
git_repos.insert_if_repo(directory);
|
||||
}
|
||||
if let StepResult::SkippedMissingSudo = result {
|
||||
skipped_missing_sudo = true;
|
||||
}
|
||||
git_repos.insert_if_repo(base_dirs.home_dir().join(".doom.d"));
|
||||
}
|
||||
|
||||
if config.should_run(Step::Vim) {
|
||||
git_repos.insert_if_repo(base_dirs.home_dir().join(".vim"));
|
||||
git_repos.insert_if_repo(base_dirs.home_dir().join(".config/nvim"));
|
||||
}
|
||||
|
||||
git_repos.insert_if_repo(base_dirs.home_dir().join(".ideavimrc"));
|
||||
git_repos.insert_if_repo(base_dirs.home_dir().join(".intellimacs"));
|
||||
|
||||
if config.should_run(Step::Rcm) {
|
||||
git_repos.insert_if_repo(base_dirs.home_dir().join(".dotfiles"));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
git_repos.insert_if_repo(zsh::zshrc(&base_dirs));
|
||||
if config.should_run(Step::Tmux) {
|
||||
git_repos.insert_if_repo(base_dirs.home_dir().join(".tmux"));
|
||||
}
|
||||
git_repos.insert_if_repo(base_dirs.home_dir().join(".config/fish"));
|
||||
git_repos.insert_if_repo(base_dirs.config_dir().join("openbox"));
|
||||
git_repos.insert_if_repo(base_dirs.config_dir().join("bspwm"));
|
||||
git_repos.insert_if_repo(base_dirs.config_dir().join("i3"));
|
||||
git_repos.insert_if_repo(base_dirs.config_dir().join("sway"));
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
git_repos.insert_if_repo(
|
||||
base_dirs
|
||||
.data_local_dir()
|
||||
.join("Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState"),
|
||||
);
|
||||
|
||||
#[cfg(windows)]
|
||||
windows::insert_startup_scripts(&ctx, &mut git_repos).ok();
|
||||
|
||||
if let Some(profile) = powershell.profile() {
|
||||
git_repos.insert_if_repo(profile);
|
||||
}
|
||||
}
|
||||
|
||||
if config.should_run(Step::GitRepos) {
|
||||
if let Some(custom_git_repos) = config.git_repos() {
|
||||
for git_repo in custom_git_repos {
|
||||
git_repos.glob_insert(git_repo);
|
||||
}
|
||||
}
|
||||
runner.execute(Step::GitRepos, "Git repositories", || {
|
||||
git.multi_pull_step(&git_repos, &ctx)
|
||||
})?;
|
||||
}
|
||||
|
||||
if should_run_powershell {
|
||||
runner.execute(Step::Powershell, "Powershell Modules Update", || {
|
||||
powershell.update_modules(&ctx)
|
||||
})?;
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
runner.execute(Step::Shell, "zr", || zsh::run_zr(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Shell, "antibody", || zsh::run_antibody(run_type))?;
|
||||
runner.execute(Step::Shell, "antigen", || zsh::run_antigen(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Shell, "zgenom", || zsh::run_zgenom(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Shell, "zplug", || zsh::run_zplug(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Shell, "zinit", || zsh::run_zinit(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Shell, "zi", || zsh::run_zi(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Shell, "zim", || zsh::run_zim(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Shell, "oh-my-zsh", || zsh::run_oh_my_zsh(&ctx))?;
|
||||
runner.execute(Step::Shell, "fisher", || unix::run_fisher(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Shell, "bash-it", || unix::run_bashit(&ctx))?;
|
||||
runner.execute(Step::Shell, "oh-my-fish", || unix::run_oh_my_fish(&ctx))?;
|
||||
runner.execute(Step::Shell, "fish-plug", || unix::run_fish_plug(&ctx))?;
|
||||
runner.execute(Step::Shell, "fundle", || unix::run_fundle(&ctx))?;
|
||||
runner.execute(Step::Tmux, "tmux", || tmux::run_tpm(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Tldr, "TLDR", || unix::run_tldr(run_type))?;
|
||||
runner.execute(Step::Pearl, "pearl", || unix::run_pearl(run_type))?;
|
||||
#[cfg(not(any(target_os = "macos", target_os = "android")))]
|
||||
runner.execute(Step::GnomeShellExtensions, "Gnome Shell Extensions", || {
|
||||
unix::upgrade_gnome_extensions(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::Sdkman, "SDKMAN!", || {
|
||||
unix::run_sdkman(&base_dirs, config.cleanup(), run_type)
|
||||
})?;
|
||||
runner.execute(Step::Rcm, "rcm", || unix::run_rcm(&ctx))?;
|
||||
}
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "dragonfly"
|
||||
)))]
|
||||
runner.execute(Step::Atom, "apm", || generic::run_apm(run_type))?;
|
||||
runner.execute(Step::Fossil, "fossil", || generic::run_fossil(run_type))?;
|
||||
runner.execute(Step::Rustup, "rustup", || generic::run_rustup(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Dotnet, ".NET", || generic::run_dotnet_upgrade(&ctx))?;
|
||||
runner.execute(Step::Choosenim, "choosenim", || generic::run_choosenim(&ctx))?;
|
||||
runner.execute(Step::Cargo, "cargo", || generic::run_cargo_update(&ctx))?;
|
||||
runner.execute(Step::Flutter, "Flutter", || generic::run_flutter_upgrade(run_type))?;
|
||||
runner.execute(Step::Go, "Go", || generic::run_go(run_type))?;
|
||||
runner.execute(Step::Emacs, "Emacs", || emacs.upgrade(&ctx))?;
|
||||
runner.execute(Step::Opam, "opam", || generic::run_opam_update(&ctx))?;
|
||||
runner.execute(Step::Vcpkg, "vcpkg", || generic::run_vcpkg_update(run_type))?;
|
||||
runner.execute(Step::Pipx, "pipx", || generic::run_pipx_update(run_type))?;
|
||||
runner.execute(Step::Conda, "conda", || generic::run_conda_update(&ctx))?;
|
||||
runner.execute(Step::Pip3, "pip3", || generic::run_pip3_update(run_type))?;
|
||||
runner.execute(Step::Ghcup, "ghcup", || generic::run_ghcup_update(run_type))?;
|
||||
runner.execute(Step::Stack, "stack", || generic::run_stack_update(run_type))?;
|
||||
runner.execute(Step::Tlmgr, "tlmgr", || generic::run_tlmgr_update(&ctx))?;
|
||||
runner.execute(Step::Myrepos, "myrepos", || {
|
||||
generic::run_myrepos_update(&base_dirs, run_type)
|
||||
})?;
|
||||
runner.execute(Step::Chezmoi, "chezmoi", || {
|
||||
generic::run_chezmoi_update(&base_dirs, run_type)
|
||||
})?;
|
||||
runner.execute(Step::Jetpack, "jetpack", || generic::run_jetpack(run_type))?;
|
||||
runner.execute(Step::Vim, "vim", || vim::upgrade_vim(&base_dirs, &ctx))?;
|
||||
runner.execute(Step::Vim, "Neovim", || vim::upgrade_neovim(&base_dirs, &ctx))?;
|
||||
runner.execute(Step::Vim, "The Ultimate vimrc", || vim::upgrade_ultimate_vimrc(&ctx))?;
|
||||
runner.execute(Step::Vim, "voom", || vim::run_voom(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Kakoune, "Kakoune", || kakoune::upgrade_kak_plug(&ctx))?;
|
||||
runner.execute(Step::Node, "npm", || node::run_npm_upgrade(&ctx))?;
|
||||
runner.execute(Step::Node, "yarn", || node::run_yarn_upgrade(&ctx))?;
|
||||
runner.execute(Step::Node, "pnpm", || node::run_pnpm_upgrade(&ctx))?;
|
||||
runner.execute(Step::Containers, "Containers", || containers::run_containers(&ctx))?;
|
||||
runner.execute(Step::Deno, "deno", || node::deno_upgrade(&ctx))?;
|
||||
runner.execute(Step::Composer, "composer", || generic::run_composer_update(&ctx))?;
|
||||
runner.execute(Step::Krew, "krew", || generic::run_krew_upgrade(run_type))?;
|
||||
runner.execute(Step::Gem, "gem", || generic::run_gem(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Julia, "julia", || generic::update_julia_packages(&ctx))?;
|
||||
runner.execute(Step::Haxelib, "haxelib", || generic::run_haxelib_update(&ctx))?;
|
||||
runner.execute(Step::Sheldon, "sheldon", || generic::run_sheldon(&ctx))?;
|
||||
runner.execute(Step::Rtcl, "rtcl", || generic::run_rtcl(&ctx))?;
|
||||
runner.execute(Step::Bin, "bin", || generic::bin_update(&ctx))?;
|
||||
runner.execute(Step::Gcloud, "gcloud", || {
|
||||
generic::run_gcloud_components_update(run_type)
|
||||
})?;
|
||||
runner.execute(Step::Micro, "micro", || generic::run_micro(run_type))?;
|
||||
runner.execute(Step::Raco, "raco", || generic::run_raco_update(run_type))?;
|
||||
runner.execute(Step::Spicetify, "spicetify", || generic::spicetify_upgrade(&ctx))?;
|
||||
runner.execute(Step::GithubCliExtensions, "GitHub CLI Extensions", || {
|
||||
generic::run_ghcli_extensions_upgrade(&ctx)
|
||||
})?;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
runner.execute(Step::DebGet, "deb-get", || linux::run_deb_get(&ctx))?;
|
||||
runner.execute(Step::Toolbx, "toolbx", || toolbx::run_toolbx(&ctx))?;
|
||||
runner.execute(Step::Flatpak, "Flatpak", || linux::flatpak_update(&ctx))?;
|
||||
runner.execute(Step::Snap, "snap", || linux::run_snap(sudo.as_ref(), run_type))?;
|
||||
runner.execute(Step::Pacstall, "pacstall", || linux::run_pacstall(&ctx))?;
|
||||
runner.execute(Step::Pacdef, "pacdef", || linux::run_pacdef(&ctx))?;
|
||||
runner.execute(Step::Protonup, "protonup", || linux::run_protonup_update(&ctx))?;
|
||||
runner.execute(Step::Distrobox, "distrobox", || linux::run_distrobox_update(&ctx))?;
|
||||
}
|
||||
|
||||
if let Some(commands) = config.commands() {
|
||||
for (name, command) in commands {
|
||||
if config.should_run_custom_command(name) {
|
||||
runner.execute(Step::CustomCommands, name, || {
|
||||
generic::run_custom_command(name, command, &ctx)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
runner.execute(Step::System, "pihole", || {
|
||||
linux::run_pihole_update(sudo.as_ref(), run_type)
|
||||
})?;
|
||||
runner.execute(Step::Firmware, "Firmware upgrades", || linux::run_fwupdmgr(&ctx))?;
|
||||
runner.execute(Step::Restarts, "Restarts", || {
|
||||
linux::run_needrestart(sudo.as_ref(), run_type)
|
||||
})?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
runner.execute(Step::Sparkle, "Sparkle", || macos::run_sparkle(&ctx))?;
|
||||
runner.execute(Step::Mas, "App Store", || macos::run_mas(run_type))?;
|
||||
runner.execute(Step::System, "System upgrade", || macos::upgrade_macos(&ctx))?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
runner.execute(Step::System, "FreeBSD Upgrade", || {
|
||||
freebsd::upgrade_freebsd(sudo.as_ref(), run_type)
|
||||
})?;
|
||||
|
||||
#[cfg(target_os = "openbsd")]
|
||||
runner.execute(Step::System, "OpenBSD Upgrade", || {
|
||||
openbsd::upgrade_openbsd(sudo.as_ref(), run_type)
|
||||
})?;
|
||||
|
||||
#[cfg(windows)]
|
||||
runner.execute(Step::System, "Windows update", || windows::windows_update(&ctx))?;
|
||||
|
||||
if config.should_run(Step::Vagrant) {
|
||||
if let Ok(boxes) = vagrant::collect_boxes(&ctx) {
|
||||
for vagrant_box in boxes {
|
||||
runner.execute(Step::Vagrant, format!("Vagrant ({})", vagrant_box.smart_name()), || {
|
||||
vagrant::topgrade_vagrant_box(&ctx, &vagrant_box)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
runner.execute(Step::Vagrant, "Vagrant boxes", || vagrant::upgrade_vagrant_boxes(&ctx))?;
|
||||
|
||||
if !runner.report().data().is_empty() {
|
||||
print_separator("Summary");
|
||||
|
||||
for (key, result) in runner.report().data() {
|
||||
print_result(key, result);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if let Ok(distribution) = &distribution {
|
||||
distribution.show_summary();
|
||||
if skipped_missing_sudo {
|
||||
print_warning(t!(
|
||||
"\nSome steps were skipped as sudo or equivalent could not be found."
|
||||
));
|
||||
// Steps can only fail with SkippedMissingSudo if sudo is None,
|
||||
// therefore we must have a sudo_err
|
||||
match sudo_err.unwrap() {
|
||||
SudoCreateError::CannotFindBinary => {
|
||||
#[cfg(unix)]
|
||||
print_warning(t!(
|
||||
"Install one of `sudo`, `doas`, `pkexec`, `run0` or `please` to run these steps."
|
||||
));
|
||||
|
||||
// if this windows version supported Windows Sudo, the error would have been WinSudoDisabled
|
||||
#[cfg(windows)]
|
||||
print_warning(t!("Install gsudo to run these steps."));
|
||||
}
|
||||
#[cfg(windows)]
|
||||
SudoCreateError::WinSudoDisabled => {
|
||||
print_warning(t!(
|
||||
"Install gsudo or enable Windows Sudo to run these steps.\nFor Windows Sudo, the default 'In a new window' mode is not supported as it prevents Topgrade from waiting for commands to finish. Please configure it to use 'Inline' mode instead.\nGo to https://go.microsoft.com/fwlink/?linkid=2257346 to learn more."
|
||||
));
|
||||
}
|
||||
#[cfg(windows)]
|
||||
SudoCreateError::WinSudoNewWindowMode => {
|
||||
print_warning(t!(
|
||||
"Windows Sudo was found, but it is set to 'In a new window' mode, which prevents Topgrade from waiting for commands to finish. Please configure it to use 'Inline' mode instead.\nGo to https://go.microsoft.com/fwlink/?linkid=2257346 to learn more."
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
freebsd::audit_packages(&sudo).ok();
|
||||
|
||||
#[cfg(target_os = "dragonfly")]
|
||||
dragonfly::audit_packages(&sudo).ok();
|
||||
}
|
||||
|
||||
let mut post_command_failed = false;
|
||||
#[cfg(target_os = "linux")]
|
||||
if config.show_distribution_summary() {
|
||||
if let Ok(distribution) = &distribution {
|
||||
distribution.show_summary();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(commands) = config.post_commands() {
|
||||
for (name, command) in commands {
|
||||
if generic::run_custom_command(name, command, &ctx).is_err() {
|
||||
post_command_failed = true;
|
||||
let result = generic::run_custom_command(name, command, &ctx);
|
||||
if !failed && result.is_err() {
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.keep_at_end() {
|
||||
print_info("\n(R)eboot\n(S)hell\n(Q)uit");
|
||||
print_info(t!("\n(R)eboot\n(S)hell\n(Q)uit"));
|
||||
loop {
|
||||
match get_key() {
|
||||
Ok(Key::Char('s')) | Ok(Key::Char('S')) => {
|
||||
run_shell();
|
||||
Ok(Key::Char('s' | 'S')) => {
|
||||
run_shell().context("Failed to execute shell")?;
|
||||
}
|
||||
Ok(Key::Char('r')) | Ok(Key::Char('R')) => {
|
||||
reboot();
|
||||
Ok(Key::Char('r' | 'R')) => {
|
||||
println!("{}", t!("Rebooting..."));
|
||||
reboot(&ctx).context("Failed to reboot")?;
|
||||
}
|
||||
Ok(Key::Char('q')) | Ok(Key::Char('Q')) => (),
|
||||
Ok(Key::Char('q' | 'Q')) => (),
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
@@ -485,23 +311,18 @@ fn run() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
let failed = post_command_failed || runner.report().data().iter().any(|(_, result)| result.failed());
|
||||
|
||||
if !config.skip_notify() {
|
||||
terminal::notify_desktop(
|
||||
format!(
|
||||
"Topgrade finished {}",
|
||||
if failed { "with errors" } else { "successfully" }
|
||||
),
|
||||
None,
|
||||
notify_desktop(
|
||||
if failed {
|
||||
t!("Topgrade finished with errors")
|
||||
} else {
|
||||
t!("Topgrade finished successfully")
|
||||
},
|
||||
Some(Duration::from_secs(10)),
|
||||
);
|
||||
}
|
||||
|
||||
if failed {
|
||||
Err(StepFailed.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
if failed { Err(StepFailed.into()) } else { Ok(()) }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -510,13 +331,6 @@ fn main() {
|
||||
exit(0);
|
||||
}
|
||||
Err(error) => {
|
||||
#[cfg(all(windows, feature = "self-update"))]
|
||||
{
|
||||
if let Some(Upgraded(status)) = error.downcast_ref::<Upgraded>() {
|
||||
exit(status.code().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
let skip_print = (error.downcast_ref::<StepFailed>().is_some())
|
||||
|| (error
|
||||
.downcast_ref::<io::Error>()
|
||||
@@ -524,10 +338,10 @@ fn main() {
|
||||
.is_some());
|
||||
|
||||
if !skip_print {
|
||||
// The `Debug` implementation of `anyhow::Result` prints a multi-line
|
||||
// The `Debug` implementation of `eyre::Result` prints a multi-line
|
||||
// error message that includes all the 'causes' added with
|
||||
// `.with_context(...)` calls.
|
||||
println!("Error: {:?}", error);
|
||||
println!("{}", t!("Error: {error}", error = format!("{:?}", error)));
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub enum StepResult {
|
||||
Success,
|
||||
Failure,
|
||||
Ignored,
|
||||
Skipped(String),
|
||||
}
|
||||
|
||||
impl StepResult {
|
||||
pub fn failed(&self) -> bool {
|
||||
match self {
|
||||
StepResult::Success | StepResult::Ignored | StepResult::Skipped(_) => false,
|
||||
StepResult::Failure => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type CowString<'a> = Cow<'a, str>;
|
||||
type ReportData<'a> = Vec<(CowString<'a>, StepResult)>;
|
||||
pub struct Report<'a> {
|
||||
data: ReportData<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Report<'a> {
|
||||
pub fn new() -> Self {
|
||||
Self { data: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn push_result<M>(&mut self, result: Option<(M, StepResult)>)
|
||||
where
|
||||
M: Into<CowString<'a>>,
|
||||
{
|
||||
if let Some((key, success)) = result {
|
||||
let key = key.into();
|
||||
|
||||
debug_assert!(!self.data.iter().any(|(k, _)| k == &key), "{} already reported", key);
|
||||
self.data.push((key, success));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &ReportData<'a> {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
104
src/runner.rs
104
src/runner.rs
@@ -1,12 +1,36 @@
|
||||
use crate::ctrlc;
|
||||
use crate::error::{DryRun, SkipStep};
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::report::{Report, StepResult};
|
||||
use crate::{config::Step, terminal::should_retry};
|
||||
use anyhow::Result;
|
||||
use log::debug;
|
||||
use color_eyre::eyre::{Result, WrapErr};
|
||||
use rust_i18n::t;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Debug;
|
||||
use std::io;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::ctrlc;
|
||||
use crate::error::{DryRun, MissingSudo, SkipStep};
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::step::Step;
|
||||
use crate::terminal::{ShouldRetry, print_error, print_warning, should_retry};
|
||||
|
||||
pub enum StepResult {
|
||||
Success,
|
||||
Failure,
|
||||
Ignored,
|
||||
SkippedMissingSudo,
|
||||
Skipped(String),
|
||||
}
|
||||
|
||||
impl StepResult {
|
||||
pub fn failed(&self) -> bool {
|
||||
use StepResult::*;
|
||||
|
||||
match self {
|
||||
Success | Ignored | Skipped(_) | SkippedMissingSudo => false,
|
||||
Failure => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Report<'a> = Vec<(Cow<'a, str>, StepResult)>;
|
||||
|
||||
pub struct Runner<'a> {
|
||||
ctx: &'a ExecutionContext<'a>,
|
||||
@@ -17,32 +41,50 @@ impl<'a> Runner<'a> {
|
||||
pub fn new(ctx: &'a ExecutionContext) -> Runner<'a> {
|
||||
Runner {
|
||||
ctx,
|
||||
report: Report::new(),
|
||||
report: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute<F, M>(&mut self, step: Step, key: M, func: F) -> Result<()>
|
||||
fn push_result(&mut self, key: Cow<'a, str>, result: StepResult) {
|
||||
debug_assert!(!self.report.iter().any(|(k, _)| k == &key), "{key} already reported");
|
||||
self.report.push((key, result));
|
||||
}
|
||||
|
||||
pub fn execute<K, F>(&mut self, step: Step, key: K, func: F) -> Result<()>
|
||||
where
|
||||
K: Into<Cow<'a, str>> + Debug,
|
||||
F: Fn() -> Result<()>,
|
||||
M: Into<Cow<'a, str>> + Debug,
|
||||
{
|
||||
if !self.ctx.config().should_run(step) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let key = key.into();
|
||||
let key: Cow<'a, str> = key.into();
|
||||
debug!("Step {:?}", key);
|
||||
|
||||
// alter the `func` to put it in a span
|
||||
let func = || {
|
||||
let span =
|
||||
tracing::span!(parent: tracing::Span::none(), tracing::Level::TRACE, "step", step = ?step, key = %key);
|
||||
let _guard = span.enter();
|
||||
func()
|
||||
};
|
||||
|
||||
loop {
|
||||
match func() {
|
||||
Ok(()) => {
|
||||
self.report.push_result(Some((key, StepResult::Success)));
|
||||
self.push_result(key, StepResult::Success);
|
||||
break;
|
||||
}
|
||||
Err(e) if e.downcast_ref::<DryRun>().is_some() => break,
|
||||
Err(e) if e.downcast_ref::<MissingSudo>().is_some() => {
|
||||
print_warning(t!("Skipping step, sudo is required"));
|
||||
self.push_result(key, StepResult::SkippedMissingSudo);
|
||||
break;
|
||||
}
|
||||
Err(e) if e.downcast_ref::<SkipStep>().is_some() => {
|
||||
if self.ctx.config().verbose() || self.ctx.config().show_skipped() {
|
||||
self.report.push_result(Some((key, StepResult::Skipped(e.to_string()))));
|
||||
self.push_result(key, StepResult::Skipped(e.to_string()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -55,18 +97,30 @@ impl<'a> Runner<'a> {
|
||||
|
||||
let ignore_failure = self.ctx.config().ignore_failure(step);
|
||||
let should_ask = interrupted || !(self.ctx.config().no_retry() || ignore_failure);
|
||||
let should_retry = should_ask && should_retry(interrupted, key.as_ref())?;
|
||||
let should_retry = if should_ask {
|
||||
print_error(&key, format!("{e:?}"));
|
||||
should_retry(key.as_ref())?
|
||||
} else {
|
||||
ShouldRetry::No
|
||||
};
|
||||
|
||||
if !should_retry {
|
||||
self.report.push_result(Some((
|
||||
key,
|
||||
if ignore_failure {
|
||||
StepResult::Ignored
|
||||
} else {
|
||||
StepResult::Failure
|
||||
},
|
||||
)));
|
||||
break;
|
||||
match should_retry {
|
||||
ShouldRetry::No | ShouldRetry::Quit => {
|
||||
self.push_result(
|
||||
key,
|
||||
if ignore_failure {
|
||||
StepResult::Ignored
|
||||
} else {
|
||||
StepResult::Failure
|
||||
},
|
||||
);
|
||||
if let ShouldRetry::Quit = should_retry {
|
||||
return Err(io::Error::from(io::ErrorKind::Interrupted))
|
||||
.context("Quit from user input");
|
||||
}
|
||||
break;
|
||||
}
|
||||
ShouldRetry::Yes => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,7 +129,7 @@ impl<'a> Runner<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn report(&self) -> &Report {
|
||||
pub fn report(&self) -> &Report<'_> {
|
||||
&self.report
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#![cfg(windows)]
|
||||
|
||||
use anyhow::Result;
|
||||
use log::{debug, error};
|
||||
use color_eyre::eyre::Result;
|
||||
use std::{env::current_exe, fs, path::PathBuf};
|
||||
use tracing::{debug, error};
|
||||
|
||||
pub struct SelfRenamer {
|
||||
exe_path: PathBuf,
|
||||
|
||||
@@ -1,63 +1,74 @@
|
||||
use super::terminal::*;
|
||||
#[cfg(windows)]
|
||||
use crate::error::Upgraded;
|
||||
use anyhow::{bail, Result};
|
||||
use self_update_crate::backends::github::Update;
|
||||
use self_update_crate::update::UpdateStatus;
|
||||
use std::env;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::process::CommandExt;
|
||||
use std::os::unix::process::CommandExt as _;
|
||||
use std::process::Command;
|
||||
#[cfg(windows)]
|
||||
use std::process::exit;
|
||||
|
||||
pub fn self_update() -> Result<()> {
|
||||
print_separator("Self update");
|
||||
let current_exe = env::current_exe();
|
||||
use crate::step::Step;
|
||||
use color_eyre::eyre::Result;
|
||||
#[cfg(unix)]
|
||||
use color_eyre::eyre::bail;
|
||||
use rust_i18n::t;
|
||||
use self_update_crate::backends::github::Update;
|
||||
use self_update_crate::update::UpdateStatus;
|
||||
|
||||
let target = self_update_crate::get_target();
|
||||
let result = Update::configure()
|
||||
.repo_owner("topgrade-rs")
|
||||
.repo_name("topgrade")
|
||||
.target(target)
|
||||
.bin_name(if cfg!(windows) {
|
||||
"topgrade-rs.exe"
|
||||
} else {
|
||||
"topgrade-rs"
|
||||
})
|
||||
.show_output(false)
|
||||
.show_download_progress(true)
|
||||
.current_version(self_update_crate::cargo_crate_version!())
|
||||
.no_confirm(true)
|
||||
.build()?
|
||||
.update_extended()?;
|
||||
use super::terminal::{print_info, print_separator};
|
||||
use crate::execution_context::ExecutionContext;
|
||||
|
||||
if let UpdateStatus::Updated(release) = &result {
|
||||
println!("\nTopgrade upgraded to {}:\n", release.version);
|
||||
if let Some(body) = &release.body {
|
||||
println!("{}", body);
|
||||
}
|
||||
pub fn self_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator(t!("Self update"));
|
||||
|
||||
if ctx.run_type().dry() {
|
||||
println!("{}", t!("Would self-update"));
|
||||
Ok(())
|
||||
} else {
|
||||
println!("Topgrade is up-to-date");
|
||||
}
|
||||
let assume_yes = ctx.config().yes(Step::SelfUpdate);
|
||||
let current_exe = env::current_exe();
|
||||
|
||||
{
|
||||
if result.updated() {
|
||||
print_warning("Respawning...");
|
||||
let mut command = Command::new(current_exe?);
|
||||
command.args(env::args().skip(1)).env("TOPGRADE_NO_SELF_UPGRADE", "");
|
||||
let target = self_update_crate::get_target();
|
||||
let result = Update::configure()
|
||||
.repo_owner("topgrade-rs")
|
||||
.repo_name("topgrade")
|
||||
.target(target)
|
||||
.bin_name(if cfg!(windows) { "topgrade.exe" } else { "topgrade" })
|
||||
.show_output(true)
|
||||
.show_download_progress(true)
|
||||
.current_version(self_update_crate::cargo_crate_version!())
|
||||
.no_confirm(assume_yes)
|
||||
.build()?
|
||||
.update_extended()?;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let err = command.exec();
|
||||
bail!(err);
|
||||
if let UpdateStatus::Updated(release) = &result {
|
||||
println!("{}", t!("Topgrade upgraded to {version}:\n", version = release.version));
|
||||
if let Some(body) = &release.body {
|
||||
println!("{body}");
|
||||
}
|
||||
} else {
|
||||
println!("{}", t!("Topgrade is up-to-date"));
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let status = command.spawn().and_then(|mut c| c.wait())?;
|
||||
bail!(Upgraded(status));
|
||||
{
|
||||
if result.updated() {
|
||||
print_info(t!("Respawning..."));
|
||||
let mut command = Command::new(current_exe?);
|
||||
command.args(env::args().skip(1)).env("TOPGRADE_NO_SELF_UPGRADE", "");
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let err = command.exec();
|
||||
bail!(err);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let status = command.status()?;
|
||||
exit(status.code().expect("This cannot return None on Windows"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
902
src/step.rs
Normal file
902
src/step.rs
Normal file
@@ -0,0 +1,902 @@
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::runner::Runner;
|
||||
use clap::ValueEnum;
|
||||
use color_eyre::Result;
|
||||
#[cfg(target_os = "linux")]
|
||||
use rust_i18n::t;
|
||||
use serde::Deserialize;
|
||||
use strum::{EnumCount, EnumIter, EnumString, VariantNames};
|
||||
|
||||
#[cfg(feature = "self-update")]
|
||||
use crate::self_update;
|
||||
use crate::steps::remote::vagrant;
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use crate::steps::*;
|
||||
use crate::utils::hostname;
|
||||
|
||||
#[derive(ValueEnum, EnumString, VariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy, EnumCount)]
|
||||
#[clap(rename_all = "snake_case")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum Step {
|
||||
AM,
|
||||
AndroidStudio,
|
||||
AppMan,
|
||||
Aqua,
|
||||
Asdf,
|
||||
Atom,
|
||||
Atuin,
|
||||
Audit,
|
||||
AutoCpufreq,
|
||||
Bin,
|
||||
Bob,
|
||||
BrewCask,
|
||||
BrewFormula,
|
||||
Bun,
|
||||
BunPackages,
|
||||
Cargo,
|
||||
Certbot,
|
||||
Chezmoi,
|
||||
Chocolatey,
|
||||
Choosenim,
|
||||
CinnamonSpices,
|
||||
ClamAvDb,
|
||||
Composer,
|
||||
Conda,
|
||||
ConfigUpdate,
|
||||
Containers,
|
||||
CustomCommands,
|
||||
DebGet,
|
||||
Deno,
|
||||
Distrobox,
|
||||
DkpPacman,
|
||||
Dotnet,
|
||||
Elan,
|
||||
Emacs,
|
||||
Falconf,
|
||||
Firmware,
|
||||
Flatpak,
|
||||
Flutter,
|
||||
Fossil,
|
||||
Gcloud,
|
||||
Gem,
|
||||
Ghcup,
|
||||
GitRepos,
|
||||
GithubCliExtensions,
|
||||
GnomeShellExtensions,
|
||||
Go,
|
||||
Guix,
|
||||
Haxelib,
|
||||
Helix,
|
||||
Helm,
|
||||
HomeManager,
|
||||
Hyprpm,
|
||||
// These names are miscapitalized on purpose, so the CLI name is
|
||||
// `jetbrains_pycharm` instead of `jet_brains_py_charm`.
|
||||
JetbrainsAqua,
|
||||
JetbrainsClion,
|
||||
JetbrainsDatagrip,
|
||||
JetbrainsDataspell,
|
||||
JetbrainsGateway,
|
||||
JetbrainsGoland,
|
||||
JetbrainsIdea,
|
||||
JetbrainsMps,
|
||||
JetbrainsPhpstorm,
|
||||
JetbrainsPycharm,
|
||||
JetbrainsRider,
|
||||
JetbrainsRubymine,
|
||||
JetbrainsRustrover,
|
||||
JetbrainsToolbox,
|
||||
JetbrainsWebstorm,
|
||||
Jetpack,
|
||||
Julia,
|
||||
Juliaup,
|
||||
Kakoune,
|
||||
Krew,
|
||||
Lensfun,
|
||||
Lure,
|
||||
Macports,
|
||||
Mamba,
|
||||
Mandb,
|
||||
Mas,
|
||||
Maza,
|
||||
Micro,
|
||||
MicrosoftStore,
|
||||
Miktex,
|
||||
Mise,
|
||||
Myrepos,
|
||||
Nix,
|
||||
NixHelper,
|
||||
Node,
|
||||
Opam,
|
||||
Pacdef,
|
||||
Pacstall,
|
||||
Pearl,
|
||||
Pip3,
|
||||
PipReview,
|
||||
PipReviewLocal,
|
||||
Pipupgrade,
|
||||
Pipx,
|
||||
Pipxu,
|
||||
Pixi,
|
||||
Pkg,
|
||||
Pkgfile,
|
||||
Pkgin,
|
||||
PlatformioCore,
|
||||
Pnpm,
|
||||
Poetry,
|
||||
Powershell,
|
||||
Protonup,
|
||||
Pyenv,
|
||||
Raco,
|
||||
Rcm,
|
||||
Remotes,
|
||||
Restarts,
|
||||
Rtcl,
|
||||
RubyGems,
|
||||
Rustup,
|
||||
Rye,
|
||||
Scoop,
|
||||
Sdkman,
|
||||
SelfUpdate,
|
||||
Sheldon,
|
||||
Shell,
|
||||
Snap,
|
||||
Sparkle,
|
||||
Spicetify,
|
||||
Stack,
|
||||
Stew,
|
||||
System,
|
||||
Tldr,
|
||||
Tlmgr,
|
||||
Tmux,
|
||||
Toolbx,
|
||||
Typst,
|
||||
Uv,
|
||||
Vagrant,
|
||||
Vcpkg,
|
||||
Vim,
|
||||
VoltaPackages,
|
||||
Vscode,
|
||||
VscodeInsiders,
|
||||
Vscodium,
|
||||
VscodiumInsiders,
|
||||
Waydroid,
|
||||
Winget,
|
||||
Wsl,
|
||||
WslUpdate,
|
||||
Xcodes,
|
||||
Yadm,
|
||||
Yarn,
|
||||
Yazi,
|
||||
Zigup,
|
||||
Zvm,
|
||||
}
|
||||
|
||||
impl Step {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn run(&self, runner: &mut Runner, ctx: &ExecutionContext) -> Result<()> {
|
||||
use Step::*;
|
||||
|
||||
match *self {
|
||||
AM =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "am", || linux::run_am(ctx))?
|
||||
}
|
||||
AndroidStudio => runner.execute(*self, "Android Studio Plugins", || generic::run_android_studio(ctx))?,
|
||||
AppMan =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "appman", || linux::run_appman(ctx))?
|
||||
}
|
||||
Aqua => runner.execute(*self, "aqua", || generic::run_aqua(ctx))?,
|
||||
Asdf =>
|
||||
{
|
||||
#[cfg(unix)]
|
||||
runner.execute(*self, "asdf", || unix::run_asdf(ctx))?
|
||||
}
|
||||
Atom =>
|
||||
{
|
||||
#[cfg(not(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "dragonfly"
|
||||
)))]
|
||||
runner.execute(*self, "apm", || generic::run_apm(ctx))?
|
||||
}
|
||||
Atuin =>
|
||||
{
|
||||
#[cfg(unix)]
|
||||
runner.execute(*self, "atuin", || unix::run_atuin(ctx))?
|
||||
}
|
||||
Audit => {
|
||||
#[cfg(target_os = "dragonfly")]
|
||||
runner.execute(*self, "DragonFly Audit", || dragonfly::audit_packages(ctx))?;
|
||||
#[cfg(target_os = "freebsd")]
|
||||
runner.execute(*self, "FreeBSD Audit", || freebsd::audit_packages(ctx))?
|
||||
}
|
||||
AutoCpufreq =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "auto-cpufreq", || linux::run_auto_cpufreq(ctx))?
|
||||
}
|
||||
Bin => runner.execute(*self, "bin", || generic::bin_update(ctx))?,
|
||||
Bob => runner.execute(*self, "Bob", || generic::run_bob(ctx))?,
|
||||
BrewCask => {
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
runner.execute(*self, "Brew Cask", || unix::run_brew_cask(ctx, unix::BrewVariant::Path))?;
|
||||
#[cfg(target_os = "macos")]
|
||||
runner.execute(*self, "Brew Cask (Intel)", || {
|
||||
unix::run_brew_cask(ctx, unix::BrewVariant::MacIntel)
|
||||
})?;
|
||||
#[cfg(target_os = "macos")]
|
||||
runner.execute(*self, "Brew Cask (ARM)", || {
|
||||
unix::run_brew_cask(ctx, unix::BrewVariant::MacArm)
|
||||
})?
|
||||
}
|
||||
BrewFormula => {
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "Brew", || unix::run_brew_formula(ctx, unix::BrewVariant::Path))?;
|
||||
#[cfg(target_os = "macos")]
|
||||
runner.execute(*self, "Brew (ARM)", || {
|
||||
unix::run_brew_formula(ctx, unix::BrewVariant::MacArm)
|
||||
})?;
|
||||
#[cfg(target_os = "macos")]
|
||||
runner.execute(*self, "Brew (Intel)", || {
|
||||
unix::run_brew_formula(ctx, unix::BrewVariant::MacIntel)
|
||||
})?
|
||||
}
|
||||
Bun => runner.execute(*self, "bun", || generic::run_bun(ctx))?,
|
||||
BunPackages =>
|
||||
{
|
||||
#[cfg(unix)]
|
||||
runner.execute(*self, "bun-packages", || unix::run_bun_packages(ctx))?
|
||||
}
|
||||
Cargo => runner.execute(*self, "cargo", || generic::run_cargo_update(ctx))?,
|
||||
Certbot => runner.execute(*self, "Certbot", || generic::run_certbot(ctx))?,
|
||||
Chezmoi => runner.execute(*self, "chezmoi", || generic::run_chezmoi_update(ctx))?,
|
||||
Chocolatey =>
|
||||
{
|
||||
#[cfg(windows)]
|
||||
runner.execute(*self, "Chocolatey", || windows::run_chocolatey(ctx))?
|
||||
}
|
||||
Choosenim => runner.execute(*self, "choosenim", || generic::run_choosenim(ctx))?,
|
||||
CinnamonSpices =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "Cinnamon spices", || linux::run_cinnamon_spices_updater(ctx))?
|
||||
}
|
||||
ClamAvDb => runner.execute(*self, "ClamAV Databases", || generic::run_freshclam(ctx))?,
|
||||
Composer => runner.execute(*self, "composer", || generic::run_composer_update(ctx))?,
|
||||
Conda => runner.execute(*self, "conda", || generic::run_conda_update(ctx))?,
|
||||
ConfigUpdate =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "config-update", || linux::run_config_update(ctx))?
|
||||
}
|
||||
Containers => runner.execute(*self, "Containers", || containers::run_containers(ctx))?,
|
||||
CustomCommands => {
|
||||
if let Some(commands) = ctx.config().commands() {
|
||||
for (name, command) in commands
|
||||
.iter()
|
||||
.filter(|(n, _)| ctx.config().should_run_custom_command(n))
|
||||
{
|
||||
runner.execute(*self, name.clone(), || generic::run_custom_command(name, command, ctx))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
DebGet =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "deb-get", || linux::run_deb_get(ctx))?
|
||||
}
|
||||
Deno => runner.execute(*self, "deno", || node::deno_upgrade(ctx))?,
|
||||
Distrobox =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "distrobox", || linux::run_distrobox_update(ctx))?
|
||||
}
|
||||
DkpPacman =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "dkp-pacman", || linux::run_dkp_pacman_update(ctx))?
|
||||
}
|
||||
Dotnet => runner.execute(*self, ".NET", || generic::run_dotnet_upgrade(ctx))?,
|
||||
Elan => runner.execute(*self, "elan", || generic::run_elan(ctx))?,
|
||||
Emacs => runner.execute(*self, "Emacs", || emacs::Emacs::new().upgrade(ctx))?,
|
||||
Falconf => runner.execute(*self, "falconf sync", || generic::run_falconf(ctx))?,
|
||||
Firmware =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "Firmware", || linux::run_fwupdmgr(ctx))?
|
||||
}
|
||||
Flatpak =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "Flatpak", || linux::run_flatpak(ctx))?
|
||||
}
|
||||
Flutter => runner.execute(*self, "Flutter", || generic::run_flutter_upgrade(ctx))?,
|
||||
Fossil => runner.execute(*self, "fossil", || generic::run_fossil(ctx))?,
|
||||
Gcloud => runner.execute(*self, "gcloud", || generic::run_gcloud_components_update(ctx))?,
|
||||
Gem => runner.execute(*self, "gem", || generic::run_gem(ctx))?,
|
||||
Ghcup => runner.execute(*self, "ghcup", || generic::run_ghcup_update(ctx))?,
|
||||
GitRepos => runner.execute(*self, "Git Repositories", || git::run_git_pull(ctx))?,
|
||||
GithubCliExtensions => runner.execute(*self, "GitHub CLI Extensions", || {
|
||||
generic::run_ghcli_extensions_upgrade(ctx)
|
||||
})?,
|
||||
GnomeShellExtensions =>
|
||||
{
|
||||
#[cfg(all(unix, not(any(target_os = "macos", target_os = "android"))))]
|
||||
runner.execute(*self, "Gnome Shell Extensions", || unix::upgrade_gnome_extensions(ctx))?
|
||||
}
|
||||
Go => {
|
||||
runner.execute(*self, "go-global-update", || go::run_go_global_update(ctx))?;
|
||||
runner.execute(*self, "gup", || go::run_go_gup(ctx))?
|
||||
}
|
||||
Guix =>
|
||||
{
|
||||
#[cfg(unix)]
|
||||
runner.execute(*self, "guix", || unix::run_guix(ctx))?
|
||||
}
|
||||
Haxelib => runner.execute(*self, "haxelib", || generic::run_haxelib_update(ctx))?,
|
||||
Helix => runner.execute(*self, "helix", || generic::run_helix_grammars(ctx))?,
|
||||
Helm => runner.execute(*self, "helm", || generic::run_helm_repo_update(ctx))?,
|
||||
HomeManager =>
|
||||
{
|
||||
#[cfg(unix)]
|
||||
runner.execute(*self, "home-manager", || unix::run_home_manager(ctx))?
|
||||
}
|
||||
Hyprpm =>
|
||||
{
|
||||
#[cfg(unix)]
|
||||
runner.execute(*self, "hyprpm", || unix::run_hyprpm(ctx))?
|
||||
}
|
||||
JetbrainsAqua => runner.execute(*self, "JetBrains Aqua Plugins", || generic::run_jetbrains_aqua(ctx))?,
|
||||
JetbrainsClion => runner.execute(*self, "JetBrains CL", || generic::run_jetbrains_clion(ctx))?,
|
||||
JetbrainsDatagrip => {
|
||||
runner.execute(*self, "JetBrains DataGrip", || generic::run_jetbrains_datagrip(ctx))?
|
||||
}
|
||||
JetbrainsDataspell => runner.execute(*self, "JetBrains DataSpell Plugins", || {
|
||||
generic::run_jetbrains_dataspell(ctx)
|
||||
})?,
|
||||
JetbrainsGateway => runner.execute(*self, "JetBrains Gateway Plugins", || {
|
||||
generic::run_jetbrains_gateway(ctx)
|
||||
})?,
|
||||
JetbrainsGoland => {
|
||||
runner.execute(*self, "JetBrains GoLand Plugins", || generic::run_jetbrains_goland(ctx))?
|
||||
}
|
||||
JetbrainsIdea => runner.execute(*self, "JetBrains IntelliJ IDEA Plugins", || {
|
||||
generic::run_jetbrains_idea(ctx)
|
||||
})?,
|
||||
JetbrainsMps => runner.execute(*self, "JetBrains MPS Plugins", || generic::run_jetbrains_mps(ctx))?,
|
||||
JetbrainsPhpstorm => runner.execute(*self, "JetBrains PhpStorm Plugins", || {
|
||||
generic::run_jetbrains_phpstorm(ctx)
|
||||
})?,
|
||||
JetbrainsPycharm => runner.execute(*self, "JetBrains PyCharm Plugins", || {
|
||||
generic::run_jetbrains_pycharm(ctx)
|
||||
})?,
|
||||
JetbrainsRider => runner.execute(*self, "JetBrains Rider Plugins", || generic::run_jetbrains_rider(ctx))?,
|
||||
JetbrainsRubymine => runner.execute(*self, "JetBrains RubyMine Plugins", || {
|
||||
generic::run_jetbrains_rubymine(ctx)
|
||||
})?,
|
||||
JetbrainsRustrover => runner.execute(*self, "JetBrains RustRover Plugins", || {
|
||||
generic::run_jetbrains_rustrover(ctx)
|
||||
})?,
|
||||
JetbrainsToolbox => runner.execute(*self, "JetBrains Toolbox", || generic::run_jetbrains_toolbox(ctx))?,
|
||||
JetbrainsWebstorm => runner.execute(*self, "JetBrains WebStorm Plugins", || {
|
||||
generic::run_jetbrains_webstorm(ctx)
|
||||
})?,
|
||||
Jetpack => runner.execute(*self, "jetpack", || generic::run_jetpack(ctx))?,
|
||||
Julia => runner.execute(*self, "julia", || generic::update_julia_packages(ctx))?,
|
||||
Juliaup => runner.execute(*self, "juliaup", || generic::run_juliaup(ctx))?,
|
||||
Kakoune => runner.execute(*self, "Kakoune", || kakoune::upgrade_kak_plug(ctx))?,
|
||||
Krew => runner.execute(*self, "krew", || generic::run_krew_upgrade(ctx))?,
|
||||
Lensfun => runner.execute(*self, "Lensfun's database update", || {
|
||||
generic::run_lensfun_update_data(ctx)
|
||||
})?,
|
||||
Lure =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "LURE", || linux::run_lure_update(ctx))?
|
||||
}
|
||||
Macports =>
|
||||
{
|
||||
#[cfg(target_os = "macos")]
|
||||
runner.execute(*self, "MacPorts", || macos::run_macports(ctx))?
|
||||
}
|
||||
Mamba => runner.execute(*self, "mamba", || generic::run_mamba_update(ctx))?,
|
||||
Mandb =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "Manual Entries", || linux::run_mandb(ctx))?
|
||||
}
|
||||
Mas =>
|
||||
{
|
||||
#[cfg(target_os = "macos")]
|
||||
runner.execute(*self, "App Store", || macos::run_mas(ctx))?
|
||||
}
|
||||
Maza =>
|
||||
{
|
||||
#[cfg(unix)]
|
||||
runner.execute(*self, "maza", || unix::run_maza(ctx))?
|
||||
}
|
||||
Micro => runner.execute(*self, "micro", || generic::run_micro(ctx))?,
|
||||
MicrosoftStore =>
|
||||
{
|
||||
#[cfg(windows)]
|
||||
runner.execute(*self, "Microsoft Store", || windows::microsoft_store(ctx))?
|
||||
}
|
||||
Miktex => runner.execute(*self, "miktex", || generic::run_miktex_packages_update(ctx))?,
|
||||
Mise =>
|
||||
{
|
||||
#[cfg(unix)]
|
||||
runner.execute(*self, "mise", || unix::run_mise(ctx))?
|
||||
}
|
||||
Myrepos => runner.execute(*self, "myrepos", || generic::run_myrepos_update(ctx))?,
|
||||
Nix => {
|
||||
#[cfg(unix)]
|
||||
runner.execute(*self, "nix", || unix::run_nix(ctx))?;
|
||||
#[cfg(unix)]
|
||||
runner.execute(*self, "nix upgrade-nix", || unix::run_nix_self_upgrade(ctx))?
|
||||
}
|
||||
NixHelper =>
|
||||
{
|
||||
#[cfg(unix)]
|
||||
runner.execute(*self, "nh", || unix::run_nix_helper(ctx))?
|
||||
}
|
||||
Node => runner.execute(*self, "npm", || node::run_npm_upgrade(ctx))?,
|
||||
Opam => runner.execute(*self, "opam", || generic::run_opam_update(ctx))?,
|
||||
Pacdef =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "pacdef", || linux::run_pacdef(ctx))?
|
||||
}
|
||||
Pacstall =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "pacstall", || linux::run_pacstall(ctx))?
|
||||
}
|
||||
Pearl =>
|
||||
{
|
||||
#[cfg(unix)]
|
||||
runner.execute(*self, "pearl", || unix::run_pearl(ctx))?
|
||||
}
|
||||
Pip3 => runner.execute(*self, "pip3", || generic::run_pip3_update(ctx))?,
|
||||
PipReview => runner.execute(*self, "pip-review", || generic::run_pip_review_update(ctx))?,
|
||||
PipReviewLocal => runner.execute(*self, "pip-review (local)", || {
|
||||
generic::run_pip_review_local_update(ctx)
|
||||
})?,
|
||||
Pipupgrade => runner.execute(*self, "pipupgrade", || generic::run_pipupgrade_update(ctx))?,
|
||||
Pipx => runner.execute(*self, "pipx", || generic::run_pipx_update(ctx))?,
|
||||
Pipxu => runner.execute(*self, "pipxu", || generic::run_pipxu_update(ctx))?,
|
||||
Pixi => runner.execute(*self, "pixi", || generic::run_pixi_update(ctx))?,
|
||||
Pkg => {
|
||||
#[cfg(target_os = "dragonfly")]
|
||||
runner.execute(*self, "Dragonfly BSD Packages", || dragonfly::upgrade_packages(ctx))?;
|
||||
#[cfg(target_os = "freebsd")]
|
||||
runner.execute(*self, "FreeBSD Packages", || freebsd::upgrade_packages(ctx))?;
|
||||
#[cfg(target_os = "openbsd")]
|
||||
runner.execute(*self, "OpenBSD Packages", || openbsd::upgrade_packages(ctx))?;
|
||||
#[cfg(target_os = "android")]
|
||||
runner.execute(*self, "Termux Packages", || android::upgrade_packages(ctx))?
|
||||
}
|
||||
Pkgfile =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "pkgfile", || linux::run_pkgfile(ctx))?
|
||||
}
|
||||
Pkgin =>
|
||||
{
|
||||
#[cfg(unix)]
|
||||
runner.execute(*self, "pkgin", || unix::run_pkgin(ctx))?
|
||||
}
|
||||
PlatformioCore => runner.execute(*self, "PlatformIO Core", || generic::run_platform_io(ctx))?,
|
||||
Pnpm => runner.execute(*self, "pnpm", || node::run_pnpm_upgrade(ctx))?,
|
||||
Poetry => runner.execute(*self, "Poetry", || generic::run_poetry(ctx))?,
|
||||
Powershell => runner.execute(*self, "Powershell Modules Update", || generic::run_powershell(ctx))?,
|
||||
Protonup =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "protonup", || linux::run_protonup_update(ctx))?
|
||||
}
|
||||
Pyenv =>
|
||||
{
|
||||
#[cfg(unix)]
|
||||
runner.execute(*self, "pyenv", || unix::run_pyenv(ctx))?
|
||||
}
|
||||
Raco => runner.execute(*self, "raco", || generic::run_raco_update(ctx))?,
|
||||
Rcm =>
|
||||
{
|
||||
#[cfg(unix)]
|
||||
runner.execute(*self, "rcm", || unix::run_rcm(ctx))?
|
||||
}
|
||||
Remotes => {
|
||||
if let Some(topgrades) = ctx.config().remote_topgrades() {
|
||||
for remote_topgrade in topgrades
|
||||
.iter()
|
||||
.filter(|t| ctx.config().should_execute_remote(hostname(), t))
|
||||
{
|
||||
runner.execute(*self, format!("Remote ({remote_topgrade})"), || {
|
||||
crate::ssh::ssh_step(ctx, remote_topgrade)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Restarts =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "Restarts", || linux::run_needrestart(ctx))?
|
||||
}
|
||||
Rtcl => runner.execute(*self, "rtcl", || generic::run_rtcl(ctx))?,
|
||||
RubyGems => runner.execute(*self, "rubygems", || generic::run_rubygems(ctx))?,
|
||||
Rustup => runner.execute(*self, "rustup", || generic::run_rustup(ctx))?,
|
||||
Rye => runner.execute(*self, "rye", || generic::run_rye(ctx))?,
|
||||
Scoop =>
|
||||
{
|
||||
#[cfg(windows)]
|
||||
runner.execute(*self, "Scoop", || windows::run_scoop(ctx))?
|
||||
}
|
||||
Sdkman =>
|
||||
{
|
||||
#[cfg(unix)]
|
||||
runner.execute(*self, "SDKMAN!", || unix::run_sdkman(ctx))?
|
||||
}
|
||||
SelfUpdate => {
|
||||
// Self-Update step, this will execute only if:
|
||||
// 1. the `self-update` feature is enabled
|
||||
// 2. it is not disabled from configuration (env var/CLI opt/file)
|
||||
#[cfg(feature = "self-update")]
|
||||
{
|
||||
if std::env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() && !ctx.config().no_self_update() {
|
||||
runner.execute(*self, "Self Update", || self_update::self_update(ctx))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Sheldon => runner.execute(*self, "sheldon", || generic::run_sheldon(ctx))?,
|
||||
Shell => {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
runner.execute(*self, "zr", || zsh::run_zr(ctx))?;
|
||||
runner.execute(*self, "antibody", || zsh::run_antibody(ctx))?;
|
||||
runner.execute(*self, "antidote", || zsh::run_antidote(ctx))?;
|
||||
runner.execute(*self, "antigen", || zsh::run_antigen(ctx))?;
|
||||
runner.execute(*self, "zgenom", || zsh::run_zgenom(ctx))?;
|
||||
runner.execute(*self, "zplug", || zsh::run_zplug(ctx))?;
|
||||
runner.execute(*self, "zinit", || zsh::run_zinit(ctx))?;
|
||||
runner.execute(*self, "zi", || zsh::run_zi(ctx))?;
|
||||
runner.execute(*self, "zim", || zsh::run_zim(ctx))?;
|
||||
runner.execute(*self, "oh-my-zsh", || zsh::run_oh_my_zsh(ctx))?;
|
||||
runner.execute(*self, "oh-my-bash", || unix::run_oh_my_bash(ctx))?;
|
||||
runner.execute(*self, "fisher", || unix::run_fisher(ctx))?;
|
||||
runner.execute(*self, "bash-it", || unix::run_bashit(ctx))?;
|
||||
runner.execute(*self, "oh-my-fish", || unix::run_oh_my_fish(ctx))?;
|
||||
runner.execute(*self, "fish-plug", || unix::run_fish_plug(ctx))?;
|
||||
runner.execute(*self, "fundle", || unix::run_fundle(ctx))?
|
||||
}
|
||||
}
|
||||
Snap =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "snap", || linux::run_snap(ctx))?
|
||||
}
|
||||
Sparkle =>
|
||||
{
|
||||
#[cfg(target_os = "macos")]
|
||||
runner.execute(*self, "Sparkle", || macos::run_sparkle(ctx))?
|
||||
}
|
||||
Spicetify => runner.execute(*self, "spicetify", || generic::spicetify_upgrade(ctx))?,
|
||||
Stack => runner.execute(*self, "stack", || generic::run_stack_update(ctx))?,
|
||||
Stew => runner.execute(*self, "stew", || generic::run_stew(ctx))?,
|
||||
System => {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// NOTE: Due to breaking `nu` updates, `packer.nu` needs to be updated before `nu` get updated
|
||||
// by other package managers.
|
||||
runner.execute(Shell, "packer.nu", || linux::run_packer_nu(ctx))?;
|
||||
|
||||
match ctx.distribution() {
|
||||
Ok(distribution) => {
|
||||
runner.execute(*self, "System update", || distribution.upgrade(ctx))?;
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{}", t!("Error detecting current distribution: {error}", error = e));
|
||||
}
|
||||
}
|
||||
runner.execute(*self, "pihole", || linux::run_pihole_update(ctx))?;
|
||||
}
|
||||
#[cfg(windows)]
|
||||
runner.execute(*self, "Windows update", || windows::windows_update(ctx))?;
|
||||
#[cfg(target_os = "macos")]
|
||||
runner.execute(*self, "System update", || macos::upgrade_macos(ctx))?;
|
||||
#[cfg(target_os = "freebsd")]
|
||||
runner.execute(*self, "FreeBSD Upgrade", || freebsd::upgrade_freebsd(ctx))?;
|
||||
#[cfg(target_os = "openbsd")]
|
||||
runner.execute(*self, "OpenBSD Upgrade", || openbsd::upgrade_openbsd(ctx))?
|
||||
}
|
||||
Tldr => runner.execute(*self, "TLDR", || generic::run_tldr(ctx))?,
|
||||
Tlmgr => runner.execute(*self, "tlmgr", || generic::run_tlmgr_update(ctx))?,
|
||||
Tmux =>
|
||||
{
|
||||
#[cfg(unix)]
|
||||
runner.execute(*self, "tmux", || tmux::run_tpm(ctx))?
|
||||
}
|
||||
Toolbx =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "toolbx", || toolbx::run_toolbx(ctx))?
|
||||
}
|
||||
Typst => runner.execute(*self, "Typst", || generic::run_typst(ctx))?,
|
||||
Uv => runner.execute(*self, "uv", || generic::run_uv(ctx))?,
|
||||
Vagrant => {
|
||||
if ctx.config().should_run(Vagrant) {
|
||||
if let Ok(boxes) = vagrant::collect_boxes(ctx) {
|
||||
for vagrant_box in boxes {
|
||||
runner.execute(*self, format!("Vagrant ({})", vagrant_box.smart_name()), || {
|
||||
vagrant::topgrade_vagrant_box(ctx, &vagrant_box)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
runner.execute(*self, "Vagrant boxes", || vagrant::upgrade_vagrant_boxes(ctx))?;
|
||||
}
|
||||
Vcpkg => runner.execute(*self, "vcpkg", || generic::run_vcpkg_update(ctx))?,
|
||||
Vim => {
|
||||
runner.execute(*self, "vim", || vim::upgrade_vim(ctx))?;
|
||||
runner.execute(*self, "Neovim", || vim::upgrade_neovim(ctx))?;
|
||||
runner.execute(*self, "The Ultimate vimrc", || vim::upgrade_ultimate_vimrc(ctx))?;
|
||||
runner.execute(*self, "voom", || vim::run_voom(ctx))?
|
||||
}
|
||||
VoltaPackages => runner.execute(*self, "volta packages", || node::run_volta_packages_upgrade(ctx))?,
|
||||
Vscode => runner.execute(*self, "Visual Studio Code extensions", || {
|
||||
generic::run_vscode_extensions_update(ctx)
|
||||
})?,
|
||||
VscodeInsiders => runner.execute(*self, "Visual Studio Code Insiders extensions", || {
|
||||
generic::run_vscode_insiders_extensions_update(ctx)
|
||||
})?,
|
||||
Vscodium => runner.execute(*self, "VSCodium extensions", || {
|
||||
generic::run_vscodium_extensions_update(ctx)
|
||||
})?,
|
||||
VscodiumInsiders => runner.execute(*self, "VSCodium Insiders extensions", || {
|
||||
generic::run_vscodium_insiders_extensions_update(ctx)
|
||||
})?,
|
||||
Waydroid =>
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
runner.execute(*self, "Waydroid", || linux::run_waydroid(ctx))?
|
||||
}
|
||||
Winget =>
|
||||
{
|
||||
#[cfg(windows)]
|
||||
runner.execute(*self, "Winget", || windows::run_winget(ctx))?
|
||||
}
|
||||
Wsl =>
|
||||
{
|
||||
#[cfg(windows)]
|
||||
runner.execute(*self, "WSL", || windows::run_wsl_topgrade(ctx))?
|
||||
}
|
||||
WslUpdate =>
|
||||
{
|
||||
#[cfg(windows)]
|
||||
runner.execute(*self, "Update WSL", || windows::update_wsl(ctx))?
|
||||
}
|
||||
Xcodes =>
|
||||
{
|
||||
#[cfg(target_os = "macos")]
|
||||
runner.execute(*self, "Xcodes", || macos::update_xcodes(ctx))?
|
||||
}
|
||||
Yadm =>
|
||||
{
|
||||
#[cfg(unix)]
|
||||
runner.execute(*self, "yadm", || unix::run_yadm(ctx))?
|
||||
}
|
||||
Yarn => runner.execute(*self, "yarn", || node::run_yarn_upgrade(ctx))?,
|
||||
Yazi => runner.execute(*self, "Yazi packages", || generic::run_yazi(ctx))?,
|
||||
Zigup => runner.execute(*self, "zigup", || generic::run_zigup(ctx))?,
|
||||
Zvm => runner.execute(*self, "ZVM", || generic::run_zvm(ctx))?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub(crate) fn default_steps() -> Vec<Step> {
|
||||
// For now, SelfRenamer and SelfUpdate isn't included as they're ran before the other non-steps (pre-commands, sudo, etc)
|
||||
|
||||
use Step::*;
|
||||
// Could probably have a smaller starting capacity, but this at least ensures only 2 allocations:
|
||||
// initial and shrink
|
||||
let mut steps = Vec::with_capacity(Step::COUNT);
|
||||
|
||||
// Not combined with other generic steps to preserve the order as it was in main.rs originally,
|
||||
// but this can be changed in the future.
|
||||
steps.push(Remotes);
|
||||
|
||||
#[cfg(windows)]
|
||||
steps.extend_from_slice(&[Wsl, WslUpdate, Chocolatey, Scoop, Winget, System, MicrosoftStore]);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
steps.extend_from_slice(&[BrewFormula, BrewCask, Macports, Xcodes, Sparkle, Mas, System]);
|
||||
|
||||
#[cfg(target_os = "dragonfly")]
|
||||
steps.extend_from_slice(&[Pkg, Audit]);
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
steps.extend_from_slice(&[Pkg, System, Audit]);
|
||||
|
||||
#[cfg(target_os = "openbsd")]
|
||||
steps.extend_from_slice(&[Pkg, System]);
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
steps.push(Pkg);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
steps.extend_from_slice(&[
|
||||
System,
|
||||
ConfigUpdate,
|
||||
AM,
|
||||
AppMan,
|
||||
DebGet,
|
||||
Toolbx,
|
||||
Snap,
|
||||
Pacstall,
|
||||
Pacdef,
|
||||
Protonup,
|
||||
Distrobox,
|
||||
DkpPacman,
|
||||
Firmware,
|
||||
Restarts,
|
||||
Flatpak,
|
||||
BrewFormula,
|
||||
BrewCask,
|
||||
Lure,
|
||||
Waydroid,
|
||||
AutoCpufreq,
|
||||
CinnamonSpices,
|
||||
Mandb,
|
||||
Pkgfile,
|
||||
]);
|
||||
|
||||
#[cfg(unix)]
|
||||
steps.extend_from_slice(&[
|
||||
Yadm,
|
||||
Nix,
|
||||
NixHelper,
|
||||
Guix,
|
||||
HomeManager,
|
||||
Asdf,
|
||||
Mise,
|
||||
Pkgin,
|
||||
BunPackages,
|
||||
Shell,
|
||||
Tmux,
|
||||
Pearl,
|
||||
#[cfg(not(any(target_os = "macos", target_os = "android")))]
|
||||
GnomeShellExtensions,
|
||||
Pyenv,
|
||||
Sdkman,
|
||||
Rcm,
|
||||
Maza,
|
||||
Hyprpm,
|
||||
Atuin,
|
||||
]);
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "dragonfly"
|
||||
)))]
|
||||
steps.push(Atom);
|
||||
|
||||
// The following update function should be executed on all OSes.
|
||||
steps.extend_from_slice(&[
|
||||
Fossil,
|
||||
Elan,
|
||||
Rye,
|
||||
Rustup,
|
||||
Juliaup,
|
||||
Dotnet,
|
||||
Choosenim,
|
||||
Cargo,
|
||||
Flutter,
|
||||
Go,
|
||||
Emacs,
|
||||
Opam,
|
||||
Vcpkg,
|
||||
Pipx,
|
||||
Pipxu,
|
||||
Vscode,
|
||||
VscodeInsiders,
|
||||
Vscodium,
|
||||
VscodiumInsiders,
|
||||
Conda,
|
||||
Mamba,
|
||||
Pixi,
|
||||
Miktex,
|
||||
Pip3,
|
||||
PipReview,
|
||||
PipReviewLocal,
|
||||
Pipupgrade,
|
||||
Ghcup,
|
||||
Stack,
|
||||
Tldr,
|
||||
Tlmgr,
|
||||
Myrepos,
|
||||
Chezmoi,
|
||||
Jetpack,
|
||||
Vim,
|
||||
Kakoune,
|
||||
Helix,
|
||||
Node,
|
||||
Yarn,
|
||||
Pnpm,
|
||||
VoltaPackages,
|
||||
Containers,
|
||||
Deno,
|
||||
Composer,
|
||||
Krew,
|
||||
Helm,
|
||||
Gem,
|
||||
RubyGems,
|
||||
Julia,
|
||||
Haxelib,
|
||||
Sheldon,
|
||||
Stew,
|
||||
Rtcl,
|
||||
Bin,
|
||||
Gcloud,
|
||||
Micro,
|
||||
Raco,
|
||||
Spicetify,
|
||||
GithubCliExtensions,
|
||||
Bob,
|
||||
Certbot,
|
||||
GitRepos,
|
||||
ClamAvDb,
|
||||
PlatformioCore,
|
||||
Lensfun,
|
||||
Poetry,
|
||||
Uv,
|
||||
Zvm,
|
||||
Aqua,
|
||||
Bun,
|
||||
Zigup,
|
||||
JetbrainsToolbox,
|
||||
AndroidStudio,
|
||||
JetbrainsAqua,
|
||||
JetbrainsClion,
|
||||
JetbrainsDatagrip,
|
||||
JetbrainsDataspell,
|
||||
// JetBrains dotCover has no CLI
|
||||
// JetBrains dotMemory has no CLI
|
||||
// JetBrains dotPeek has no CLI
|
||||
// JetBrains dotTrace has no CLI
|
||||
// JetBrains Fleet has a different CLI without a `fleet update` command.
|
||||
JetbrainsGateway,
|
||||
JetbrainsGoland,
|
||||
JetbrainsIdea,
|
||||
JetbrainsMps,
|
||||
JetbrainsPhpstorm,
|
||||
JetbrainsPycharm,
|
||||
// JetBrains ReSharper has no CLI (it's a VSCode extension)
|
||||
// JetBrains ReSharper C++ has no CLI (it's a VSCode extension)
|
||||
JetbrainsRider,
|
||||
JetbrainsRubymine,
|
||||
JetbrainsRustrover,
|
||||
// JetBrains Space Desktop does not have a CLI
|
||||
JetbrainsWebstorm,
|
||||
Yazi,
|
||||
Falconf,
|
||||
Powershell,
|
||||
CustomCommands,
|
||||
Vagrant,
|
||||
Typst,
|
||||
]);
|
||||
|
||||
steps.shrink_to_fit();
|
||||
|
||||
steps
|
||||
}
|
||||
@@ -1,109 +1,239 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::error::{self, TopgradeError};
|
||||
use crate::executor::CommandExt;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::{execution_context::ExecutionContext, utils::require};
|
||||
use log::{debug, error, warn};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
// A string found in the output of docker for containers that weren't found in
|
||||
// the docker registry. We use this to gracefully handle and skip containers
|
||||
// that cannot be pulled, likely because they don't exist in the registry in
|
||||
// the first place. This happens e.g. when the user tags an image locally
|
||||
// themselves or when using docker-compose.
|
||||
const NONEXISTENT_REPO: &str = "repository does not exist";
|
||||
|
||||
/// Returns a Vector of all containers, with Strings in the format
|
||||
/// "REGISTRY/[PATH/]CONTAINER_NAME:TAG"
|
||||
fn list_containers(crt: &Path) -> Result<Vec<String>> {
|
||||
debug!(
|
||||
"Querying '{} image ls --format \"{{{{.Repository}}}}:{{{{.Tag}}}}\"' for containers",
|
||||
crt.display()
|
||||
);
|
||||
let output = Command::new(crt)
|
||||
.args(["image", "ls", "--format", "{{.Repository}}:{{.Tag}}"])
|
||||
.output()?;
|
||||
let output_str = String::from_utf8(output.stdout)?;
|
||||
|
||||
let mut retval = vec![];
|
||||
for line in output_str.lines() {
|
||||
if line.starts_with("localhost") {
|
||||
// Don't know how to update self-built containers
|
||||
debug!("Skipping self-built container '{}'", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if line.contains("<none>") {
|
||||
// Bogus/dangling container or intermediate layer
|
||||
debug!("Skipping bogus container '{}'", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if line.starts_with("vsc-") {
|
||||
debug!("Skipping visual studio code dev container '{}'", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
debug!("Using container '{}'", line);
|
||||
retval.push(String::from(line));
|
||||
}
|
||||
|
||||
Ok(retval)
|
||||
}
|
||||
|
||||
pub fn run_containers(ctx: &ExecutionContext) -> Result<()> {
|
||||
// Prefer podman, fall back to docker if not present
|
||||
let crt = require("podman").or_else(|_| require("docker"))?;
|
||||
debug!("Using container runtime '{}'", crt.display());
|
||||
|
||||
print_separator("Containers");
|
||||
let mut success = true;
|
||||
let containers = list_containers(&crt)?;
|
||||
debug!("Containers to inspect: {:?}", containers);
|
||||
|
||||
for container in containers.iter() {
|
||||
debug!("Pulling container '{}'", container);
|
||||
let args = vec!["pull", &container[..]];
|
||||
let mut exec = ctx.run_type().execute(&crt);
|
||||
|
||||
if let Err(e) = exec.args(&args).check_run() {
|
||||
error!("Pulling container '{}' failed: {}", container, e);
|
||||
|
||||
// Find out if this is 'skippable'
|
||||
// This is necessary e.g. for docker, because unlike podman docker doesn't tell from
|
||||
// which repository a container originates (such as `docker.io`). This has the
|
||||
// practical consequence that all containers, whether self-built, created by
|
||||
// docker-compose or pulled from the docker hub, look exactly the same to us. We can
|
||||
// only find out what went wrong by manually parsing the output of the command...
|
||||
if match exec.check_output() {
|
||||
Ok(s) => s.contains(NONEXISTENT_REPO),
|
||||
Err(e) => match e.downcast_ref::<TopgradeError>() {
|
||||
Some(TopgradeError::ProcessFailedWithOutput(_, stderr)) => stderr.contains(NONEXISTENT_REPO),
|
||||
_ => false,
|
||||
},
|
||||
} {
|
||||
warn!("Skipping unknown container '{}'", container);
|
||||
continue;
|
||||
}
|
||||
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
// Remove dangling images
|
||||
debug!("Removing dangling images");
|
||||
if let Err(e) = ctx.run_type().execute(&crt).args(["image", "prune", "-f"]).check_run() {
|
||||
error!("Removing dangling images failed: {}", e);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if success {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow::anyhow!(error::StepFailed))
|
||||
}
|
||||
}
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::Result;
|
||||
use color_eyre::eyre::{OptionExt, eyre};
|
||||
use tracing::{debug, error, warn};
|
||||
use wildmatch::WildMatch;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::error::{SkipStep, TopgradeError};
|
||||
use crate::terminal::print_separator;
|
||||
use crate::{execution_context::ExecutionContext, utils::require};
|
||||
use rust_i18n::t;
|
||||
|
||||
// A string found in the output of docker for containers that weren't found in
|
||||
// the docker registry. We use this to gracefully handle and skip containers
|
||||
// that cannot be pulled, likely because they don't exist in the registry in
|
||||
// the first place. This happens e.g. when the user tags an image locally
|
||||
// themselves or when using docker-compose.
|
||||
const NONEXISTENT_REPO: &str = "repository does not exist";
|
||||
|
||||
// A string found in the output of docker when Docker Desktop is not running.
|
||||
const DOCKER_NOT_RUNNING: &str = "We recommend to activate the WSL integration in Docker Desktop settings.";
|
||||
|
||||
/// Uniquely identifies a `Container`.
|
||||
#[derive(Debug)]
|
||||
struct Container {
|
||||
/// `Repository` and `Tag`
|
||||
///
|
||||
/// format: `Repository:Tag`, e.g., `nixos/nix:latest`.
|
||||
repo_tag: String,
|
||||
/// Platform
|
||||
///
|
||||
/// format: `OS/Architecture`, e.g., `linux/amd64`.
|
||||
platform: String,
|
||||
}
|
||||
|
||||
impl Container {
|
||||
/// Construct a new `Container`.
|
||||
fn new(repo_tag: String, platform: String) -> Self {
|
||||
Self { repo_tag, platform }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Container {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
// e.g., "`fedora:latest` for `linux/amd64`"
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
t!(
|
||||
"`{repo_tag}` for `{platform}`",
|
||||
repo_tag = self.repo_tag,
|
||||
platform = self.platform
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Vector of all containers, with Strings in the format
|
||||
/// "REGISTRY/[PATH/]CONTAINER_NAME:TAG"
|
||||
///
|
||||
/// Containers specified in `ignored_containers` will be filtered out.
|
||||
fn list_containers(crt: &Path, ignored_containers: Option<&Vec<String>>) -> Result<Vec<Container>> {
|
||||
let ignored_containers = ignored_containers.map(|patterns| {
|
||||
patterns
|
||||
.iter()
|
||||
.map(|pattern| WildMatch::new(pattern))
|
||||
.collect::<Vec<WildMatch>>()
|
||||
});
|
||||
|
||||
debug!(
|
||||
"Querying '{} image ls --format \"{{{{.Repository}}}}:{{{{.Tag}}}}/{{{{.ID}}}}\"' for containers",
|
||||
crt.display()
|
||||
);
|
||||
let output = Command::new(crt)
|
||||
.args(["image", "ls", "--format", "{{.Repository}}:{{.Tag}} {{.ID}}"])
|
||||
.output_checked_utf8()?;
|
||||
|
||||
let mut retval = vec![];
|
||||
for line in output.stdout.lines() {
|
||||
if line.starts_with("localhost") {
|
||||
// Don't know how to update self-built containers
|
||||
debug!("Skipping self-built container '{}'", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if line.contains("<none>") {
|
||||
// Bogus/dangling container or intermediate layer
|
||||
debug!("Skipping bogus container '{}'", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if line.starts_with("vsc-") {
|
||||
debug!("Skipping visual studio code dev container '{}'", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
debug!("Using container '{}'", line);
|
||||
|
||||
// line is of format: `Repository:Tag ImageID`, e.g., `nixos/nix:latest d80fea9c32b4`
|
||||
let split_res = line.split(' ').collect::<Vec<&str>>();
|
||||
if split_res.len() != 2 {
|
||||
return Err(eyre!(format!(
|
||||
"Got erroneous output from `{} image ls --format \"{{.Repository}}:{{.Tag}} {{.ID}}\"; Expected line to split into 2 parts",
|
||||
crt.display()
|
||||
)));
|
||||
}
|
||||
let (repo_tag, image_id) = (split_res[0], split_res[1]);
|
||||
|
||||
if let Some(ref ignored_containers) = ignored_containers {
|
||||
if ignored_containers.iter().any(|pattern| pattern.matches(repo_tag)) {
|
||||
debug!("Skipping ignored container '{}'", line);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Querying '{} image inspect --format \"{{{{.Os}}}}/{{{{.Architecture}}}}\"' for container {}",
|
||||
crt.display(),
|
||||
image_id
|
||||
);
|
||||
let inspect_output = Command::new(crt)
|
||||
.args(["image", "inspect", image_id, "--format", "{{.Os}}/{{.Architecture}}"])
|
||||
.output_checked_utf8()?;
|
||||
let mut platform = inspect_output.stdout;
|
||||
// truncate the tailing new line character
|
||||
platform.truncate(platform.len() - 1);
|
||||
if !platform.contains('/') {
|
||||
return Err(eyre!(format!(
|
||||
"Got erroneous output from `{} image ls --format \"{{.Repository}}:{{.Tag}} {{.ID}}\"; Expected platform to contain '/'",
|
||||
crt.display()
|
||||
)));
|
||||
}
|
||||
|
||||
retval.push(Container::new(repo_tag.to_string(), platform));
|
||||
}
|
||||
|
||||
Ok(retval)
|
||||
}
|
||||
|
||||
pub fn run_containers(ctx: &ExecutionContext) -> Result<()> {
|
||||
// Check what runtime is specified in the config
|
||||
let container_runtime = ctx.config().containers_runtime().to_string();
|
||||
let crt = require(container_runtime)?;
|
||||
debug!("Using container runtime '{}'", crt.display());
|
||||
|
||||
print_separator(t!("Containers"));
|
||||
|
||||
let output = Command::new(&crt).arg("--help").output_checked_with(|_| Ok(()))?;
|
||||
let status_code = output
|
||||
.status
|
||||
.code()
|
||||
.ok_or_eyre("Couldn't get status code (terminated by signal)")?;
|
||||
let stdout = std::str::from_utf8(&output.stdout).wrap_err("Expected output to be valid UTF-8")?;
|
||||
if stdout.contains(DOCKER_NOT_RUNNING) && status_code == 1 {
|
||||
// Write the output
|
||||
io::stdout().write_all(&output.stdout)?;
|
||||
io::stderr().write_all(&output.stderr)?;
|
||||
// Don't crash, but don't be silent either.
|
||||
// This can happen in other ways than Docker Desktop not running, but even in those cases
|
||||
// we don't want to crash, since the containers step is enabled by default.
|
||||
warn!(
|
||||
"{} seems to be non-functional right now (see above). Is WSL integration enabled for Docker Desktop? Is Docker Desktop running?",
|
||||
crt.display()
|
||||
);
|
||||
return Err(SkipStep(format!(
|
||||
"{} seems to be non-functional right now. Possibly WSL integration is not enabled for Docker Desktop, or Docker Desktop is not running.",
|
||||
crt.display()
|
||||
)).into());
|
||||
} else if !output.status.success() {
|
||||
// Write the output
|
||||
io::stdout().write_all(&output.stdout)?;
|
||||
io::stderr().write_all(&output.stderr)?;
|
||||
// If we saw the message, but the code is not 1 (e.g. 0, or a non-1 failure), crash, as we expect a 1.
|
||||
// If we did not see the message, it's broken in some way we do not understand.
|
||||
return Err(eyre!(
|
||||
"{0} seems to be non-functional (`{0} --help` returned non-zero exit code {1})",
|
||||
crt.display(),
|
||||
status_code,
|
||||
));
|
||||
}
|
||||
|
||||
let containers =
|
||||
list_containers(&crt, ctx.config().containers_ignored_tags()).context("Failed to list Docker containers")?;
|
||||
debug!("Containers to inspect: {:?}", containers);
|
||||
|
||||
for container in &containers {
|
||||
debug!("Pulling container '{}'", container);
|
||||
let mut args = vec!["pull", container.repo_tag.as_str()];
|
||||
if container.platform.as_str() != "/" {
|
||||
args.push("--platform");
|
||||
args.push(container.platform.as_str());
|
||||
}
|
||||
|
||||
let mut exec = ctx.execute(&crt);
|
||||
|
||||
if let Err(e) = exec.args(&args).status_checked() {
|
||||
error!("Pulling container '{}' failed: {}", container, e);
|
||||
|
||||
// Find out if this is 'skippable'
|
||||
// This is necessary e.g. for docker, because unlike podman docker doesn't tell from
|
||||
// which repository a container originates (such as `docker.io`). This has the
|
||||
// practical consequence that all containers, whether self-built, created by
|
||||
// docker-compose or pulled from the docker hub, look exactly the same to us. We can
|
||||
// only find out what went wrong by manually parsing the output of the command...
|
||||
if match exec.output_checked_utf8() {
|
||||
Ok(s) => s.stdout.contains(NONEXISTENT_REPO) || s.stderr.contains(NONEXISTENT_REPO),
|
||||
Err(e) => match e.downcast_ref::<TopgradeError>() {
|
||||
Some(TopgradeError::ProcessFailedWithOutput(_, _, stderr)) => stderr.contains(NONEXISTENT_REPO),
|
||||
_ => false,
|
||||
},
|
||||
} {
|
||||
warn!("Skipping unknown container '{}'", container);
|
||||
continue;
|
||||
}
|
||||
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.config().containers_system_prune() {
|
||||
// Run system prune to clean up unused containers, networks, and build cache
|
||||
ctx.execute(&crt)
|
||||
.args(["system", "prune", "--force"])
|
||||
.status_checked()?
|
||||
// Only run `image prune` if we don't run `system prune`
|
||||
} else if ctx.config().cleanup() {
|
||||
// Remove dangling images
|
||||
debug!("Removing dangling images");
|
||||
ctx.execute(&crt).args(["image", "prune", "-f"]).status_checked()?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
(when (fboundp 'paradox-upgrade-packages)
|
||||
(progn
|
||||
(unless (boundp 'paradox-github-token)
|
||||
(setq paradox-github-token t))
|
||||
(paradox-upgrade-packages)
|
||||
(princ
|
||||
(if (get-buffer "*Paradox Report*")
|
||||
(with-current-buffer "*Paradox Report*" (buffer-string))
|
||||
"\nNothing to upgrade\n"))))
|
||||
(when (featurep 'package)
|
||||
(if (fboundp 'package-upgrade-all)
|
||||
(package-upgrade-all nil)
|
||||
(message "Your Emacs version doesn't support unattended packages upgrade")))
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
#[cfg(any(windows, target_os = "macos"))]
|
||||
#[cfg(windows)]
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use directories::BaseDirs;
|
||||
use color_eyre::eyre::Result;
|
||||
use etcetera::base_strategy::BaseStrategy;
|
||||
use rust_i18n::t;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::step::Step;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::{require, require_option, PathExt};
|
||||
use crate::Step;
|
||||
use crate::utils::{PathExt, require, require_option};
|
||||
|
||||
const EMACS_UPGRADE: &str = include_str!("emacs.el");
|
||||
#[cfg(windows)]
|
||||
@@ -22,20 +24,12 @@ pub struct Emacs {
|
||||
}
|
||||
|
||||
impl Emacs {
|
||||
fn directory_path(base_dirs: &BaseDirs) -> Option<PathBuf> {
|
||||
fn directory_path() -> Option<PathBuf> {
|
||||
#[cfg(unix)]
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "macos")] {
|
||||
let emacs_xdg_dir = env::var("XDG_CONFIG_HOME")
|
||||
.ok()
|
||||
.and_then(|config| PathBuf::from(config).join("emacs").if_exists())
|
||||
.or_else(|| base_dirs.home_dir().join(".config/emacs").if_exists());
|
||||
} else {
|
||||
let emacs_xdg_dir = base_dirs.config_dir().join("emacs").if_exists();
|
||||
}
|
||||
}
|
||||
#[cfg(unix)]
|
||||
return base_dirs.home_dir().join(".emacs.d").if_exists().or(emacs_xdg_dir);
|
||||
return {
|
||||
let emacs_xdg_dir = crate::XDG_DIRS.config_dir().join("emacs").if_exists();
|
||||
crate::HOME_DIR.join(".emacs.d").if_exists().or(emacs_xdg_dir)
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
return env::var("HOME")
|
||||
@@ -46,11 +40,11 @@ impl Emacs {
|
||||
.if_exists()
|
||||
.or_else(|| PathBuf::from(&home).join(".config\\emacs").if_exists())
|
||||
})
|
||||
.or_else(|| base_dirs.data_dir().join(".emacs.d").if_exists());
|
||||
.or_else(|| crate::WINDOWS_DIRS.data_dir().join(".emacs.d").if_exists());
|
||||
}
|
||||
|
||||
pub fn new(base_dirs: &BaseDirs) -> Self {
|
||||
let directory = Emacs::directory_path(base_dirs);
|
||||
pub fn new() -> Self {
|
||||
let directory = Emacs::directory_path();
|
||||
let doom = directory.as_ref().and_then(|d| d.join(DOOM_PATH).if_exists());
|
||||
Self { directory, doom }
|
||||
}
|
||||
@@ -66,14 +60,18 @@ impl Emacs {
|
||||
fn update_doom(doom: &Path, ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator("Doom Emacs");
|
||||
|
||||
let mut command = ctx.run_type().execute(doom);
|
||||
let mut command = ctx.execute(doom);
|
||||
if ctx.config().yes(Step::Emacs) {
|
||||
command.arg("--force");
|
||||
}
|
||||
|
||||
command.args(["upgrade"]);
|
||||
command.arg("upgrade");
|
||||
|
||||
command.check_run()
|
||||
if ctx.config().doom_aot() {
|
||||
command.arg("--aot");
|
||||
}
|
||||
|
||||
command.status_checked()
|
||||
}
|
||||
|
||||
pub fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -81,13 +79,16 @@ impl Emacs {
|
||||
if let Some(doom) = &self.doom {
|
||||
Emacs::update_doom(doom, ctx)?;
|
||||
}
|
||||
let init_file = require_option(self.directory.as_ref(), String::from("Emacs directory does not exist"))?
|
||||
.join("init.el")
|
||||
.require()?;
|
||||
let init_file = require_option(
|
||||
self.directory.as_ref(),
|
||||
t!("Emacs directory does not exist").to_string(),
|
||||
)?
|
||||
.join("init.el")
|
||||
.require()?;
|
||||
|
||||
print_separator("Emacs");
|
||||
|
||||
let mut command = ctx.run_type().execute(emacs);
|
||||
let mut command = ctx.execute(emacs);
|
||||
|
||||
command
|
||||
.args(["--batch", "--debug-init", "-l"])
|
||||
@@ -105,6 +106,6 @@ impl Emacs {
|
||||
#[cfg(not(unix))]
|
||||
command.arg(EMACS_UPGRADE);
|
||||
|
||||
command.check_run()
|
||||
command.status_checked()
|
||||
}
|
||||
}
|
||||
|
||||
1906
src/steps/generic.rs
1906
src/steps/generic.rs
File diff suppressed because it is too large
Load Diff
503
src/steps/git.rs
503
src/steps/git.rs
@@ -3,141 +3,177 @@ use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Output, Stdio};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::{Result, eyre};
|
||||
use console::style;
|
||||
use futures::stream::{iter, FuturesUnordered};
|
||||
use futures::StreamExt;
|
||||
use glob::{glob_with, MatchOptions};
|
||||
use log::{debug, error};
|
||||
use futures::stream::{FuturesUnordered, StreamExt, iter};
|
||||
use glob::{MatchOptions, glob_with};
|
||||
use tokio::process::Command as AsyncCommand;
|
||||
use tokio::runtime;
|
||||
use tracing::{debug, error};
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::executor::{CommandExt, RunType};
|
||||
use crate::step::Step;
|
||||
use crate::steps::emacs::Emacs;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::{which, PathExt};
|
||||
use crate::{error::SkipStep, terminal::print_warning};
|
||||
use crate::utils::{PathExt, require};
|
||||
use crate::{HOME_DIR, error::SkipStep, terminal::print_warning};
|
||||
use etcetera::base_strategy::BaseStrategy;
|
||||
use rust_i18n::t;
|
||||
|
||||
#[cfg(unix)]
|
||||
use crate::XDG_DIRS;
|
||||
|
||||
#[cfg(windows)]
|
||||
use crate::WINDOWS_DIRS;
|
||||
|
||||
pub fn run_git_pull(ctx: &ExecutionContext) -> Result<()> {
|
||||
let mut repos = RepoStep::try_new()?;
|
||||
let config = ctx.config();
|
||||
|
||||
// handle built-in repos
|
||||
if config.use_predefined_git_repos() {
|
||||
// should be executed on all the platforms
|
||||
{
|
||||
if config.should_run(Step::Emacs) {
|
||||
let emacs = Emacs::new();
|
||||
if !emacs.is_doom() {
|
||||
if let Some(directory) = emacs.directory() {
|
||||
repos.insert_if_repo(directory);
|
||||
}
|
||||
}
|
||||
repos.insert_if_repo(HOME_DIR.join(".doom.d"));
|
||||
}
|
||||
|
||||
if config.should_run(Step::Vim) {
|
||||
repos.insert_if_repo(HOME_DIR.join(".vim"));
|
||||
repos.insert_if_repo(HOME_DIR.join(".config/nvim"));
|
||||
}
|
||||
|
||||
repos.insert_if_repo(HOME_DIR.join(".ideavimrc"));
|
||||
repos.insert_if_repo(HOME_DIR.join(".intellimacs"));
|
||||
|
||||
if config.should_run(Step::Rcm) {
|
||||
repos.insert_if_repo(HOME_DIR.join(".dotfiles"));
|
||||
}
|
||||
|
||||
if let Some(powershell) = ctx.powershell() {
|
||||
if let Some(profile) = powershell.profile() {
|
||||
repos.insert_if_repo(profile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
repos.insert_if_repo(crate::steps::zsh::zshrc());
|
||||
if config.should_run(Step::Tmux) {
|
||||
repos.insert_if_repo(HOME_DIR.join(".tmux"));
|
||||
}
|
||||
repos.insert_if_repo(HOME_DIR.join(".config/fish"));
|
||||
repos.insert_if_repo(XDG_DIRS.config_dir().join("openbox"));
|
||||
repos.insert_if_repo(XDG_DIRS.config_dir().join("bspwm"));
|
||||
repos.insert_if_repo(XDG_DIRS.config_dir().join("i3"));
|
||||
repos.insert_if_repo(XDG_DIRS.config_dir().join("sway"));
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
repos.insert_if_repo(
|
||||
WINDOWS_DIRS
|
||||
.cache_dir()
|
||||
.join("Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState"),
|
||||
);
|
||||
|
||||
super::os::windows::insert_startup_scripts(&mut repos).ok();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle user-defined repos
|
||||
if let Some(custom_git_repos) = config.git_repos() {
|
||||
for git_repo in custom_git_repos {
|
||||
repos.glob_insert(&shellexpand::tilde(git_repo));
|
||||
}
|
||||
}
|
||||
|
||||
// Warn the user about the bad patterns.
|
||||
//
|
||||
// NOTE: this should be executed **before** skipping the Git step or the
|
||||
// user won't receive this warning in the cases where all the paths configured
|
||||
// are bad patterns.
|
||||
repos.bad_patterns.iter().for_each(|pattern| {
|
||||
print_warning(t!(
|
||||
"Path {pattern} did not contain any git repositories",
|
||||
pattern = pattern
|
||||
));
|
||||
});
|
||||
|
||||
if repos.is_repos_empty() {
|
||||
return Err(SkipStep(t!("No repositories to pull").to_string()).into());
|
||||
}
|
||||
|
||||
print_separator(t!("Git repositories"));
|
||||
|
||||
repos.pull_repos(ctx)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
static PATH_PREFIX: &str = "\\\\?\\";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Git {
|
||||
git: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub struct Repositories<'a> {
|
||||
git: &'a Git,
|
||||
repositories: HashSet<String>,
|
||||
pub struct RepoStep {
|
||||
git: PathBuf,
|
||||
repos: HashSet<PathBuf>,
|
||||
glob_match_options: MatchOptions,
|
||||
bad_patterns: Vec<String>,
|
||||
}
|
||||
|
||||
fn check_output(output: Output) -> Result<()> {
|
||||
#[track_caller]
|
||||
fn output_checked_utf8(output: Output) -> Result<()> {
|
||||
if !(output.status.success()) {
|
||||
let stderr = String::from_utf8(output.stderr).unwrap();
|
||||
Err(anyhow!(stderr))
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let stderr = stderr.trim();
|
||||
Err(eyre!("{stderr}"))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn pull_repository(repo: String, git: &Path, ctx: &ExecutionContext<'_>) -> Result<()> {
|
||||
let path = repo.to_string();
|
||||
let before_revision = get_head_revision(git, &repo);
|
||||
|
||||
println!("{} {}", style("Pulling").cyan().bold(), path);
|
||||
|
||||
let mut command = AsyncCommand::new(git);
|
||||
|
||||
command
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(&repo)
|
||||
.args(["pull", "--ff-only"]);
|
||||
|
||||
if let Some(extra_arguments) = ctx.config().git_arguments() {
|
||||
command.args(extra_arguments.split_whitespace());
|
||||
}
|
||||
|
||||
let pull_output = command.output().await?;
|
||||
let submodule_output = AsyncCommand::new(git)
|
||||
.args(["submodule", "update", "--recursive"])
|
||||
.current_dir(&repo)
|
||||
.stdin(Stdio::null())
|
||||
.output()
|
||||
.await?;
|
||||
let result = check_output(pull_output).and_then(|_| check_output(submodule_output));
|
||||
|
||||
if let Err(message) = &result {
|
||||
println!("{} pulling {}", style("Failed").red().bold(), &repo);
|
||||
print!("{}", message);
|
||||
} else {
|
||||
let after_revision = get_head_revision(git, &repo);
|
||||
|
||||
match (&before_revision, &after_revision) {
|
||||
(Some(before), Some(after)) if before != after => {
|
||||
println!("{} {}:", style("Changed").yellow().bold(), &repo);
|
||||
|
||||
Command::new(git)
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(&repo)
|
||||
.args([
|
||||
"--no-pager",
|
||||
"log",
|
||||
"--no-decorate",
|
||||
"--oneline",
|
||||
&format!("{}..{}", before, after),
|
||||
])
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap();
|
||||
println!();
|
||||
}
|
||||
_ => {
|
||||
println!("{} {}", style("Up-to-date").green().bold(), &repo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.map(|_| ())
|
||||
}
|
||||
|
||||
fn get_head_revision(git: &Path, repo: &str) -> Option<String> {
|
||||
fn get_head_revision<P: AsRef<Path>>(git: &Path, repo: P) -> Option<String> {
|
||||
Command::new(git)
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(repo)
|
||||
.current_dir(repo.as_ref())
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.check_output()
|
||||
.map(|output| output.trim().to_string())
|
||||
.output_checked_utf8()
|
||||
.map(|output| output.stdout.trim().to_string())
|
||||
.map_err(|e| {
|
||||
error!("Error getting revision for {}: {}", repo, e);
|
||||
error!("Error getting revision for {}: {e}", repo.as_ref().display(),);
|
||||
|
||||
e
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn has_remotes(git: &Path, repo: &str) -> Option<bool> {
|
||||
Command::new(git)
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(repo)
|
||||
.args(["remote", "show"])
|
||||
.check_output()
|
||||
.map(|output| output.lines().count() > 0)
|
||||
.map_err(|e| {
|
||||
error!("Error getting remotes for {}: {}", repo, e);
|
||||
e
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
impl RepoStep {
|
||||
/// Try to create a `RepoStep`, fail if `git` is not found.
|
||||
pub fn try_new() -> Result<Self> {
|
||||
let git = require("git")?;
|
||||
let mut glob_match_options = MatchOptions::new();
|
||||
|
||||
impl Git {
|
||||
pub fn new() -> Self {
|
||||
Self { git: which("git") }
|
||||
if cfg!(windows) {
|
||||
glob_match_options.case_sensitive = false;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
git,
|
||||
repos: HashSet::new(),
|
||||
bad_patterns: Vec::new(),
|
||||
glob_match_options,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_repo_root<P: AsRef<Path>>(&self, path: P) -> Option<String> {
|
||||
/// Try to get the root of the repo specified in `path`.
|
||||
pub fn get_repo_root<P: AsRef<Path>>(&self, path: P) -> Option<PathBuf> {
|
||||
match path.as_ref().canonicalize() {
|
||||
Ok(mut path) => {
|
||||
debug_assert!(path.exists());
|
||||
@@ -161,105 +197,59 @@ impl Git {
|
||||
path_string
|
||||
};
|
||||
|
||||
if let Some(git) = &self.git {
|
||||
let output = Command::new(git)
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(path)
|
||||
.args(["rev-parse", "--show-toplevel"])
|
||||
.check_output()
|
||||
.ok()
|
||||
.map(|output| output.trim().to_string());
|
||||
return output;
|
||||
let output = Command::new(&self.git)
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(path)
|
||||
.args(["rev-parse", "--show-toplevel"])
|
||||
.output_checked_utf8()
|
||||
.ok()
|
||||
// trim the last newline char
|
||||
.map(|output| PathBuf::from(output.stdout.trim()));
|
||||
|
||||
return output;
|
||||
}
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::NotFound {
|
||||
debug!("{} does not exist", path.as_ref().display());
|
||||
} else {
|
||||
error!("Error looking for {}: {e}", path.as_ref().display());
|
||||
}
|
||||
}
|
||||
Err(e) => match e.kind() {
|
||||
io::ErrorKind::NotFound => debug!("{} does not exists", path.as_ref().display()),
|
||||
_ => error!("Error looking for {}: {}", path.as_ref().display(), e),
|
||||
},
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
pub fn multi_pull_step(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
|
||||
if repositories.repositories.is_empty() {
|
||||
return Err(SkipStep(String::from("No repositories to pull")).into());
|
||||
}
|
||||
|
||||
print_separator("Git repositories");
|
||||
repositories
|
||||
.bad_patterns
|
||||
.iter()
|
||||
.for_each(|pattern| print_warning(format!("Path {} did not contain any git repositories", pattern)));
|
||||
self.multi_pull(repositories, ctx)
|
||||
}
|
||||
|
||||
pub fn multi_pull(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
|
||||
let git = self.git.as_ref().unwrap();
|
||||
|
||||
if let RunType::Dry = ctx.run_type() {
|
||||
repositories
|
||||
.repositories
|
||||
.iter()
|
||||
.for_each(|repo| println!("Would pull {}", &repo));
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let futures_iterator = repositories
|
||||
.repositories
|
||||
.iter()
|
||||
.filter(|repo| match has_remotes(git, repo) {
|
||||
Some(false) => {
|
||||
println!(
|
||||
"{} {} because it has no remotes",
|
||||
style("Skipping").yellow().bold(),
|
||||
repo
|
||||
);
|
||||
false
|
||||
}
|
||||
_ => true, // repo has remotes or command to check for remotes has failed. proceed to pull anyway.
|
||||
})
|
||||
.map(|repo| pull_repository(repo.clone(), git, ctx));
|
||||
|
||||
let stream_of_futures = if let Some(limit) = ctx.config().git_concurrency_limit() {
|
||||
iter(futures_iterator).buffer_unordered(limit).boxed()
|
||||
} else {
|
||||
futures_iterator.collect::<FuturesUnordered<_>>().boxed()
|
||||
};
|
||||
|
||||
let basic_rt = runtime::Runtime::new()?;
|
||||
let results = basic_rt.block_on(async { stream_of_futures.collect::<Vec<Result<()>>>().await });
|
||||
|
||||
let error = results.into_iter().find(|r| r.is_err());
|
||||
error.unwrap_or(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Repositories<'a> {
|
||||
pub fn new(git: &'a Git) -> Self {
|
||||
let mut glob_match_options = MatchOptions::new();
|
||||
|
||||
if cfg!(windows) {
|
||||
glob_match_options.case_sensitive = false;
|
||||
}
|
||||
|
||||
Self {
|
||||
git,
|
||||
repositories: HashSet::new(),
|
||||
bad_patterns: Vec::new(),
|
||||
glob_match_options,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if `path` is a git repo, if yes, add it to `self.repos`.
|
||||
///
|
||||
/// Return the check result.
|
||||
pub fn insert_if_repo<P: AsRef<Path>>(&mut self, path: P) -> bool {
|
||||
if let Some(repo) = self.git.get_repo_root(path) {
|
||||
self.repositories.insert(repo);
|
||||
if let Some(repo) = self.get_repo_root(path) {
|
||||
self.repos.insert(repo);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if `repo` has a remote.
|
||||
fn has_remotes<P: AsRef<Path>>(&self, repo: P) -> Option<bool> {
|
||||
let mut cmd = Command::new(&self.git);
|
||||
cmd.stdin(Stdio::null())
|
||||
.current_dir(repo.as_ref())
|
||||
.args(["remote", "show"]);
|
||||
|
||||
let res = cmd.output_checked_utf8();
|
||||
|
||||
res.map(|output| output.stdout.lines().count() > 0)
|
||||
.map_err(|e| {
|
||||
error!("Error getting remotes for {}: {e}", repo.as_ref().display());
|
||||
e
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Similar to `insert_if_repo`, with glob support.
|
||||
pub fn glob_insert(&mut self, pattern: &str) {
|
||||
if let Ok(glob) = glob_with(pattern, self.glob_match_options) {
|
||||
let mut last_git_repo: Option<PathBuf> = None;
|
||||
@@ -269,7 +259,7 @@ impl<'a> Repositories<'a> {
|
||||
if let Some(last_git_repo) = &last_git_repo {
|
||||
if path.is_descendant_of(last_git_repo) {
|
||||
debug!(
|
||||
"Skipping {} because it's a decendant of last known repo {}",
|
||||
"Skipping {} because it's a descendant of last known repo {}",
|
||||
path.display(),
|
||||
last_git_repo.display()
|
||||
);
|
||||
@@ -281,7 +271,7 @@ impl<'a> Repositories<'a> {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error in path {}", e);
|
||||
error!("Error in path {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,18 +280,141 @@ impl<'a> Repositories<'a> {
|
||||
self.bad_patterns.push(String::from(pattern));
|
||||
}
|
||||
} else {
|
||||
error!("Bad glob pattern: {}", pattern);
|
||||
error!("Bad glob pattern: {pattern}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.repositories.is_empty()
|
||||
/// True if `self.repos` is empty.
|
||||
pub fn is_repos_empty(&self) -> bool {
|
||||
self.repos.is_empty()
|
||||
}
|
||||
|
||||
/// Remove `path` from `self.repos`.
|
||||
///
|
||||
// `cfg(unix)` because it is only used in the oh-my-zsh step.
|
||||
#[cfg(unix)]
|
||||
pub fn remove(&mut self, path: &str) {
|
||||
let _removed = self.repositories.remove(path);
|
||||
pub fn remove<P: AsRef<Path>>(&mut self, path: P) {
|
||||
let _removed = self.repos.remove(path.as_ref());
|
||||
debug_assert!(_removed);
|
||||
}
|
||||
|
||||
/// Try to pull a repo.
|
||||
async fn pull_repo<P: AsRef<Path>>(&self, ctx: &ExecutionContext<'_>, repo: P) -> Result<()> {
|
||||
let before_revision = get_head_revision(&self.git, &repo);
|
||||
|
||||
if ctx.config().verbose() {
|
||||
println!("{} {}", style(t!("Pulling")).cyan().bold(), repo.as_ref().display());
|
||||
}
|
||||
|
||||
let mut command = AsyncCommand::new(&self.git);
|
||||
|
||||
command
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(&repo)
|
||||
.args(["pull", "--ff-only"]);
|
||||
|
||||
if let Some(extra_arguments) = ctx.config().git_arguments() {
|
||||
command.args(extra_arguments.split_whitespace());
|
||||
}
|
||||
|
||||
let pull_output = command.output().await?;
|
||||
let submodule_output = AsyncCommand::new(&self.git)
|
||||
.args(["submodule", "update", "--recursive"])
|
||||
.current_dir(&repo)
|
||||
.stdin(Stdio::null())
|
||||
.output()
|
||||
.await?;
|
||||
let result = output_checked_utf8(pull_output)
|
||||
.and_then(|()| output_checked_utf8(submodule_output))
|
||||
.wrap_err_with(|| format!("Failed to pull {}", repo.as_ref().display()));
|
||||
|
||||
if result.is_err() {
|
||||
println!(
|
||||
"{} {} {}",
|
||||
style(t!("Failed")).red().bold(),
|
||||
t!("pulling"),
|
||||
repo.as_ref().display()
|
||||
);
|
||||
} else {
|
||||
let after_revision = get_head_revision(&self.git, repo.as_ref());
|
||||
|
||||
match (&before_revision, &after_revision) {
|
||||
(Some(before), Some(after)) if before != after => {
|
||||
println!("{} {}", style(t!("Changed")).yellow().bold(), repo.as_ref().display());
|
||||
|
||||
Command::new(&self.git)
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(&repo)
|
||||
.args([
|
||||
"--no-pager",
|
||||
"log",
|
||||
"--no-decorate",
|
||||
"--oneline",
|
||||
&format!("{before}..{after}"),
|
||||
])
|
||||
.status_checked()?;
|
||||
println!();
|
||||
}
|
||||
_ => {
|
||||
if ctx.config().verbose() {
|
||||
println!("{} {}", style(t!("Up-to-date")).green().bold(), repo.as_ref().display());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Pull the repositories specified in `self.repos`.
|
||||
///
|
||||
/// # NOTE
|
||||
/// This function will create an async runtime and do the real job so the
|
||||
/// function itself is not async.
|
||||
fn pull_repos(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
if ctx.run_type().dry() {
|
||||
self.repos
|
||||
.iter()
|
||||
.for_each(|repo| println!("{}", t!("Would pull {repo}", repo = repo.display())));
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !ctx.config().verbose() {
|
||||
println!(
|
||||
"\n{} {}\n",
|
||||
style(t!("Only")).green().bold(),
|
||||
t!("updated repositories will be shown...")
|
||||
);
|
||||
}
|
||||
|
||||
let futures_iterator = self
|
||||
.repos
|
||||
.iter()
|
||||
.filter(|repo| match self.has_remotes(repo) {
|
||||
Some(false) => {
|
||||
println!(
|
||||
"{} {} {}",
|
||||
style(t!("Skipping")).yellow().bold(),
|
||||
repo.display(),
|
||||
t!("because it has no remotes")
|
||||
);
|
||||
false
|
||||
}
|
||||
_ => true, // repo has remotes or command to check for remotes has failed. proceed to pull anyway.
|
||||
})
|
||||
.map(|repo| self.pull_repo(ctx, repo));
|
||||
|
||||
let stream_of_futures = if let Some(limit) = ctx.config().git_concurrency_limit() {
|
||||
iter(futures_iterator).buffer_unordered(limit).boxed()
|
||||
} else {
|
||||
futures_iterator.collect::<FuturesUnordered<_>>().boxed()
|
||||
};
|
||||
|
||||
let basic_rt = runtime::Runtime::new()?;
|
||||
let results = basic_rt.block_on(async { stream_of_futures.collect::<Vec<Result<()>>>().await });
|
||||
|
||||
let error = results.into_iter().find(std::result::Result::is_err);
|
||||
error.unwrap_or(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
45
src/steps/go.rs
Normal file
45
src/steps/go.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils;
|
||||
use crate::utils::PathExt;
|
||||
|
||||
/// <https://github.com/Gelio/go-global-update>
|
||||
pub fn run_go_global_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let go_global_update = require_go_bin("go-global-update")?;
|
||||
|
||||
print_separator("go-global-update");
|
||||
|
||||
ctx.execute(go_global_update).status_checked()
|
||||
}
|
||||
|
||||
/// <https://github.com/nao1215/gup>
|
||||
pub fn run_go_gup(ctx: &ExecutionContext) -> Result<()> {
|
||||
let gup = require_go_bin("gup")?;
|
||||
|
||||
print_separator("gup");
|
||||
|
||||
ctx.execute(gup).arg("update").status_checked()
|
||||
}
|
||||
|
||||
/// Get the path of a Go binary.
|
||||
fn require_go_bin(name: &str) -> Result<PathBuf> {
|
||||
utils::require(name).or_else(|_| {
|
||||
let go = utils::require("go")?;
|
||||
// TODO: Does this work? `go help gopath` says that:
|
||||
// > The GOPATH environment variable lists places to look for Go code.
|
||||
// > On Unix, the value is a colon-separated string.
|
||||
// > On Windows, the value is a semicolon-separated string.
|
||||
// > On Plan 9, the value is a list.
|
||||
// Should we also fallback to the env variable?
|
||||
let gopath_output = Command::new(go).args(["env", "GOPATH"]).output_checked_utf8()?;
|
||||
let gopath = gopath_output.stdout.trim();
|
||||
|
||||
PathBuf::from(gopath).join("bin").join(name).require()
|
||||
})
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
use crate::error::TopgradeError;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::require;
|
||||
use anyhow::Result;
|
||||
use color_eyre::eyre::Result;
|
||||
use rust_i18n::t;
|
||||
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::executor::ExecutorOutput;
|
||||
|
||||
const UPGRADE_KAK: &str = include_str!("upgrade.kak");
|
||||
|
||||
@@ -13,19 +12,10 @@ pub fn upgrade_kak_plug(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator("Kakoune");
|
||||
|
||||
let mut command = ctx.run_type().execute(kak);
|
||||
command.args(["-ui", "dummy", "-e", UPGRADE_KAK]);
|
||||
// TODO: Why suppress output for this command?
|
||||
ctx.execute(kak).args(["-ui", "dummy", "-e", UPGRADE_KAK]).output()?;
|
||||
|
||||
let output = command.output()?;
|
||||
|
||||
if let ExecutorOutput::Wet(output) = output {
|
||||
let status = output.status;
|
||||
if !status.success() {
|
||||
return Err(TopgradeError::ProcessFailed(status).into());
|
||||
} else {
|
||||
println!("Plugins upgraded")
|
||||
}
|
||||
}
|
||||
println!("{}", t!("Plugins upgraded"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ pub mod containers;
|
||||
pub mod emacs;
|
||||
pub mod generic;
|
||||
pub mod git;
|
||||
pub mod go;
|
||||
pub mod kakoune;
|
||||
pub mod node;
|
||||
pub mod os;
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use std::fmt::Display;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::prelude::MetadataExt;
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
use anyhow::Result;
|
||||
use directories::BaseDirs;
|
||||
use log::debug;
|
||||
#[cfg(unix)]
|
||||
use crate::HOME_DIR;
|
||||
use color_eyre::eyre::Result;
|
||||
#[cfg(target_os = "linux")]
|
||||
use nix::unistd::Uid;
|
||||
use rust_i18n::t;
|
||||
use semver::Version;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::executor::{CommandExt, RunType};
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::{require, PathExt};
|
||||
use crate::command::CommandExt;
|
||||
use crate::terminal::{print_info, print_separator};
|
||||
use crate::utils::{PathExt, require};
|
||||
use crate::{error::SkipStep, execution_context::ExecutionContext};
|
||||
|
||||
enum NPMVariant {
|
||||
@@ -24,13 +23,6 @@ enum NPMVariant {
|
||||
}
|
||||
|
||||
impl NPMVariant {
|
||||
const fn long_name(&self) -> &str {
|
||||
match self {
|
||||
NPMVariant::Npm => "Node Package Manager",
|
||||
NPMVariant::Pnpm => "PNPM",
|
||||
}
|
||||
}
|
||||
|
||||
const fn short_name(&self) -> &str {
|
||||
match self {
|
||||
NPMVariant::Npm => "npm",
|
||||
@@ -73,11 +65,7 @@ impl NPM {
|
||||
/// If the “NPM” version is larger than 8.11.0, we use
|
||||
/// `--location=global`; otherwise, use `-g`.
|
||||
fn global_location_arg(&self) -> &str {
|
||||
if self.is_npm_8() {
|
||||
"--location=global"
|
||||
} else {
|
||||
"-g"
|
||||
}
|
||||
if self.is_npm_8() { "--location=global" } else { "-g" }
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -85,25 +73,25 @@ impl NPM {
|
||||
let args = ["root", self.global_location_arg()];
|
||||
Command::new(&self.command)
|
||||
.args(args)
|
||||
.check_output()
|
||||
.map(|s| PathBuf::from(s.trim()))
|
||||
.output_checked_utf8()
|
||||
.map(|s| PathBuf::from(s.stdout.trim()))
|
||||
}
|
||||
|
||||
fn version(&self) -> Result<Version> {
|
||||
let version_str = Command::new(&self.command)
|
||||
.args(["--version"])
|
||||
.check_output()
|
||||
.map(|s| s.trim().to_owned());
|
||||
Version::parse(&version_str?).map_err(|err| err.into())
|
||||
.output_checked_utf8()
|
||||
.map(|s| s.stdout.trim().to_owned());
|
||||
Version::parse(&version_str?).map_err(std::convert::Into::into)
|
||||
}
|
||||
|
||||
fn upgrade(&self, run_type: RunType, use_sudo: bool) -> Result<()> {
|
||||
print_separator(self.variant.long_name());
|
||||
fn upgrade(&self, ctx: &ExecutionContext, use_sudo: bool) -> Result<()> {
|
||||
let args = ["update", self.global_location_arg()];
|
||||
if use_sudo {
|
||||
run_type.execute("sudo").args(args).check_run()?;
|
||||
let sudo = ctx.require_sudo()?;
|
||||
sudo.execute(ctx, &self.command)?.args(args).status_checked()?;
|
||||
} else {
|
||||
run_type.execute(&self.command).args(args).check_run()?;
|
||||
ctx.execute(&self.command).args(args).status_checked()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -125,15 +113,11 @@ impl NPM {
|
||||
|
||||
struct Yarn {
|
||||
command: PathBuf,
|
||||
yarn: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Yarn {
|
||||
fn new(command: PathBuf) -> Self {
|
||||
Self {
|
||||
command,
|
||||
yarn: require("yarn").ok(),
|
||||
}
|
||||
Self { command }
|
||||
}
|
||||
|
||||
fn has_global_subcmd(&self) -> bool {
|
||||
@@ -142,9 +126,9 @@ impl Yarn {
|
||||
//
|
||||
// As “yarn dlx” don't need to “upgrade”, we
|
||||
// ignore the whole task if Yarn is 2.x or above.
|
||||
let version = Command::new(&self.command).args(["--version"]).check_output();
|
||||
let version = Command::new(&self.command).args(["--version"]).output_checked_utf8();
|
||||
|
||||
matches!(version, Ok(ver) if ver.starts_with('1') || ver.starts_with('0'))
|
||||
matches!(version, Ok(ver) if ver.stdout.starts_with('1') || ver.stdout.starts_with('0'))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -152,22 +136,18 @@ impl Yarn {
|
||||
let args = ["global", "dir"];
|
||||
Command::new(&self.command)
|
||||
.args(args)
|
||||
.check_output()
|
||||
.map(|s| PathBuf::from(s.trim()))
|
||||
.output_checked_utf8()
|
||||
.map(|s| PathBuf::from(s.stdout.trim()))
|
||||
}
|
||||
|
||||
fn upgrade(&self, run_type: RunType, use_sudo: bool) -> Result<()> {
|
||||
print_separator("Yarn Package Manager");
|
||||
fn upgrade(&self, ctx: &ExecutionContext, use_sudo: bool) -> Result<()> {
|
||||
let args = ["global", "upgrade"];
|
||||
|
||||
if use_sudo {
|
||||
run_type
|
||||
.execute("sudo")
|
||||
.arg(self.yarn.as_ref().unwrap_or(&self.command))
|
||||
.args(args)
|
||||
.check_run()?;
|
||||
let sudo = ctx.require_sudo()?;
|
||||
sudo.execute(ctx, &self.command)?.args(args).status_checked()?;
|
||||
} else {
|
||||
run_type.execute(&self.command).args(args).check_run()?;
|
||||
ctx.execute(&self.command).args(args).status_checked()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -187,6 +167,88 @@ impl Yarn {
|
||||
}
|
||||
}
|
||||
|
||||
struct Deno {
|
||||
command: PathBuf,
|
||||
}
|
||||
|
||||
impl Deno {
|
||||
fn new(command: PathBuf) -> Self {
|
||||
Self { command }
|
||||
}
|
||||
|
||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
let mut args = vec![];
|
||||
|
||||
let version = ctx.config().deno_version();
|
||||
if let Some(version) = version {
|
||||
let bin_version = self.version()?;
|
||||
|
||||
if bin_version >= Version::new(2, 0, 0) {
|
||||
args.push(version);
|
||||
} else if bin_version >= Version::new(1, 6, 0) {
|
||||
match version {
|
||||
"stable" => { /* do nothing, as stable is the default channel to upgrade */ }
|
||||
"rc" => {
|
||||
return Err(SkipStep(
|
||||
"Deno (1.6.0-2.0.0) cannot be upgraded to a release candidate".to_string(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
"canary" => args.push("--canary"),
|
||||
_ => {
|
||||
if Version::parse(version).is_err() {
|
||||
return Err(SkipStep("Invalid Deno version".to_string()).into());
|
||||
}
|
||||
|
||||
args.push("--version");
|
||||
args.push(version);
|
||||
}
|
||||
}
|
||||
} else if bin_version >= Version::new(1, 0, 0) {
|
||||
match version {
|
||||
"stable" | "rc" | "canary" => {
|
||||
// Prior to v1.6.0, `deno upgrade` is not able fetch the latest tag version.
|
||||
return Err(
|
||||
SkipStep("Deno (1.0.0-1.6.0) cannot be upgraded to a named channel".to_string()).into(),
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
if Version::parse(version).is_err() {
|
||||
return Err(SkipStep("Invalid Deno version".to_string()).into());
|
||||
}
|
||||
|
||||
args.push("--version");
|
||||
args.push(version);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// v0.x cannot be upgraded with `deno upgrade` to v1.x or v2.x
|
||||
// nor can be upgraded to a specific version.
|
||||
return Err(SkipStep("Unsupported Deno version".to_string()).into());
|
||||
}
|
||||
}
|
||||
|
||||
ctx.execute(&self.command).arg("upgrade").args(args).status_checked()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the version of Deno.
|
||||
///
|
||||
/// This function will return the version of Deno installed on the system.
|
||||
/// The version is parsed from the output of `deno -V`.
|
||||
///
|
||||
/// ```sh
|
||||
/// deno -V # deno 1.6.0
|
||||
/// ```
|
||||
fn version(&self) -> Result<Version> {
|
||||
let version_str = Command::new(&self.command)
|
||||
.args(["-V"])
|
||||
.output_checked_utf8()
|
||||
.map(|s| s.stdout.trim().to_owned().split_off(5)); // remove "deno " prefix
|
||||
Version::parse(&version_str?).map_err(std::convert::Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn should_use_sudo(npm: &NPM, ctx: &ExecutionContext) -> Result<bool> {
|
||||
if npm.should_use_sudo()? {
|
||||
@@ -218,28 +280,32 @@ fn should_use_sudo_yarn(yarn: &Yarn, ctx: &ExecutionContext) -> Result<bool> {
|
||||
pub fn run_npm_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
let npm = require("npm").map(|b| NPM::new(b, NPMVariant::Npm))?;
|
||||
|
||||
print_separator(t!("Node Package Manager"));
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
npm.upgrade(ctx.run_type(), should_use_sudo(&npm, ctx)?)
|
||||
npm.upgrade(ctx, should_use_sudo(&npm, ctx)?)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
npm.upgrade(ctx.run_type(), false)
|
||||
npm.upgrade(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_pnpm_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
let pnpm = require("pnpm").map(|b| NPM::new(b, NPMVariant::Pnpm))?;
|
||||
|
||||
print_separator(t!("Performant Node Package Manager"));
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
pnpm.upgrade(ctx.run_type(), should_use_sudo(&pnpm, ctx)?)
|
||||
pnpm.upgrade(ctx, should_use_sudo(&pnpm, ctx)?)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
pnpm.upgrade(ctx.run_type(), false)
|
||||
pnpm.upgrade(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,26 +317,69 @@ pub fn run_yarn_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
print_separator(t!("Yarn Package Manager"));
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
yarn.upgrade(ctx.run_type(), should_use_sudo_yarn(&yarn, ctx)?)
|
||||
yarn.upgrade(ctx, should_use_sudo_yarn(&yarn, ctx)?)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
yarn.upgrade(ctx.run_type(), false)
|
||||
yarn.upgrade(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deno_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
let deno = require("deno")?;
|
||||
let deno_dir = ctx.base_dirs().home_dir().join(".deno");
|
||||
let deno = require("deno").map(Deno::new)?;
|
||||
let deno_dir = HOME_DIR.join(".deno");
|
||||
|
||||
if !deno.canonicalize()?.is_descendant_of(&deno_dir) {
|
||||
let skip_reason = SkipStep("Deno installed outside of .deno directory".to_string());
|
||||
if !deno.command.canonicalize()?.is_descendant_of(&deno_dir) {
|
||||
let skip_reason = SkipStep(t!("Deno installed outside of .deno directory").to_string());
|
||||
return Err(skip_reason.into());
|
||||
}
|
||||
|
||||
print_separator("Deno");
|
||||
ctx.run_type().execute(&deno).arg("upgrade").check_run()
|
||||
deno.upgrade(ctx)
|
||||
}
|
||||
|
||||
/// There is no `volta upgrade` command, so we need to upgrade each package
|
||||
pub fn run_volta_packages_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
let volta = require("volta")?;
|
||||
|
||||
print_separator("Volta");
|
||||
|
||||
if ctx.run_type().dry() {
|
||||
print_info(t!("Updating Volta packages..."));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let list_output = ctx
|
||||
.execute(&volta)
|
||||
.args(["list", "--format=plain"])
|
||||
.output_checked_utf8()?
|
||||
.stdout;
|
||||
|
||||
let installed_packages: Vec<&str> = list_output
|
||||
.lines()
|
||||
.filter_map(|line| {
|
||||
// format is 'kind package@version ...'
|
||||
let mut parts = line.split_whitespace();
|
||||
parts.next();
|
||||
let package_part = parts.next()?;
|
||||
let version_index = package_part.rfind('@').unwrap_or(package_part.len());
|
||||
Some(package_part[..version_index].trim())
|
||||
})
|
||||
.collect();
|
||||
|
||||
if installed_packages.is_empty() {
|
||||
print_info(t!("No packages installed with Volta"));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for package in &installed_packages {
|
||||
ctx.execute(&volta).args(["install", package]).status_checked()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::step::Step;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::require;
|
||||
use crate::Step;
|
||||
use anyhow::Result;
|
||||
use crate::utils::which;
|
||||
use color_eyre::Result;
|
||||
|
||||
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
//let pkg = require("pkg")?;
|
||||
@@ -10,28 +12,26 @@ pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator("Termux Packages");
|
||||
|
||||
let is_nala = pkg.end_with("nala");
|
||||
let is_nala = pkg.ends_with("nala");
|
||||
|
||||
let mut command = ctx.run_type().execute(&pkg);
|
||||
let mut command = ctx.execute(&pkg);
|
||||
command.arg("upgrade");
|
||||
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("-y");
|
||||
}
|
||||
command.check_run()?;
|
||||
command.status_checked()?;
|
||||
|
||||
if !is_nala {
|
||||
if ctx.config().cleanup() {
|
||||
ctx.run_type().execute(&pkg).arg("clean").check_run()?;
|
||||
if !is_nala && ctx.config().cleanup() {
|
||||
ctx.execute(&pkg).arg("clean").status_checked()?;
|
||||
|
||||
let apt = require("apt")?;
|
||||
let mut command = ctx.run_type().execute(&apt);
|
||||
command.arg("autoremove");
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("-y");
|
||||
}
|
||||
command.check_run()?;
|
||||
let apt = require("apt")?;
|
||||
let mut command = ctx.execute(apt);
|
||||
command.arg("autoremove");
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("-y");
|
||||
}
|
||||
command.status_checked()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
use std::env::var_os;
|
||||
use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use anyhow::Result;
|
||||
use color_eyre::eyre;
|
||||
use color_eyre::eyre::{Context, Result};
|
||||
use rust_i18n::t;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::error::TopgradeError;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::step::Step;
|
||||
use crate::utils::which;
|
||||
use crate::{config, Step};
|
||||
use crate::{config, output_changed_message};
|
||||
|
||||
fn get_execution_path() -> OsString {
|
||||
let mut path = OsString::from("/usr/bin:");
|
||||
@@ -29,14 +32,12 @@ pub struct YayParu {
|
||||
impl ArchPackageManager for YayParu {
|
||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
if ctx.config().show_arch_news() {
|
||||
Command::new(&self.executable)
|
||||
ctx.execute(&self.executable)
|
||||
.arg("-Pw")
|
||||
.spawn()
|
||||
.and_then(|mut p| p.wait())
|
||||
.ok();
|
||||
.status_checked_with_codes(&[1, 0])?;
|
||||
}
|
||||
|
||||
let mut command = ctx.run_type().execute(&self.executable);
|
||||
let mut command = ctx.execute(&self.executable);
|
||||
|
||||
command
|
||||
.arg("--pacman")
|
||||
@@ -48,15 +49,15 @@ impl ArchPackageManager for YayParu {
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("--noconfirm");
|
||||
}
|
||||
command.check_run()?;
|
||||
command.status_checked()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
let mut command = ctx.run_type().execute(&self.executable);
|
||||
let mut command = ctx.execute(&self.executable);
|
||||
command.arg("--pacman").arg(&self.pacman).arg("-Scc");
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("--noconfirm");
|
||||
}
|
||||
command.check_run()?;
|
||||
command.status_checked()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -72,13 +73,44 @@ impl YayParu {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GarudaUpdate {
|
||||
executable: PathBuf,
|
||||
}
|
||||
|
||||
impl ArchPackageManager for GarudaUpdate {
|
||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
let mut command = ctx.execute(&self.executable);
|
||||
|
||||
command
|
||||
.env("PATH", get_execution_path())
|
||||
.env("UPDATE_AUR", "1")
|
||||
.env("SKIP_MIRRORLIST", "1");
|
||||
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.env("PACMAN_NOCONFIRM", "1");
|
||||
}
|
||||
command.args(ctx.config().garuda_update_arguments().split_whitespace());
|
||||
command.status_checked()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl GarudaUpdate {
|
||||
fn get() -> Option<Self> {
|
||||
Some(Self {
|
||||
executable: which("garuda-update")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Trizen {
|
||||
executable: PathBuf,
|
||||
}
|
||||
|
||||
impl ArchPackageManager for Trizen {
|
||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
let mut command = ctx.run_type().execute(&self.executable);
|
||||
let mut command = ctx.execute(&self.executable);
|
||||
|
||||
command
|
||||
.arg("-Syu")
|
||||
@@ -88,15 +120,15 @@ impl ArchPackageManager for Trizen {
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("--noconfirm");
|
||||
}
|
||||
command.check_run()?;
|
||||
command.status_checked()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
let mut command = ctx.run_type().execute(&self.executable);
|
||||
let mut command = ctx.execute(&self.executable);
|
||||
command.arg("-Sc");
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("--noconfirm");
|
||||
}
|
||||
command.check_run()?;
|
||||
command.status_checked()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -112,29 +144,26 @@ impl Trizen {
|
||||
}
|
||||
|
||||
pub struct Pacman {
|
||||
sudo: PathBuf,
|
||||
executable: PathBuf,
|
||||
}
|
||||
|
||||
impl ArchPackageManager for Pacman {
|
||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
let mut command = ctx.run_type().execute(&self.sudo);
|
||||
command
|
||||
.arg(&self.executable)
|
||||
.arg("-Syu")
|
||||
.env("PATH", get_execution_path());
|
||||
let sudo = ctx.require_sudo()?;
|
||||
let mut command = sudo.execute(ctx, &self.executable)?;
|
||||
command.arg("-Syu").env("PATH", get_execution_path());
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("--noconfirm");
|
||||
}
|
||||
command.check_run()?;
|
||||
command.status_checked()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
let mut command = ctx.run_type().execute(&self.sudo);
|
||||
command.arg(&self.executable).arg("-Scc");
|
||||
let mut command = sudo.execute(ctx, &self.executable)?;
|
||||
command.arg("-Scc");
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("--noconfirm");
|
||||
}
|
||||
command.check_run()?;
|
||||
command.status_checked()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -142,10 +171,9 @@ impl ArchPackageManager for Pacman {
|
||||
}
|
||||
|
||||
impl Pacman {
|
||||
pub fn get(ctx: &ExecutionContext) -> Option<Self> {
|
||||
pub fn get() -> Option<Self> {
|
||||
Some(Self {
|
||||
executable: which("powerpill").unwrap_or_else(|| PathBuf::from("pacman")),
|
||||
sudo: ctx.sudo().to_owned()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -164,7 +192,7 @@ impl Pikaur {
|
||||
|
||||
impl ArchPackageManager for Pikaur {
|
||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
let mut command = ctx.run_type().execute(&self.executable);
|
||||
let mut command = ctx.execute(&self.executable);
|
||||
|
||||
command
|
||||
.arg("-Syu")
|
||||
@@ -175,15 +203,15 @@ impl ArchPackageManager for Pikaur {
|
||||
command.arg("--noconfirm");
|
||||
}
|
||||
|
||||
command.check_run()?;
|
||||
command.status_checked()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
let mut command = ctx.run_type().execute(&self.executable);
|
||||
let mut command = ctx.execute(&self.executable);
|
||||
command.arg("-Sc");
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("--noconfirm");
|
||||
}
|
||||
command.check_run()?;
|
||||
command.status_checked()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -203,7 +231,7 @@ impl Pamac {
|
||||
}
|
||||
impl ArchPackageManager for Pamac {
|
||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
let mut command = ctx.run_type().execute(&self.executable);
|
||||
let mut command = ctx.execute(&self.executable);
|
||||
|
||||
command
|
||||
.arg("upgrade")
|
||||
@@ -214,15 +242,15 @@ impl ArchPackageManager for Pamac {
|
||||
command.arg("--no-confirm");
|
||||
}
|
||||
|
||||
command.check_run()?;
|
||||
command.status_checked()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
let mut command = ctx.run_type().execute(&self.executable);
|
||||
let mut command = ctx.execute(&self.executable);
|
||||
command.arg("clean");
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("--no-confirm");
|
||||
}
|
||||
command.check_run()?;
|
||||
command.status_checked()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -231,46 +259,67 @@ impl ArchPackageManager for Pamac {
|
||||
|
||||
pub struct Aura {
|
||||
executable: PathBuf,
|
||||
sudo: PathBuf,
|
||||
}
|
||||
|
||||
impl Aura {
|
||||
fn get(ctx: &ExecutionContext) -> Option<Self> {
|
||||
fn get() -> Option<Self> {
|
||||
Some(Self {
|
||||
executable: which("aura")?,
|
||||
sudo: ctx.sudo().to_owned()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchPackageManager for Aura {
|
||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = which("sudo").unwrap_or_else(PathBuf::new);
|
||||
let mut aur_update = ctx.run_type().execute(&sudo);
|
||||
use semver::Version;
|
||||
|
||||
if sudo.ends_with("sudo") {
|
||||
aur_update
|
||||
.arg(&self.executable)
|
||||
.arg("-Au")
|
||||
let version_cmd_output = ctx.execute(&self.executable).arg("--version").output_checked_utf8()?;
|
||||
// Output will be something like: "aura x.x.x\n"
|
||||
let version_cmd_stdout = version_cmd_output.stdout;
|
||||
let version_str = version_cmd_stdout.trim_start_matches("aura ").trim_end();
|
||||
let version = Version::parse(version_str)
|
||||
.wrap_err_with(|| output_changed_message!("aura --version", "invalid version"))?;
|
||||
|
||||
// Aura, since version 4.0.6, no longer needs sudo.
|
||||
//
|
||||
// https://github.com/fosskers/aura/releases/tag/v4.0.6
|
||||
let version_no_sudo = Version::new(4, 0, 6);
|
||||
|
||||
if version >= version_no_sudo {
|
||||
let mut cmd = ctx.execute(&self.executable);
|
||||
cmd.arg("-Au")
|
||||
.args(ctx.config().aura_aur_arguments().split_whitespace());
|
||||
if ctx.config().yes(Step::System) {
|
||||
aur_update.arg("--noconfirm");
|
||||
cmd.arg("--noconfirm");
|
||||
}
|
||||
cmd.status_checked()?;
|
||||
|
||||
aur_update.check_run()?;
|
||||
let mut cmd = ctx.execute(&self.executable);
|
||||
cmd.arg("-Syu")
|
||||
.args(ctx.config().aura_pacman_arguments().split_whitespace());
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("--noconfirm");
|
||||
}
|
||||
cmd.status_checked()?;
|
||||
} else {
|
||||
println!("Aura requires sudo installed to work with AUR packages")
|
||||
}
|
||||
let sudo = ctx.require_sudo()?;
|
||||
|
||||
let mut pacman_update = ctx.run_type().execute(&self.sudo);
|
||||
pacman_update
|
||||
.arg(&self.executable)
|
||||
.arg("-Syu")
|
||||
.args(ctx.config().aura_pacman_arguments().split_whitespace());
|
||||
if ctx.config().yes(Step::System) {
|
||||
pacman_update.arg("--noconfirm");
|
||||
let mut cmd = sudo.execute(ctx, &self.executable)?;
|
||||
cmd.arg("-Au")
|
||||
.args(ctx.config().aura_aur_arguments().split_whitespace());
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("--noconfirm");
|
||||
}
|
||||
cmd.status_checked()?;
|
||||
|
||||
let mut cmd = sudo.execute(ctx, &self.executable)?;
|
||||
cmd.arg("-Syu")
|
||||
.args(ctx.config().aura_pacman_arguments().split_whitespace());
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("--noconfirm");
|
||||
}
|
||||
cmd.status_checked()?;
|
||||
}
|
||||
pacman_update.check_run()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -284,27 +333,29 @@ pub fn get_arch_package_manager(ctx: &ExecutionContext) -> Option<Box<dyn ArchPa
|
||||
let pacman = which("powerpill").unwrap_or_else(|| PathBuf::from("pacman"));
|
||||
|
||||
match ctx.config().arch_package_manager() {
|
||||
config::ArchPackageManager::Autodetect => YayParu::get("paru", &pacman)
|
||||
config::ArchPackageManager::Autodetect => GarudaUpdate::get()
|
||||
.map(box_package_manager)
|
||||
.or_else(|| YayParu::get("paru", &pacman).map(box_package_manager))
|
||||
.or_else(|| YayParu::get("yay", &pacman).map(box_package_manager))
|
||||
.or_else(|| Trizen::get().map(box_package_manager))
|
||||
.or_else(|| Pikaur::get().map(box_package_manager))
|
||||
.or_else(|| Pamac::get().map(box_package_manager))
|
||||
.or_else(|| Pacman::get(ctx).map(box_package_manager))
|
||||
.or_else(|| Aura::get(ctx).map(box_package_manager)),
|
||||
.or_else(|| Pacman::get().map(box_package_manager))
|
||||
.or_else(|| Aura::get().map(box_package_manager)),
|
||||
config::ArchPackageManager::GarudaUpdate => GarudaUpdate::get().map(box_package_manager),
|
||||
config::ArchPackageManager::Trizen => Trizen::get().map(box_package_manager),
|
||||
config::ArchPackageManager::Paru => YayParu::get("paru", &pacman).map(box_package_manager),
|
||||
config::ArchPackageManager::Yay => YayParu::get("yay", &pacman).map(box_package_manager),
|
||||
config::ArchPackageManager::Pacman => Pacman::get(ctx).map(box_package_manager),
|
||||
config::ArchPackageManager::Pacman => Pacman::get().map(box_package_manager),
|
||||
config::ArchPackageManager::Pikaur => Pikaur::get().map(box_package_manager),
|
||||
config::ArchPackageManager::Pamac => Pamac::get().map(box_package_manager),
|
||||
config::ArchPackageManager::Aura => Aura::get(ctx).map(box_package_manager),
|
||||
config::ArchPackageManager::Aura => Aura::get().map(box_package_manager),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn upgrade_arch_linux(ctx: &ExecutionContext) -> Result<()> {
|
||||
let package_manager =
|
||||
get_arch_package_manager(ctx).ok_or_else(|| anyhow::Error::from(TopgradeError::FailedGettingPackageManager))?;
|
||||
get_arch_package_manager(ctx).ok_or_else(|| eyre::Report::from(TopgradeError::FailedGettingPackageManager))?;
|
||||
package_manager.upgrade(ctx)
|
||||
}
|
||||
|
||||
@@ -321,7 +372,7 @@ pub fn show_pacnew() {
|
||||
.peekable();
|
||||
|
||||
if iter.peek().is_some() {
|
||||
println!("\nPacman backup configuration files found:");
|
||||
println!("\n{}", t!("Pacman backup configuration files found:"));
|
||||
|
||||
for entry in iter {
|
||||
println!("{}", entry.path().display());
|
||||
|
||||
@@ -1,26 +1,35 @@
|
||||
use crate::executor::RunType;
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::step::Step;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::require_option;
|
||||
use anyhow::Result;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use color_eyre::eyre::Result;
|
||||
use rust_i18n::t;
|
||||
|
||||
pub fn upgrade_packages(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> {
|
||||
let sudo = require_option(sudo, String::from("No sudo detected"))?;
|
||||
print_separator("DrgaonFly BSD Packages");
|
||||
run_type
|
||||
.execute(sudo)
|
||||
.args(&["/usr/local/sbin/pkg", "upgrade"])
|
||||
.check_run()
|
||||
}
|
||||
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator(t!("DragonFly BSD Packages"));
|
||||
|
||||
pub fn audit_packages(sudo: &Option<PathBuf>) -> Result<()> {
|
||||
if let Some(sudo) = sudo {
|
||||
println!();
|
||||
Command::new(sudo)
|
||||
.args(&["/usr/local/sbin/pkg", "audit", "-Fr"])
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
let sudo = ctx.require_sudo()?;
|
||||
let mut cmd = sudo.execute(ctx, "/usr/local/sbin/pkg")?;
|
||||
cmd.arg("upgrade");
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("-y");
|
||||
}
|
||||
Ok(())
|
||||
cmd.status_checked()
|
||||
}
|
||||
|
||||
pub fn audit_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator(t!("DragonFly BSD Audit"));
|
||||
|
||||
let sudo = ctx.require_sudo()?;
|
||||
sudo.execute(ctx, "/usr/local/sbin/pkg")?
|
||||
.args(["audit", "-Fr"])
|
||||
.status_checked_with(|status| {
|
||||
if !status.success() {
|
||||
println!(
|
||||
"{}",
|
||||
t!("The package audit was successful, but vulnerable packages still remain on the system")
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,32 +1,36 @@
|
||||
use crate::executor::RunType;
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::step::Step;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::require_option;
|
||||
use anyhow::Result;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use color_eyre::Result;
|
||||
use rust_i18n::t;
|
||||
|
||||
pub fn upgrade_freebsd(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> {
|
||||
let sudo = require_option(sudo, String::from("No sudo detected"))?;
|
||||
print_separator("FreeBSD Update");
|
||||
run_type
|
||||
.execute(sudo)
|
||||
.args(&["/usr/sbin/freebsd-update", "fetch", "install"])
|
||||
.check_run()
|
||||
pub fn upgrade_freebsd(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator(t!("FreeBSD Update"));
|
||||
|
||||
let sudo = ctx.require_sudo()?;
|
||||
sudo.execute(ctx, "/usr/sbin/freebsd-update")?
|
||||
.args(["fetch", "install"])
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
pub fn upgrade_packages(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> {
|
||||
let sudo = require_option(sudo, String::from("No sudo detected"))?;
|
||||
print_separator("FreeBSD Packages");
|
||||
run_type.execute(sudo).args(&["/usr/sbin/pkg", "upgrade"]).check_run()
|
||||
}
|
||||
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator(t!("FreeBSD Packages"));
|
||||
|
||||
pub fn audit_packages(sudo: &Option<PathBuf>) -> Result<()> {
|
||||
if let Some(sudo) = sudo {
|
||||
println!();
|
||||
Command::new(sudo)
|
||||
.args(&["/usr/sbin/pkg", "audit", "-Fr"])
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
let sudo = ctx.require_sudo()?;
|
||||
let mut command = sudo.execute(ctx, "/usr/sbin/pkg")?;
|
||||
command.arg("upgrade");
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("-y");
|
||||
}
|
||||
Ok(())
|
||||
command.status_checked()
|
||||
}
|
||||
|
||||
pub fn audit_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator(t!("FreeBSD Audit"));
|
||||
|
||||
let sudo = ctx.require_sudo()?;
|
||||
sudo.execute(ctx, "/usr/sbin/pkg")?
|
||||
.args(["audit", "-Fr"])
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,77 +1,74 @@
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::executor::{CommandExt, RunType};
|
||||
use crate::step::Step;
|
||||
use crate::terminal::{print_separator, prompt_yesno};
|
||||
use crate::{error::TopgradeError, utils::require, Step};
|
||||
use anyhow::Result;
|
||||
use log::debug;
|
||||
use crate::utils::require;
|
||||
use color_eyre::eyre::Result;
|
||||
use rust_i18n::t;
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
use tracing::debug;
|
||||
|
||||
pub fn run_macports(ctx: &ExecutionContext) -> Result<()> {
|
||||
require("port")?;
|
||||
let sudo = ctx.sudo().as_ref().unwrap();
|
||||
let port = require("port")?;
|
||||
|
||||
print_separator("MacPorts");
|
||||
ctx.run_type().execute(sudo).args(["port", "selfupdate"]).check_run()?;
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(["port", "-u", "upgrade", "outdated"])
|
||||
.check_run()?;
|
||||
|
||||
let sudo = ctx.require_sudo()?;
|
||||
|
||||
sudo.execute(ctx, &port)?.arg("selfupdate").status_checked()?;
|
||||
sudo.execute(ctx, &port)?
|
||||
.args(["-u", "upgrade", "outdated"])
|
||||
.status_checked()?;
|
||||
if ctx.config().cleanup() {
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(["port", "-N", "reclaim"])
|
||||
.check_run()?;
|
||||
sudo.execute(ctx, &port)?.args(["-N", "reclaim"]).status_checked()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_mas(run_type: RunType) -> Result<()> {
|
||||
pub fn run_mas(ctx: &ExecutionContext) -> Result<()> {
|
||||
let mas = require("mas")?;
|
||||
print_separator("macOS App Store");
|
||||
print_separator(t!("macOS App Store"));
|
||||
|
||||
run_type.execute(mas).arg("upgrade").check_run()
|
||||
ctx.execute(mas).arg("upgrade").status_checked()
|
||||
}
|
||||
|
||||
pub fn upgrade_macos(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator("macOS system update");
|
||||
print_separator(t!("macOS system update"));
|
||||
|
||||
let should_ask = !(ctx.config().yes(Step::System)) || (ctx.config().dry_run());
|
||||
let should_ask = !(ctx.config().yes(Step::System) || ctx.run_type().dry());
|
||||
if should_ask {
|
||||
println!("Finding available software");
|
||||
println!("{}", t!("Finding available software"));
|
||||
if system_update_available()? {
|
||||
let answer = prompt_yesno("A system update is available. Do you wish to install it?")?;
|
||||
let answer = prompt_yesno(t!("A system update is available. Do you wish to install it?").as_ref())?;
|
||||
if !answer {
|
||||
return Ok(());
|
||||
}
|
||||
println!();
|
||||
} else {
|
||||
println!("No new software available.");
|
||||
println!("{}", t!("No new software available."));
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let mut command = ctx.run_type().execute("softwareupdate");
|
||||
let mut command = ctx.execute("softwareupdate");
|
||||
command.args(["--install", "--all"]);
|
||||
|
||||
if should_ask {
|
||||
command.arg("--no-scan");
|
||||
}
|
||||
|
||||
command.check_run()
|
||||
command.status_checked()
|
||||
}
|
||||
|
||||
fn system_update_available() -> Result<bool> {
|
||||
let output = Command::new("softwareupdate").arg("--list").output()?;
|
||||
let output = Command::new("softwareupdate").arg("--list").output_checked_utf8()?;
|
||||
|
||||
debug!("{:?}", output);
|
||||
|
||||
let status = output.status;
|
||||
if !status.success() {
|
||||
return Err(TopgradeError::ProcessFailed(status).into());
|
||||
}
|
||||
let string_output = String::from_utf8(output.stderr)?;
|
||||
debug!("{:?}", string_output);
|
||||
Ok(!string_output.contains("No new software available"))
|
||||
Ok(!output.stderr.contains("No new software available"))
|
||||
}
|
||||
|
||||
pub fn run_sparkle(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -83,13 +80,143 @@ pub fn run_sparkle(ctx: &ExecutionContext) -> Result<()> {
|
||||
let probe = Command::new(&sparkle)
|
||||
.args(["--probe", "--application"])
|
||||
.arg(application.path())
|
||||
.check_output();
|
||||
.output_checked_utf8();
|
||||
if probe.is_ok() {
|
||||
let mut command = ctx.run_type().execute(&sparkle);
|
||||
let mut command = ctx.execute(&sparkle);
|
||||
command.args(["bundle", "--check-immediately", "--application"]);
|
||||
command.arg(application.path());
|
||||
command.spawn()?.wait()?;
|
||||
command.status_checked()?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_xcodes(ctx: &ExecutionContext) -> Result<()> {
|
||||
let xcodes = require("xcodes")?;
|
||||
print_separator("Xcodes");
|
||||
|
||||
let should_ask = !(ctx.config().yes(Step::Xcodes) || ctx.run_type().dry());
|
||||
|
||||
let releases = ctx.execute(&xcodes).args(["update"]).output_checked_utf8()?.stdout;
|
||||
|
||||
let releases_installed: Vec<String> = releases
|
||||
.lines()
|
||||
.filter(|r| r.contains("(Installed)"))
|
||||
.map(String::from)
|
||||
.collect();
|
||||
|
||||
if releases_installed.is_empty() {
|
||||
println!("{}", t!("No Xcode releases installed."));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (installed_gm, installed_beta, installed_regular) =
|
||||
releases_installed
|
||||
.iter()
|
||||
.fold((false, false, false), |(gm, beta, regular), release| {
|
||||
(
|
||||
gm || release.contains("GM") || release.contains("Release Candidate"),
|
||||
beta || release.contains("Beta"),
|
||||
regular
|
||||
|| !(release.contains("GM")
|
||||
|| release.contains("Release Candidate")
|
||||
|| release.contains("Beta")),
|
||||
)
|
||||
});
|
||||
|
||||
let releases_gm = releases
|
||||
.lines()
|
||||
.filter(|&r| r.matches("GM").count() > 0 || r.matches("Release Candidate").count() > 0)
|
||||
.map(String::from)
|
||||
.collect();
|
||||
let releases_beta = releases
|
||||
.lines()
|
||||
.filter(|&r| r.matches("Beta").count() > 0)
|
||||
.map(String::from)
|
||||
.collect();
|
||||
let releases_regular = releases
|
||||
.lines()
|
||||
.filter(|&r| {
|
||||
r.matches("GM").count() == 0
|
||||
&& r.matches("Release Candidate").count() == 0
|
||||
&& r.matches("Beta").count() == 0
|
||||
})
|
||||
.map(String::from)
|
||||
.collect();
|
||||
|
||||
if installed_gm {
|
||||
process_xcodes_releases(releases_gm, should_ask, ctx)?;
|
||||
}
|
||||
if installed_beta {
|
||||
process_xcodes_releases(releases_beta, should_ask, ctx)?;
|
||||
}
|
||||
if installed_regular {
|
||||
process_xcodes_releases(releases_regular, should_ask, ctx)?;
|
||||
}
|
||||
|
||||
let releases_new = ctx.execute(&xcodes).args(["list"]).output_checked_utf8()?.stdout;
|
||||
|
||||
let releases_gm_new_installed: HashSet<_> = releases_new
|
||||
.lines()
|
||||
.filter(|release| {
|
||||
release.contains("(Installed)") && (release.contains("GM") || release.contains("Release Candidate"))
|
||||
})
|
||||
.collect();
|
||||
let releases_beta_new_installed: HashSet<_> = releases_new
|
||||
.lines()
|
||||
.filter(|release| release.contains("(Installed)") && release.contains("Beta"))
|
||||
.collect();
|
||||
let releases_regular_new_installed: HashSet<_> = releases_new
|
||||
.lines()
|
||||
.filter(|release| {
|
||||
release.contains("(Installed)")
|
||||
&& !(release.contains("GM") || release.contains("Release Candidate") || release.contains("Beta"))
|
||||
})
|
||||
.collect();
|
||||
|
||||
for releases_new_installed in [
|
||||
releases_gm_new_installed,
|
||||
releases_beta_new_installed,
|
||||
releases_regular_new_installed,
|
||||
] {
|
||||
if should_ask && releases_new_installed.len() == 2 {
|
||||
let answer_uninstall =
|
||||
prompt_yesno(t!("Would you like to move the former Xcode release to the trash?").as_ref())?;
|
||||
if answer_uninstall {
|
||||
let _ = ctx
|
||||
.execute(&xcodes)
|
||||
.args([
|
||||
"uninstall",
|
||||
releases_new_installed.iter().next().copied().unwrap_or_default(),
|
||||
])
|
||||
.status_checked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_xcodes_releases(releases_filtered: Vec<String>, should_ask: bool, ctx: &ExecutionContext) -> Result<()> {
|
||||
let xcodes = require("xcodes")?;
|
||||
|
||||
if releases_filtered.last().is_none_or(|s| !s.contains("(Installed)")) && !releases_filtered.is_empty() {
|
||||
println!(
|
||||
"{} {}",
|
||||
t!("New Xcode release detected:"),
|
||||
releases_filtered.last().cloned().unwrap_or_default()
|
||||
);
|
||||
if should_ask {
|
||||
let answer_install = prompt_yesno(t!("Would you like to install it?").as_ref())?;
|
||||
if answer_install {
|
||||
let _ = ctx
|
||||
.execute(xcodes)
|
||||
.args(["install", &releases_filtered.last().cloned().unwrap_or_default()])
|
||||
.status_checked();
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ pub mod macos;
|
||||
pub mod openbsd;
|
||||
#[cfg(unix)]
|
||||
pub mod unix;
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(windows)]
|
||||
pub mod windows;
|
||||
|
||||
#[cfg(windows)]
|
||||
|
||||
@@ -1,17 +1,49 @@
|
||||
use crate::executor::RunType;
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::require_option;
|
||||
use anyhow::Result;
|
||||
use std::path::PathBuf;
|
||||
use color_eyre::eyre::Result;
|
||||
use rust_i18n::t;
|
||||
use std::fs;
|
||||
use tracing::debug;
|
||||
|
||||
pub fn upgrade_openbsd(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> {
|
||||
let sudo = require_option(sudo, String::from("No sudo detected"))?;
|
||||
print_separator("OpenBSD Update");
|
||||
run_type.execute(sudo).args(&["/usr/sbin/sysupgrade", "-n"]).check_run()
|
||||
fn is_openbsd_current() -> Result<bool> {
|
||||
let motd_content = fs::read_to_string("/etc/motd")?;
|
||||
let is_current = ["-current", "-beta"].iter().any(|&s| motd_content.contains(s));
|
||||
|
||||
debug!("OpenBSD is -current/-beta: {is_current}");
|
||||
|
||||
Ok(is_current)
|
||||
}
|
||||
|
||||
pub fn upgrade_packages(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> {
|
||||
let sudo = require_option(sudo, String::from("No sudo detected"))?;
|
||||
print_separator("OpenBSD Packages");
|
||||
run_type.execute(sudo).args(&["/usr/sbin/pkg_add", "-u"]).check_run()
|
||||
pub fn upgrade_openbsd(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator(t!("OpenBSD Update"));
|
||||
|
||||
let sudo = ctx.require_sudo()?;
|
||||
|
||||
let is_current = is_openbsd_current()?;
|
||||
|
||||
if is_current {
|
||||
sudo.execute(ctx, "/usr/sbin/sysupgrade")?.arg("-sn").status_checked()
|
||||
} else {
|
||||
sudo.execute(ctx, "/usr/sbin/syspatch")?.status_checked()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator(t!("OpenBSD Packages"));
|
||||
|
||||
let sudo = ctx.require_sudo()?;
|
||||
|
||||
let is_current = is_openbsd_current()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
sudo.execute(ctx, "/usr/sbin/pkg_delete")?.arg("-ac").status_checked()?;
|
||||
}
|
||||
|
||||
let mut command = sudo.execute(ctx, "/usr/sbin/pkg_add")?;
|
||||
command.arg("-u");
|
||||
if is_current {
|
||||
command.arg("-Dsnap");
|
||||
}
|
||||
command.status_checked()
|
||||
}
|
||||
|
||||
@@ -6,4 +6,4 @@ VERSION_ID="2"
|
||||
PRETTY_NAME="Amazon Linux 2"
|
||||
ANSI_COLOR="0;33"
|
||||
CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2"
|
||||
HOME_URL="https://amazonlinux.com/"
|
||||
HOME_URL="https://amazonlinux.com/"
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
NAME="Anarchy Linux"
|
||||
PRETTY_NAME="Anarchy Linux"
|
||||
ID=anarchy
|
||||
ID_LIKE=anarchylinux
|
||||
ANSI_COLOR="0;36"
|
||||
HOME_URL="https://anarchylinux.org/"
|
||||
@@ -1,10 +0,0 @@
|
||||
NAME="Antergos Linux"
|
||||
VERSION="18.7-ISO-Rolling"
|
||||
ID="antergos"
|
||||
ID_LIKE="arch"
|
||||
PRETTY_NAME="Antergos Linux"
|
||||
CPE_NAME="cpe:/o:antergosproject:antergos:18.7"
|
||||
ANSI_COLOR="1;34;40"
|
||||
HOME_URL="antergos.com"
|
||||
SUPPORT_URL="forum.antergos.com"
|
||||
BUG_REPORT_URL="@antergos"
|
||||
10
src/steps/os/os_release/aosc
Normal file
10
src/steps/os/os_release/aosc
Normal file
@@ -0,0 +1,10 @@
|
||||
PRETTY_NAME="AOSC OS (12.2.2)"
|
||||
NAME="AOSC OS"
|
||||
VERSION_ID="12.2.2"
|
||||
VERSION="12.2.2 (localhost)"
|
||||
BUILD_ID="20250916"
|
||||
ID=aosc
|
||||
ANSI_COLOR="1;36"
|
||||
HOME_URL="https://aosc.io/"
|
||||
SUPPORT_URL="https://github.com/AOSC-Dev/aosc-os-abbs"
|
||||
BUG_REPORT_URL="https://github.com/AOSC-Dev/aosc-os-abbs/issues"
|
||||
@@ -8,4 +8,4 @@ HOME_URL="https://www.archlinux32.org/"
|
||||
DOCUMENTATION_URL="https://wiki.archlinux.org/"
|
||||
SUPPORT_URL="https://bbs.archlinux32.org/"
|
||||
BUG_REPORT_URL="https://bugs.archlinux32.org/"
|
||||
LOGO=archlinux
|
||||
LOGO=archlinux
|
||||
|
||||
23
src/steps/os/os_release/aurora
Normal file
23
src/steps/os/os_release/aurora
Normal file
@@ -0,0 +1,23 @@
|
||||
NAME="Aurora"
|
||||
VERSION="latest-41.20250210.4 (Kinoite)"
|
||||
RELEASE_TYPE=stable
|
||||
ID=aurora
|
||||
ID_LIKE="fedora"
|
||||
VERSION_ID=41
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f41"
|
||||
PRETTY_NAME="Aurora (Version: latest-41.20250210.4 / FROM Fedora Kinoite 41)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:universal-blue:aurora:41"
|
||||
DEFAULT_HOSTNAME="aurora"
|
||||
HOME_URL="https://getaurora.dev/"
|
||||
DOCUMENTATION_URL="https://docs.getaurora.dev"
|
||||
SUPPORT_URL="https://github.com/ublue-os/aurora/issues/"
|
||||
BUG_REPORT_URL="https://github.com/ublue-os/aurora/issues/"
|
||||
SUPPORT_END=2025-12-15
|
||||
VARIANT="Kinoite"
|
||||
VARIANT_ID=aurora
|
||||
OSTREE_VERSION='latest-41.20250210.4'
|
||||
BUILD_ID="fc1570c"
|
||||
IMAGE_ID="aurora"
|
||||
25
src/steps/os/os_release/bazzite
Normal file
25
src/steps/os/os_release/bazzite
Normal file
@@ -0,0 +1,25 @@
|
||||
NAME="Bazzite"
|
||||
VERSION="41.20250208.0 (Kinoite)"
|
||||
RELEASE_TYPE=stable
|
||||
ID=bazzite
|
||||
ID_LIKE="fedora"
|
||||
VERSION_ID=41
|
||||
VERSION_CODENAME="Holographic"
|
||||
PLATFORM_ID="platform:f41"
|
||||
PRETTY_NAME="Bazzite 41 (FROM Fedora Kinoite)"
|
||||
ANSI_COLOR="0;38;2;138;43;226"
|
||||
LOGO=bazzite-logo-icon
|
||||
CPE_NAME="cpe:/o:universal-blue:bazzite:41"
|
||||
DEFAULT_HOSTNAME="bazzite"
|
||||
HOME_URL="https://bazzite.gg"
|
||||
DOCUMENTATION_URL="https://docs.bazzite.gg"
|
||||
SUPPORT_URL="https://discord.bazzite.gg"
|
||||
BUG_REPORT_URL="https://github.com/ublue-os/bazzite/issues/"
|
||||
SUPPORT_END=2025-12-15
|
||||
VARIANT="Kinoite"
|
||||
VARIANT_ID=bazzite-nvidia-open
|
||||
OSTREE_VERSION='41.20250208.0'
|
||||
BUILD_ID="Stable (F41.20250208)"
|
||||
BOOTLOADER_NAME="Bazzite Stable (F41.20250208)"
|
||||
BUILD_ID="Stable (F41.20250208)"
|
||||
BOOTLOADER_NAME="Bazzite Stable (F41.20250208)"
|
||||
24
src/steps/os/os_release/bluefin
Normal file
24
src/steps/os/os_release/bluefin
Normal file
@@ -0,0 +1,24 @@
|
||||
NAME="Bluefin"
|
||||
VERSION="41.20250216.1 (Silverblue)"
|
||||
RELEASE_TYPE=stable
|
||||
ID=bluefin
|
||||
ID_LIKE="fedora"
|
||||
VERSION_ID=41
|
||||
VERSION_CODENAME="Archaeopteryx"
|
||||
PLATFORM_ID="platform:f41"
|
||||
PRETTY_NAME="Bluefin (Version: 41.20250216.1 / FROM Fedora Silverblue 41)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:universal-blue:bluefin:41"
|
||||
DEFAULT_HOSTNAME="bluefin"
|
||||
HOME_URL="https://projectbluefin.io"
|
||||
DOCUMENTATION_URL="https://docs.projectbluefin.io"
|
||||
SUPPORT_URL="https://github.com/ublue-os/bluefin/issues/"
|
||||
BUG_REPORT_URL="https://github.com/ublue-os/bluefin/issues/"
|
||||
SUPPORT_END=2025-12-15
|
||||
VARIANT="Silverblue"
|
||||
VARIANT_ID=bluefin
|
||||
OSTREE_VERSION='41.20250216.1'
|
||||
BUILD_ID="185146a"
|
||||
IMAGE_ID="bluefin"
|
||||
IMAGE_VERSION="41.20250216.1"
|
||||
11
src/steps/os/os_release/cachyos
Normal file
11
src/steps/os/os_release/cachyos
Normal file
@@ -0,0 +1,11 @@
|
||||
NAME="CachyOS Linux"
|
||||
PRETTY_NAME="CachyOS"
|
||||
ID=cachyos
|
||||
BUILD_ID=rolling
|
||||
ANSI_COLOR="38;2;23;147;209"
|
||||
HOME_URL="https://cachyos.org/"
|
||||
DOCUMENTATION_URL="https://wiki.cachyos.org/"
|
||||
SUPPORT_URL="https://discuss.cachyos.org/"
|
||||
BUG_REPORT_URL="https://github.com/cachyos"
|
||||
PRIVACY_POLICY_URL="https://terms.archlinux.org/docs/privacy-policy/"
|
||||
LOGO=cachyos
|
||||
@@ -13,4 +13,3 @@ CENTOS_MANTISBT_PROJECT="CentOS-7"
|
||||
CENTOS_MANTISBT_PROJECT_VERSION="7"
|
||||
REDHAT_SUPPORT_PRODUCT="centos"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION="7"
|
||||
|
||||
|
||||
23
src/steps/os/os_release/coreos
Normal file
23
src/steps/os/os_release/coreos
Normal file
@@ -0,0 +1,23 @@
|
||||
NAME="Fedora Linux"
|
||||
VERSION="41.20250117.3.0 (CoreOS)"
|
||||
RELEASE_TYPE=stable
|
||||
ID=fedora
|
||||
VERSION_ID=41
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f41"
|
||||
PRETTY_NAME="Fedora CoreOS 41.20250117.3.0 (uCore)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:41"
|
||||
HOME_URL="https://getfedora.org/coreos/"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-coreos/"
|
||||
SUPPORT_URL="https://github.com/coreos/fedora-coreos-tracker/"
|
||||
BUG_REPORT_URL="https://github.com/coreos/fedora-coreos-tracker/"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=41
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=41
|
||||
SUPPORT_END=2025-12-15
|
||||
VARIANT="CoreOS"
|
||||
VARIANT_ID=coreos
|
||||
OSTREE_VERSION='41.20250117.3.0'
|
||||
8
src/steps/os/os_release/deepin
Normal file
8
src/steps/os/os_release/deepin
Normal file
@@ -0,0 +1,8 @@
|
||||
PRETTY_NAME="Deepin 20.9"
|
||||
NAME="Deepin"
|
||||
VERSION_ID="20.9"
|
||||
VERSION="20.9"
|
||||
VERSION_CODENAME="apricot"
|
||||
ID=Deepin
|
||||
HOME_URL="https://www.deepin.org/"
|
||||
BUG_REPORT_URL="https://bbs.deepin.org/"
|
||||
22
src/steps/os/os_release/fedoraiot
Normal file
22
src/steps/os/os_release/fedoraiot
Normal file
@@ -0,0 +1,22 @@
|
||||
NAME="Fedora Linux"
|
||||
VERSION="39.20240415.0 (IoT Edition)"
|
||||
ID=fedora
|
||||
VERSION_ID=39
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f39"
|
||||
PRETTY_NAME="Fedora Linux 39.20240415.0 (IoT Edition)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
|
||||
HOME_URL="https://fedoraproject.org/"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f39/system-administrators-guide/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://bugzilla.redhat.com/"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||
SUPPORT_END=2024-11-12
|
||||
VARIANT="IoT Edition"
|
||||
VARIANT_ID=iot
|
||||
OSTREE_VERSION='39.20240415.0'
|
||||
23
src/steps/os/os_release/fedorakinoite
Normal file
23
src/steps/os/os_release/fedorakinoite
Normal file
@@ -0,0 +1,23 @@
|
||||
NAME="Fedora Linux"
|
||||
VERSION="39.20240105.0 (Kinoite)"
|
||||
ID=fedora
|
||||
VERSION_ID=39
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f39"
|
||||
PRETTY_NAME="Fedora Linux 39.20240105.0 (Kinoite)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
|
||||
DEFAULT_HOSTNAME="fedora"
|
||||
HOME_URL="https://kinoite.fedoraproject.org"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-kinoite/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://pagure.io/fedora-kde/SIG/issues"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||
SUPPORT_END=2024-11-12
|
||||
VARIANT="Kinoite"
|
||||
VARIANT_ID=kinoite
|
||||
OSTREE_VERSION='39.20240105.0'
|
||||
22
src/steps/os/os_release/fedoraonyx
Normal file
22
src/steps/os/os_release/fedoraonyx
Normal file
@@ -0,0 +1,22 @@
|
||||
NAME="Fedora Linux"
|
||||
VERSION="39 (Onyx)"
|
||||
ID=fedora
|
||||
VERSION_ID=39
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f39"
|
||||
PRETTY_NAME="Fedora Linux 39 (Onyx)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
|
||||
DEFAULT_HOSTNAME="fedora"
|
||||
HOME_URL="https://fedoraproject.org/onyx/"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-onyx/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://bugzilla.redhat.com/"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||
SUPPORT_END=2024-05-14
|
||||
VARIANT="Onyx"
|
||||
VARIANT_ID=onyx
|
||||
22
src/steps/os/os_release/fedorasericea
Normal file
22
src/steps/os/os_release/fedorasericea
Normal file
@@ -0,0 +1,22 @@
|
||||
NAME="Fedora Linux"
|
||||
VERSION="39 (Sericea)"
|
||||
ID=fedora
|
||||
VERSION_ID=39
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f39"
|
||||
PRETTY_NAME="Fedora Linux 39 (Sericea)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
|
||||
DEFAULT_HOSTNAME="fedora"
|
||||
HOME_URL="https://fedoraproject.org/sericea/"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-sericea/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://gitlab.com/fedora/sigs/sway/SIG/-/issues"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||
SUPPORT_END=2024-05-14
|
||||
VARIANT="Sericea"
|
||||
VARIANT_ID=sericea
|
||||
22
src/steps/os/os_release/fedorasilverblue
Normal file
22
src/steps/os/os_release/fedorasilverblue
Normal file
@@ -0,0 +1,22 @@
|
||||
NAME="Fedora Linux"
|
||||
VERSION="39 (Silverblue)"
|
||||
ID=fedora
|
||||
VERSION_ID=39
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f39"
|
||||
PRETTY_NAME="Fedora Linux 39 (Silverblue)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
|
||||
DEFAULT_HOSTNAME="fedora"
|
||||
HOME_URL="https://silverblue.fedoraproject.org"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-silverblue/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://github.com/fedora-silverblue/issue-tracker/issues"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||
SUPPORT_END=2024-05-14
|
||||
VARIANT="Silverblue"
|
||||
VARIANT_ID=silverblue
|
||||
23
src/steps/os/os_release/fedoraswayatomic
Normal file
23
src/steps/os/os_release/fedoraswayatomic
Normal file
@@ -0,0 +1,23 @@
|
||||
NAME="Fedora Linux"
|
||||
VERSION="40.20240426.0 (Sway Atomic)"
|
||||
ID=fedora
|
||||
VERSION_ID=40
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f40"
|
||||
PRETTY_NAME="Fedora Linux 40.20240426.0 (Sway Atomic)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:40"
|
||||
DEFAULT_HOSTNAME="fedora"
|
||||
HOME_URL="https://fedoraproject.org/atomic-desktops/sway/"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-sericea/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://gitlab.com/fedora/sigs/sway/SIG/-/issues"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=40
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=40
|
||||
SUPPORT_END=2025-05-13
|
||||
VARIANT="Sway Atomic"
|
||||
VARIANT_ID=sway-atomic
|
||||
OSTREE_VERSION='40.20240426.0'
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user