mirror of
https://github.com/platformio/platformio-core.git
synced 2025-12-23 07:12:31 +01:00
Compare commits
820 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f7362951c | ||
|
|
f4535190a3 | ||
|
|
236c4570cf | ||
|
|
5844c536a4 | ||
|
|
6627fd5790 | ||
|
|
25074d80d3 | ||
|
|
f032663b33 | ||
|
|
d24702eb29 | ||
|
|
9051677d74 | ||
|
|
7637286efa | ||
|
|
31a24e1652 | ||
|
|
c8c4028a23 | ||
|
|
0bd27a36e9 | ||
|
|
ddfe5a6c03 | ||
|
|
ee93ca1615 | ||
|
|
4c2aca4956 | ||
|
|
dd14b5e2ed | ||
|
|
6464420c1c | ||
|
|
79ec493c79 | ||
|
|
abb464707d | ||
|
|
7c846b8968 | ||
|
|
84c2e0a3d6 | ||
|
|
c2ddc89e46 | ||
|
|
1495e24e1e | ||
|
|
6e16b43568 | ||
|
|
6c18b37d54 | ||
|
|
6134db8e81 | ||
|
|
3cf62f8fa6 | ||
|
|
523b6dfa98 | ||
|
|
3928cb522e | ||
|
|
de856ee730 | ||
|
|
d3b7508bd5 | ||
|
|
6c71a3bea2 | ||
|
|
d2e27f5385 | ||
|
|
2a5de43964 | ||
|
|
029e66cd06 | ||
|
|
96fb8c74f9 | ||
|
|
b006f53010 | ||
|
|
19d518fc4c | ||
|
|
f01cd7570c | ||
|
|
ffebfd4376 | ||
|
|
e4264a6a51 | ||
|
|
d85bc0f7f8 | ||
|
|
1445a91fab | ||
|
|
3b878747f2 | ||
|
|
401f8a4891 | ||
|
|
6bec593b93 | ||
|
|
aef49a8bff | ||
|
|
772e25df49 | ||
|
|
3363b3a516 | ||
|
|
1f096fe03f | ||
|
|
32e440bec7 | ||
|
|
99b5204802 | ||
|
|
3c17b31d5e | ||
|
|
89a80f158e | ||
|
|
c42db2ec22 | ||
|
|
6a3b6f0d44 | ||
|
|
ca2622b7a6 | ||
|
|
b9a9fd4f43 | ||
|
|
1ea6d47110 | ||
|
|
256acf7e23 | ||
|
|
284ccc9e8a | ||
|
|
655eedd7b0 | ||
|
|
bb6490d6f2 | ||
|
|
300b7b2138 | ||
|
|
86c4bd69d2 | ||
|
|
dd63c8002a | ||
|
|
13fc8508b3 | ||
|
|
a76933990c | ||
|
|
7e3e394707 | ||
|
|
cee3f4d90f | ||
|
|
c557473cfb | ||
|
|
f893fcf135 | ||
|
|
092326cb91 | ||
|
|
92a5c1bac6 | ||
|
|
4b2f0eb1d5 | ||
|
|
9ae67fdad9 | ||
|
|
5142feba7a | ||
|
|
8cbe7bc7a6 | ||
|
|
d8f36b6534 | ||
|
|
58d533a3bb | ||
|
|
18e130fd12 | ||
|
|
b72c1636f7 | ||
|
|
f68c18d1e5 | ||
|
|
db6b8a6dbc | ||
|
|
5afa0a955e | ||
|
|
ca3b3717d3 | ||
|
|
d4784c05f5 | ||
|
|
7a01da7039 | ||
|
|
42690d3fa7 | ||
|
|
50cbc4d4e2 | ||
|
|
63c2278a83 | ||
|
|
4bccaae945 | ||
|
|
e12bc9fe5f | ||
|
|
ac63cf0240 | ||
|
|
30709fd0b3 | ||
|
|
6f9985125d | ||
|
|
743a3e2c02 | ||
|
|
bd21ff0d3e | ||
|
|
46858fff39 | ||
|
|
854c549e1c | ||
|
|
4b5bc91abb | ||
|
|
375c396b7b | ||
|
|
7aaa9c028b | ||
|
|
7f351bc7c8 | ||
|
|
c42fe32972 | ||
|
|
a6e61a7a5a | ||
|
|
4bc3e3cf95 | ||
|
|
4a7a8b8b68 | ||
|
|
51ab0bbd3c | ||
|
|
30937df4e6 | ||
|
|
b15a4e746a | ||
|
|
1b17234c41 | ||
|
|
26f897cb55 | ||
|
|
99d049a6dd | ||
|
|
f3c3402b35 | ||
|
|
55b9c446f1 | ||
|
|
e3ca0c6f04 | ||
|
|
4ff591bd7e | ||
|
|
b02335a294 | ||
|
|
cc3ea65faa | ||
|
|
206bb38f54 | ||
|
|
10da6bf5c6 | ||
|
|
22860cd4e5 | ||
|
|
0b8a595288 | ||
|
|
7e7856e44c | ||
|
|
b104b840c4 | ||
|
|
32386bec18 | ||
|
|
db366b3163 | ||
|
|
472c80159d | ||
|
|
4a95148cd0 | ||
|
|
11a43b2693 | ||
|
|
12fb02db6e | ||
|
|
52f8e98eed | ||
|
|
dcc63da2ef | ||
|
|
756bb07d1a | ||
|
|
d2be7033e9 | ||
|
|
27ccdc76a0 | ||
|
|
dcecd5f922 | ||
|
|
506a08c7cf | ||
|
|
e2892d5d4c | ||
|
|
0ce7885833 | ||
|
|
6b7e8ebe97 | ||
|
|
6e5aee5ef3 | ||
|
|
4aebf8c9d7 | ||
|
|
1f75430fab | ||
|
|
2564b9eb78 | ||
|
|
cf558036d0 | ||
|
|
b568eb68d6 | ||
|
|
19006378a8 | ||
|
|
a19c4dbcda | ||
|
|
22a0a20666 | ||
|
|
440bb1e6f4 | ||
|
|
87dffa36b8 | ||
|
|
bd052d0ce0 | ||
|
|
73dd29c59c | ||
|
|
460a983ab2 | ||
|
|
ea94f65159 | ||
|
|
6f6460fd4e | ||
|
|
5f812409d4 | ||
|
|
87f2e86928 | ||
|
|
626640cc05 | ||
|
|
f5e0ccecc3 | ||
|
|
598769fe1b | ||
|
|
f7e24f2093 | ||
|
|
9b141bf5a8 | ||
|
|
9d2adb37f3 | ||
|
|
97e2d24cd1 | ||
|
|
720732eba6 | ||
|
|
37c6f20747 | ||
|
|
e27c1c39e4 | ||
|
|
1e000027c7 | ||
|
|
e3fea07596 | ||
|
|
06ed9ba77d | ||
|
|
f4d9769450 | ||
|
|
9da7c42be4 | ||
|
|
3419558265 | ||
|
|
61383f9b08 | ||
|
|
be0acaed40 | ||
|
|
0c4c4ac657 | ||
|
|
bb8b115a0b | ||
|
|
2e2735a49c | ||
|
|
7badd54c89 | ||
|
|
4dfc561551 | ||
|
|
3c2afeba89 | ||
|
|
d2d46f4aea | ||
|
|
ccc7d9c9a4 | ||
|
|
45fcb40a5c | ||
|
|
1585b829be | ||
|
|
0ceae62701 | ||
|
|
f2bdb17c55 | ||
|
|
83b00ac80c | ||
|
|
a76e445ed9 | ||
|
|
edff591c90 | ||
|
|
cb7148d018 | ||
|
|
38afa07dbe | ||
|
|
92073a4ccd | ||
|
|
abf6304818 | ||
|
|
9a86175701 | ||
|
|
b764a2220f | ||
|
|
3776233233 | ||
|
|
0d92e8fc17 | ||
|
|
40422eac2e | ||
|
|
0fb4b1e109 | ||
|
|
44ecc7c666 | ||
|
|
26d659c433 | ||
|
|
58c4145809 | ||
|
|
fe08ce7795 | ||
|
|
9163e9e67d | ||
|
|
7acae6461e | ||
|
|
e7a172b8dd | ||
|
|
b90e89a791 | ||
|
|
db11244f49 | ||
|
|
54f0748201 | ||
|
|
575f0ae300 | ||
|
|
7a100fb0b0 | ||
|
|
d01d314f47 | ||
|
|
e5e2210768 | ||
|
|
d22b479bd3 | ||
|
|
19853b0b66 | ||
|
|
ce62514a17 | ||
|
|
4a4ba5594b | ||
|
|
af5a820862 | ||
|
|
40e4e38e0c | ||
|
|
cb1c825747 | ||
|
|
8c27754045 | ||
|
|
3247e661e9 | ||
|
|
7c93167d52 | ||
|
|
79b2bfdefe | ||
|
|
de7d710943 | ||
|
|
b88a29e652 | ||
|
|
ed0b12dcf9 | ||
|
|
280bede0e9 | ||
|
|
e6938f8f39 | ||
|
|
6d705172f5 | ||
|
|
8fff7084db | ||
|
|
e75bf27b5f | ||
|
|
2c99607d3d | ||
|
|
c09af13b7f | ||
|
|
ee6b498ca9 | ||
|
|
65f2f02d93 | ||
|
|
960edb5611 | ||
|
|
cda7a97e67 | ||
|
|
c520700276 | ||
|
|
a7654a6098 | ||
|
|
814679522a | ||
|
|
4249349c2b | ||
|
|
d065646d3e | ||
|
|
0cf7aeeec9 | ||
|
|
277ccdafb6 | ||
|
|
5b00f6fb95 | ||
|
|
3f46a97b6b | ||
|
|
3989979ca3 | ||
|
|
50eda82e27 | ||
|
|
daa3481862 | ||
|
|
2d94000dd5 | ||
|
|
e3eb155d76 | ||
|
|
f95e23118c | ||
|
|
82778473fe | ||
|
|
dae3b9665b | ||
|
|
f19058df65 | ||
|
|
3c7bec7c61 | ||
|
|
c4388a6904 | ||
|
|
6d1e637518 | ||
|
|
bbd56d6eb0 | ||
|
|
0b317ef04b | ||
|
|
c0cfbe2ce0 | ||
|
|
3ed5d41df5 | ||
|
|
517ee6532f | ||
|
|
653f22f85b | ||
|
|
38906478d3 | ||
|
|
e81d83b8c2 | ||
|
|
b12d9f62b9 | ||
|
|
0849e5faad | ||
|
|
1a4419059d | ||
|
|
4ef1333abc | ||
|
|
2b11f64ef1 | ||
|
|
5b98f432f2 | ||
|
|
76779e6af4 | ||
|
|
738d537266 | ||
|
|
327d5990d6 | ||
|
|
16021d0df7 | ||
|
|
b37a74dfd9 | ||
|
|
d02f02731f | ||
|
|
4295c54c67 | ||
|
|
fb1e4fa02b | ||
|
|
62b8a63b80 | ||
|
|
ab3c832f5e | ||
|
|
d380e7ea01 | ||
|
|
e69fd5e682 | ||
|
|
285f19e132 | ||
|
|
4151f53e14 | ||
|
|
5895fb9faf | ||
|
|
19e22d74f3 | ||
|
|
26ed6a5548 | ||
|
|
05dd7dd811 | ||
|
|
8b694f3734 | ||
|
|
c9026a1b9c | ||
|
|
9b221a06c8 | ||
|
|
f88904e246 | ||
|
|
e3533dcb01 | ||
|
|
8edb5ffe20 | ||
|
|
90e6cd7b46 | ||
|
|
1fa73fb632 | ||
|
|
a615af233a | ||
|
|
4817e13823 | ||
|
|
ee43b86742 | ||
|
|
93bfc57dea | ||
|
|
a568a5c356 | ||
|
|
0b21977e48 | ||
|
|
2f7668aef5 | ||
|
|
72fa6eebba | ||
|
|
2f6a417168 | ||
|
|
faa63727ab | ||
|
|
a2b1a0a0a7 | ||
|
|
0d7bc09c49 | ||
|
|
f57ca747a9 | ||
|
|
624421e4b0 | ||
|
|
943c6bc59c | ||
|
|
9ce0b0e25b | ||
|
|
df3a13fc61 | ||
|
|
5a0a215bfc | ||
|
|
eaff7f307c | ||
|
|
8d63591ce8 | ||
|
|
0e3aa29689 | ||
|
|
a56b19ff65 | ||
|
|
62b7ec271f | ||
|
|
5515bef3d7 | ||
|
|
092f5de231 | ||
|
|
81fdd75aac | ||
|
|
f63b2f79e0 | ||
|
|
0501d55c8f | ||
|
|
fe6f51369e | ||
|
|
8f454c7e9c | ||
|
|
965feccfdc | ||
|
|
5e18f9bbda | ||
|
|
541fcbf015 | ||
|
|
16f5374474 | ||
|
|
b414745aa1 | ||
|
|
696d95bf1b | ||
|
|
1269ce064a | ||
|
|
9097d455db | ||
|
|
1615159014 | ||
|
|
e4e1e72c30 | ||
|
|
43329b7748 | ||
|
|
2280865936 | ||
|
|
fb2f3c8836 | ||
|
|
e2f21212b7 | ||
|
|
d7597d0992 | ||
|
|
c21876ebe3 | ||
|
|
76bea5b7a7 | ||
|
|
a03d82ff1a | ||
|
|
f555656c92 | ||
|
|
f289ebd1f3 | ||
|
|
41b3646012 | ||
|
|
8de5db4b48 | ||
|
|
d8be12dcdd | ||
|
|
71f9401e23 | ||
|
|
cdd63dec65 | ||
|
|
279fdfc47a | ||
|
|
feda42f18f | ||
|
|
d86f7fc25e | ||
|
|
e4fb675d5f | ||
|
|
25e786e6a5 | ||
|
|
fd01e98cb1 | ||
|
|
2a88cdb8df | ||
|
|
be8f842061 | ||
|
|
fcb81ae074 | ||
|
|
7d9c018b44 | ||
|
|
a6e12532f8 | ||
|
|
bd202f55ce | ||
|
|
f7b5a7bed8 | ||
|
|
6123d6f9bf | ||
|
|
6c8173d1aa | ||
|
|
d2f857d176 | ||
|
|
1e2afafbc4 | ||
|
|
927c5c5e36 | ||
|
|
b2ea96b4a7 | ||
|
|
6afb53dd7d | ||
|
|
d7477833d6 | ||
|
|
7624645626 | ||
|
|
53753c0127 | ||
|
|
95604ff66a | ||
|
|
99e0d1071a | ||
|
|
13aacbcc05 | ||
|
|
b137b25169 | ||
|
|
b44fb101c4 | ||
|
|
accc8ac254 | ||
|
|
435a526140 | ||
|
|
346580d955 | ||
|
|
81f343dbe8 | ||
|
|
fa443f2e5f | ||
|
|
a25a86e42f | ||
|
|
1ffa924483 | ||
|
|
463a16a68f | ||
|
|
d2adca8d68 | ||
|
|
057bf89894 | ||
|
|
c9037982d7 | ||
|
|
ce1264564f | ||
|
|
61ffab376d | ||
|
|
f3bcaae4e4 | ||
|
|
2201214717 | ||
|
|
eba4231cdc | ||
|
|
de0a810fcf | ||
|
|
644fc36c32 | ||
|
|
41144bffeb | ||
|
|
c84709dd9d | ||
|
|
f28651eaf7 | ||
|
|
9e40eb992e | ||
|
|
f445cb7895 | ||
|
|
dfc0ecdf69 | ||
|
|
6f11f812f8 | ||
|
|
4191a9bc3c | ||
|
|
f2fbdafe64 | ||
|
|
22a037b213 | ||
|
|
dbe3ab6c97 | ||
|
|
6bed610af3 | ||
|
|
4d9547066b | ||
|
|
54c18ae0c6 | ||
|
|
e49fb9f0d0 | ||
|
|
33da2af31e | ||
|
|
bcb3678055 | ||
|
|
28da2d245b | ||
|
|
e6864adfb6 | ||
|
|
8562319638 | ||
|
|
6be17cec37 | ||
|
|
f34e6e9c4c | ||
|
|
e8051838a3 | ||
|
|
f1f5497d8d | ||
|
|
1b44ba4ce0 | ||
|
|
a4d2dc856c | ||
|
|
7964d1c2bf | ||
|
|
5df5dd155f | ||
|
|
89cce21161 | ||
|
|
0bdef36e2a | ||
|
|
e549a07901 | ||
|
|
98603dad66 | ||
|
|
c37fbda7a8 | ||
|
|
34ea4d8f41 | ||
|
|
452a76105f | ||
|
|
4982676ca8 | ||
|
|
83d115acca | ||
|
|
86bd0f7c37 | ||
|
|
83fe00a0cf | ||
|
|
526abc6a9f | ||
|
|
63feda6efc | ||
|
|
c9b3dedbb0 | ||
|
|
dae8dfe1fc | ||
|
|
100def7609 | ||
|
|
8594012fa1 | ||
|
|
27400f66a9 | ||
|
|
bb1e590222 | ||
|
|
a4b414010d | ||
|
|
1d72a96654 | ||
|
|
9b85ed86a9 | ||
|
|
e36066a9a2 | ||
|
|
8082158a16 | ||
|
|
1a8567a6da | ||
|
|
b17cbe30e2 | ||
|
|
8aadc88dd5 | ||
|
|
f3d26fae64 | ||
|
|
828d6f5baf | ||
|
|
2003806481 | ||
|
|
362823c1e1 | ||
|
|
9c10e00234 | ||
|
|
a4cef2fbd8 | ||
|
|
e5fca99b52 | ||
|
|
f4c692eed2 | ||
|
|
2e0688db5f | ||
|
|
ac2b358f87 | ||
|
|
251a2c9fa4 | ||
|
|
0064d4b2c5 | ||
|
|
ebbac6b483 | ||
|
|
d5373a62f4 | ||
|
|
681b91a6a4 | ||
|
|
8c66352994 | ||
|
|
4e1ec1215a | ||
|
|
6981894060 | ||
|
|
57c92e877c | ||
|
|
e8c0b8504a | ||
|
|
93bbe8f2a3 | ||
|
|
c78bb1f572 | ||
|
|
7256102785 | ||
|
|
fc907c568d | ||
|
|
9e078ff4d7 | ||
|
|
5658e7f718 | ||
|
|
111eb55a9f | ||
|
|
0630ec5503 | ||
|
|
38cc493eb7 | ||
|
|
254507c3a3 | ||
|
|
7cdcc9099b | ||
|
|
fb046c43ea | ||
|
|
73ddf80fc1 | ||
|
|
a5a224ac6f | ||
|
|
c56dfda833 | ||
|
|
6081f9ff1b | ||
|
|
f3c7d71b3b | ||
|
|
5748bf9549 | ||
|
|
84a0a6a418 | ||
|
|
1ee9f183cc | ||
|
|
55e8523925 | ||
|
|
c9efe24959 | ||
|
|
69aff39205 | ||
|
|
f6e9e15253 | ||
|
|
b7f685ed62 | ||
|
|
6e03eff303 | ||
|
|
3e0b95e1e1 | ||
|
|
a32997ceba | ||
|
|
63674d85e8 | ||
|
|
56848ece7a | ||
|
|
449722f08c | ||
|
|
949b4562c7 | ||
|
|
75f68c8be1 | ||
|
|
1b117712cf | ||
|
|
11356af502 | ||
|
|
9dbdf7fc8d | ||
|
|
dec38273b6 | ||
|
|
5098f5f420 | ||
|
|
d32fd72d13 | ||
|
|
a4692d5457 | ||
|
|
24ea7aaede | ||
|
|
b7f10982c3 | ||
|
|
8f28d1ad43 | ||
|
|
d5db2f0eb7 | ||
|
|
fe69f3de04 | ||
|
|
5534394b06 | ||
|
|
24fc2f7e14 | ||
|
|
5b23c9a294 | ||
|
|
7338a02b48 | ||
|
|
8555e83cb1 | ||
|
|
39494d18bf | ||
|
|
aab42c3cff | ||
|
|
f5a23c3817 | ||
|
|
b3eb81c3b4 | ||
|
|
4f4c88aca9 | ||
|
|
c3ad3ebb57 | ||
|
|
f13734dda4 | ||
|
|
24e63e7a02 | ||
|
|
a163048396 | ||
|
|
55f8471aff | ||
|
|
04e9f38e0e | ||
|
|
90972e9ce0 | ||
|
|
6e8f60a27a | ||
|
|
014090c407 | ||
|
|
e40b251c06 | ||
|
|
414a194c9d | ||
|
|
7bffe3993d | ||
|
|
3828e6d15e | ||
|
|
85c582bc93 | ||
|
|
ea1c9dec12 | ||
|
|
6753121a6a | ||
|
|
f63d899c42 | ||
|
|
7219c9f806 | ||
|
|
df2f1d10fd | ||
|
|
3f71067b67 | ||
|
|
8dc68a01fd | ||
|
|
9e0ded958c | ||
|
|
68243aa95b | ||
|
|
507df1f507 | ||
|
|
1800c29b44 | ||
|
|
0343548f6e | ||
|
|
5cb5c9713e | ||
|
|
5e2c5c793f | ||
|
|
3022cb6955 | ||
|
|
4687665ff3 | ||
|
|
001f075a49 | ||
|
|
7d78e4a60a | ||
|
|
2786bfbeb8 | ||
|
|
d3049a8d62 | ||
|
|
831a2582ed | ||
|
|
0919019123 | ||
|
|
7dd9c99c91 | ||
|
|
326c24911a | ||
|
|
133fa1495b | ||
|
|
7c040ed99f | ||
|
|
f88a2de8a9 | ||
|
|
a24ec8b07a | ||
|
|
d6ad6f96e8 | ||
|
|
411764854b | ||
|
|
973f77012f | ||
|
|
1d80da2559 | ||
|
|
00d298935a | ||
|
|
4a9a478243 | ||
|
|
9040bbb75a | ||
|
|
abcc4c0a12 | ||
|
|
ceb3a19b81 | ||
|
|
2a2f7825cc | ||
|
|
a0e9f6a92d | ||
|
|
dbc73f5086 | ||
|
|
78a67b754e | ||
|
|
de4b02eaf1 | ||
|
|
751c82fd29 | ||
|
|
8c8a94fc71 | ||
|
|
1174958e8b | ||
|
|
6399de7a66 | ||
|
|
c0f2275b61 | ||
|
|
256a9ee45d | ||
|
|
c835ce780a | ||
|
|
d7b7d2de6e | ||
|
|
1dd0635e5e | ||
|
|
67506511c3 | ||
|
|
3fbb4cde36 | ||
|
|
9aaa80a213 | ||
|
|
acb6cbffa0 | ||
|
|
6a70ab74bc | ||
|
|
852c252302 | ||
|
|
3a670b55b6 | ||
|
|
d01435f4f2 | ||
|
|
f1638c9cd7 | ||
|
|
4943504898 | ||
|
|
7d7480c120 | ||
|
|
78182fea0a | ||
|
|
947e57b5b4 | ||
|
|
e0e4a594e9 | ||
|
|
4839fe37a3 | ||
|
|
9914b7ea38 | ||
|
|
f86ed97820 | ||
|
|
8d8b0807e2 | ||
|
|
e3c6237430 | ||
|
|
e964c7fa5c | ||
|
|
f1e84e145c | ||
|
|
2e2773fa6b | ||
|
|
a9c7a27d47 | ||
|
|
e41ecb19cf | ||
|
|
5b091b602f | ||
|
|
768681c4f2 | ||
|
|
2e4e5c1873 | ||
|
|
4a61806e60 | ||
|
|
883187f9ac | ||
|
|
2d9a5031e9 | ||
|
|
39c93f6512 | ||
|
|
a7905b373e | ||
|
|
a7c82ff9b9 | ||
|
|
5b4b4a4051 | ||
|
|
c348fec609 | ||
|
|
4af17356f3 | ||
|
|
384e5052bc | ||
|
|
a5adae1491 | ||
|
|
fe62b810db | ||
|
|
ee78496058 | ||
|
|
8afe4bae87 | ||
|
|
b04bb2b740 | ||
|
|
3d46f0d72f | ||
|
|
a65d973660 | ||
|
|
df83d90c06 | ||
|
|
a1d55f2529 | ||
|
|
aa097f3fd6 | ||
|
|
e0b72202fd | ||
|
|
e8769fff7d | ||
|
|
ed33652534 | ||
|
|
d1c1f972a6 | ||
|
|
6008275aae | ||
|
|
edf8bb3945 | ||
|
|
dd7d133263 | ||
|
|
b6f783674b | ||
|
|
eab70fae3b | ||
|
|
fed40ef104 | ||
|
|
6d087f5a38 | ||
|
|
0edcf33547 | ||
|
|
443417b0f4 | ||
|
|
369e994b0d | ||
|
|
55469327c6 | ||
|
|
27f326673c | ||
|
|
e6fd766fff | ||
|
|
7da3ccfacb | ||
|
|
624d6b3b0b | ||
|
|
9528083a66 | ||
|
|
55408f6ccb | ||
|
|
dce5a39b10 | ||
|
|
03a23876a7 | ||
|
|
775357dd94 | ||
|
|
d10cbb2823 | ||
|
|
63a2465bac | ||
|
|
d97ed52e91 | ||
|
|
e1dc12c14d | ||
|
|
7c755d4e2d | ||
|
|
55b786d9f0 | ||
|
|
131f4be4ea | ||
|
|
d819617d2b | ||
|
|
b9219a2b62 | ||
|
|
554e378dd6 | ||
|
|
cc11402bc9 | ||
|
|
40220f92c1 | ||
|
|
8c4d9021c2 | ||
|
|
efefb02d86 | ||
|
|
3ee281aaf9 | ||
|
|
097b6d5097 | ||
|
|
6cdaf05f98 | ||
|
|
3be0f58c30 | ||
|
|
f3489a3b01 | ||
|
|
173dbeb24a | ||
|
|
0607b86818 | ||
|
|
1282a65bcb | ||
|
|
45d3207dfe | ||
|
|
76b46f59e9 | ||
|
|
19fa108f61 | ||
|
|
2372d06591 | ||
|
|
7015375892 | ||
|
|
e9bf2b361f | ||
|
|
51b790b767 | ||
|
|
ac84431361 | ||
|
|
7dc8463da9 | ||
|
|
71ae579bc0 | ||
|
|
5036d25b60 | ||
|
|
ff6d169862 | ||
|
|
dde8898aae | ||
|
|
72cc23ef46 | ||
|
|
5390b4ed42 | ||
|
|
17c7d90d52 | ||
|
|
5c3b5be613 | ||
|
|
5ab7769745 | ||
|
|
05374d1145 | ||
|
|
311e10f91e | ||
|
|
2b94791387 | ||
|
|
fbcae11cd0 | ||
|
|
0d6eff2a9a | ||
|
|
6a9b7fdb6d | ||
|
|
e8f703648a | ||
|
|
710f82de0f | ||
|
|
bee35acfa6 | ||
|
|
90fdaf80e4 | ||
|
|
27feb1ddd7 | ||
|
|
2be7e0f7e6 | ||
|
|
186ab70bf9 | ||
|
|
0fa9006e45 | ||
|
|
60c83bae93 | ||
|
|
553c398c8e | ||
|
|
1c90bb383f | ||
|
|
4281225b02 | ||
|
|
14dc9c6c43 | ||
|
|
c9e10b1a3e | ||
|
|
915c850760 | ||
|
|
2c3f430203 | ||
|
|
1a152ed7fa | ||
|
|
5953480807 | ||
|
|
b5c1a195be | ||
|
|
310cc086c6 | ||
|
|
61d6cd3c18 | ||
|
|
cccabf5330 | ||
|
|
6f33460afd | ||
|
|
603d524aaf | ||
|
|
eb2cd001b6 | ||
|
|
b5b57790be | ||
|
|
286f4ef961 | ||
|
|
ad28d1906c | ||
|
|
dfdccac67d | ||
|
|
b8c2752237 | ||
|
|
834c7b0def | ||
|
|
5bfe70142e | ||
|
|
b35c5a22bb | ||
|
|
eecc825c90 | ||
|
|
3823c22dad | ||
|
|
551bd3dbfe | ||
|
|
7e9956963a | ||
|
|
80c24a1993 | ||
|
|
66091bae24 | ||
|
|
73d4f10f4b | ||
|
|
ee7ea77fc3 | ||
|
|
32e1cbe2a3 | ||
|
|
3539724843 | ||
|
|
940b25f158 | ||
|
|
37e601e5b5 | ||
|
|
0230374709 | ||
|
|
86db237e5d | ||
|
|
1542b1cebb | ||
|
|
990071af5c | ||
|
|
f543e00307 | ||
|
|
34b4f8265a | ||
|
|
a366d1af2a | ||
|
|
ebe5785a91 | ||
|
|
887d46725b | ||
|
|
a326b718f2 | ||
|
|
c14b298cb9 | ||
|
|
9cca8f3f55 | ||
|
|
f5cee56740 | ||
|
|
972d183d85 | ||
|
|
eebdf04357 | ||
|
|
9ede20a367 | ||
|
|
b0c3e22a52 | ||
|
|
a78db17784 | ||
|
|
dbb9998f69 | ||
|
|
2745dbd124 | ||
|
|
c0357daf01 | ||
|
|
064fa6027d | ||
|
|
779e02a05e | ||
|
|
e222d0356a | ||
|
|
d2ae333bb8 | ||
|
|
764c42a810 | ||
|
|
18b18f1c3d | ||
|
|
b54a8b40a4 | ||
|
|
edf724d20d | ||
|
|
622a190a61 | ||
|
|
5b4a78ba20 | ||
|
|
44b85f6e4b | ||
|
|
7f1f760645 | ||
|
|
54d8c96c30 | ||
|
|
c6ab7827e7 | ||
|
|
ae26079e2e | ||
|
|
3e993156f2 | ||
|
|
3b2fafd789 | ||
|
|
72ebaddcb8 | ||
|
|
5a9950cc19 | ||
|
|
cf29d7e400 | ||
|
|
244dba3614 | ||
|
|
21886517e1 | ||
|
|
3996236729 | ||
|
|
560cb3ac82 | ||
|
|
81c7e23ae9 | ||
|
|
0b8bd6d4fc | ||
|
|
7c271c8207 | ||
|
|
58947d91a6 | ||
|
|
20096be990 | ||
|
|
7c8508b651 | ||
|
|
b56d0fdd9b | ||
|
|
d0cc06f766 | ||
|
|
d8d2b215d1 | ||
|
|
c478d383b4 | ||
|
|
e01cd1c037 | ||
|
|
e63019c469 | ||
|
|
698594525f |
22
.github/workflows/core.yml
vendored
22
.github/workflows/core.yml
vendored
@@ -8,29 +8,36 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
python-version: [3.6, 3.7, 3.8, 3.9]
|
||||
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
|
||||
exclude:
|
||||
- os: macos-latest
|
||||
python-version: "3.6"
|
||||
- os: windows-latest
|
||||
python-version: "3.10"
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v1
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install tox
|
||||
|
||||
- name: Python Lint
|
||||
if: ${{ matrix.python-version != '3.6' }}
|
||||
run: |
|
||||
tox -e lint
|
||||
|
||||
- name: Integration Tests
|
||||
env:
|
||||
TEST_EMAIL_LOGIN: ${{ secrets.TEST_EMAIL_LOGIN }}
|
||||
TEST_EMAIL_PASSWORD: ${{ secrets.TEST_EMAIL_PASSWORD }}
|
||||
TEST_EMAIL_IMAP_SERVER: ${{ secrets.TEST_EMAIL_IMAP_SERVER }}
|
||||
run: |
|
||||
tox -e testcore
|
||||
|
||||
@@ -42,3 +49,4 @@ jobs:
|
||||
job_name: '*Core*'
|
||||
commit: true
|
||||
url: ${{ secrets.SLACK_BUILD_WEBHOOK }}
|
||||
token: ${{ secrets.SLACK_GITHUB_TOKEN }}
|
||||
|
||||
44
.github/workflows/deployment.yml
vendored
Normal file
44
.github/workflows/deployment.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
name: Deployment
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "release/**"
|
||||
|
||||
jobs:
|
||||
deployment:
|
||||
runs-on: ubuntu-latest
|
||||
environment: production
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.9"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install tox
|
||||
|
||||
- name: Deployment Tests
|
||||
env:
|
||||
TEST_EMAIL_LOGIN: ${{ secrets.TEST_EMAIL_LOGIN }}
|
||||
TEST_EMAIL_PASSWORD: ${{ secrets.TEST_EMAIL_PASSWORD }}
|
||||
TEST_EMAIL_IMAP_SERVER: ${{ secrets.TEST_EMAIL_IMAP_SERVER }}
|
||||
run: |
|
||||
tox -e testcore
|
||||
|
||||
- name: Build Python source tarball
|
||||
run: python setup.py sdist
|
||||
|
||||
- name: Publish package to PyPI
|
||||
if: ${{ github.ref == 'refs/heads/master' }}
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
81
.github/workflows/docs.yml
vendored
81
.github/workflows/docs.yml
vendored
@@ -4,13 +4,14 @@ on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: "recursive"
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v1
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.7
|
||||
- name: Install dependencies
|
||||
@@ -29,4 +30,80 @@ jobs:
|
||||
type: ${{ job.status }}
|
||||
job_name: '*Docs*'
|
||||
commit: true
|
||||
url: ${{ secrets.SLACK_BUILD_WEBHOOK }}
|
||||
url: ${{ secrets.SLACK_BUILD_WEBHOOK }}
|
||||
token: ${{ secrets.SLACK_GITHUB_TOKEN }}
|
||||
|
||||
- name: Preserve Docs
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
run: |
|
||||
tar -czvf docs.tar.gz -C docs/_build html rtdpage
|
||||
|
||||
- name: Save artifact
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: docs
|
||||
path: ./docs.tar.gz
|
||||
|
||||
deploy:
|
||||
name: Deploy Docs
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOCS_REPO: platformio/platformio-docs
|
||||
DOCS_DIR: platformio-docs
|
||||
LATEST_DOCS_DIR: latest-docs
|
||||
RELEASE_BUILD: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: docs
|
||||
- name: Unpack artifact
|
||||
run: |
|
||||
mkdir ./${{ env.LATEST_DOCS_DIR }}
|
||||
tar -xzf ./docs.tar.gz -C ./${{ env.LATEST_DOCS_DIR }}
|
||||
- name: Delete Artifact
|
||||
uses: geekyeggo/delete-artifact@v1
|
||||
with:
|
||||
name: docs
|
||||
- name: Select Docs type
|
||||
id: get-destination-dir
|
||||
run: |
|
||||
if [[ ${{ env.RELEASE_BUILD }} == true ]]; then
|
||||
echo "::set-output name=dst_dir::stable"
|
||||
else
|
||||
echo "::set-output name=dst_dir::latest"
|
||||
fi
|
||||
- name: Checkout latest Docs
|
||||
continue-on-error: true
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: ${{ env.DOCS_REPO }}
|
||||
path: ${{ env.DOCS_DIR }}
|
||||
ref: gh-pages
|
||||
- name: Synchronize Docs
|
||||
run: |
|
||||
rm -rf ${{ env.DOCS_DIR }}/.git
|
||||
rm -rf ${{ env.DOCS_DIR }}/en/${{ steps.get-destination-dir.outputs.dst_dir }}
|
||||
mkdir -p ${{ env.DOCS_DIR }}/en/${{ steps.get-destination-dir.outputs.dst_dir }}
|
||||
cp -rf ${{ env.LATEST_DOCS_DIR }}/html/* ${{ env.DOCS_DIR }}/en/${{ steps.get-destination-dir.outputs.dst_dir }}
|
||||
if [[ ${{ env.RELEASE_BUILD }} == false ]]; then
|
||||
rm -rf ${{ env.DOCS_DIR }}/page
|
||||
mkdir -p ${{ env.DOCS_DIR }}/page
|
||||
cp -rf ${{ env.LATEST_DOCS_DIR }}/rtdpage/* ${{ env.DOCS_DIR }}/page
|
||||
fi
|
||||
- name: Validate Docs
|
||||
run: |
|
||||
if [ -z "$(ls -A ${{ env.DOCS_DIR }})" ]; then
|
||||
echo "Docs folder is empty. Aborting!"
|
||||
exit 1
|
||||
fi
|
||||
- name: Deploy to Github Pages
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
personal_token: ${{ secrets.DEPLOY_GH_DOCS_TOKEN }}
|
||||
external_repository: ${{ env.DOCS_REPO }}
|
||||
publish_dir: ./${{ env.DOCS_DIR }}
|
||||
commit_message: Sync Docs
|
||||
|
||||
30
.github/workflows/examples.yml
vendored
30
.github/workflows/examples.yml
vendored
@@ -2,22 +2,28 @@ name: Examples
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-16.04, windows-latest, macos-latest]
|
||||
python-version: [3.7]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
PIO_INSTALL_DEVPLATFORM_OWNERNAMES: "platformio"
|
||||
PIO_INSTALL_DEVPLATFORM_NAMES: "aceinna_imu,atmelavr,atmelmegaavr,atmelsam,espressif32,espressif8266,nordicnrf52,raspberrypi,ststm32,teensy"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: "recursive"
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
python-version: "3.9"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
@@ -25,24 +31,15 @@ jobs:
|
||||
|
||||
- name: Run on Linux
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
env:
|
||||
PIO_INSTALL_DEVPLATFORMS_OWNERNAMES: "platformio"
|
||||
PIO_INSTALL_DEVPLATFORMS_IGNORE: "ststm8,infineonxmc,intel_mcs51"
|
||||
run: |
|
||||
# ChipKIT issue: install 32-bit support for GCC PIC32
|
||||
sudo apt-get install libc6-i386
|
||||
# Free space
|
||||
sudo apt clean
|
||||
docker rmi $(docker image ls -aq)
|
||||
df -h
|
||||
# Run
|
||||
tox -e testexamples
|
||||
|
||||
- name: Run on macOS
|
||||
if: startsWith(matrix.os, 'macos')
|
||||
env:
|
||||
PIO_INSTALL_DEVPLATFORMS_OWNERNAMES: "platformio"
|
||||
PIO_INSTALL_DEVPLATFORMS_IGNORE: "ststm8,infineonxmc,microchippic32,lattice_ice40,gd32v"
|
||||
run: |
|
||||
df -h
|
||||
tox -e testexamples
|
||||
@@ -52,8 +49,6 @@ jobs:
|
||||
env:
|
||||
PLATFORMIO_CORE_DIR: C:/pio
|
||||
PLATFORMIO_WORKSPACE_DIR: C:/pio-workspace/$PROJECT_HASH
|
||||
PIO_INSTALL_DEVPLATFORMS_OWNERNAMES: "platformio"
|
||||
PIO_INSTALL_DEVPLATFORMS_IGNORE: "ststm8,infineonxmc,riscv_gap"
|
||||
run: |
|
||||
tox -e testexamples
|
||||
|
||||
@@ -65,3 +60,4 @@ jobs:
|
||||
job_name: '*Examples*'
|
||||
commit: true
|
||||
url: ${{ secrets.SLACK_BUILD_WEBHOOK }}
|
||||
token: ${{ secrets.SLACK_GITHUB_TOKEN }}
|
||||
|
||||
69
.github/workflows/projects.yml
vendored
Normal file
69
.github/workflows/projects.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: Projects
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
project:
|
||||
- marlin:
|
||||
repository: "MarlinFirmware/Marlin"
|
||||
folder: "Marlin"
|
||||
config_dir: "Marlin"
|
||||
env_name: "mega2560"
|
||||
# - esphome:
|
||||
# repository: "esphome/esphome"
|
||||
# folder: "esphome"
|
||||
# config_dir: "esphome"
|
||||
# env_name: "esp32-arduino"
|
||||
- smartknob:
|
||||
repository: "scottbez1/smartknob"
|
||||
folder: "smartknob"
|
||||
config_dir: "smartknob/firmware"
|
||||
env_name: "view"
|
||||
- espurna:
|
||||
repository: "xoseperez/espurna"
|
||||
folder: "espurna"
|
||||
config_dir: "espurna/code"
|
||||
env_name: "nodemcu-lolin"
|
||||
- OpenMQTTGateway:
|
||||
repository: "1technophile/OpenMQTTGateway"
|
||||
folder: "OpenMQTTGateway"
|
||||
config_dir: "OpenMQTTGateway"
|
||||
env_name: "esp32-m5atom"
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
exclude:
|
||||
- os: windows-latest
|
||||
project: {"esphome": "", "repository": "esphome/esphome", "folder": "esphome", "config_dir": "esphome", "env_name": "esp32-arduino"}
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: 3.9
|
||||
|
||||
- name: Install PlatformIO
|
||||
run: pip install -U .
|
||||
|
||||
- name: Check out ${{ matrix.project.repository }}
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: "recursive"
|
||||
repository: ${{ matrix.project.repository }}
|
||||
path: ${{ matrix.project.folder }}
|
||||
|
||||
- name: Install ESPHome dependencies
|
||||
# Requires esptool package as it's used in a custom prescript
|
||||
if: ${{ contains(matrix.project.repository, 'esphome') }}
|
||||
run: pip install esptool==3.*
|
||||
|
||||
- name: Compile ${{ matrix.project.repository }}
|
||||
run: pio run -d ${{ matrix.project.config_dir }} -e ${{ matrix.project.env_name }}
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,6 @@
|
||||
*.egg-info
|
||||
*.pyc
|
||||
.pioenvs
|
||||
__pycache__
|
||||
.tox
|
||||
docs/_build
|
||||
dist
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
[settings]
|
||||
line_length=88
|
||||
known_third_party=OpenSSL, SCons, jsonrpc, twisted, zope
|
||||
16
.pylintrc
16
.pylintrc
@@ -3,19 +3,9 @@ output-format=colorized
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
disable=
|
||||
bad-continuation,
|
||||
bad-whitespace,
|
||||
missing-docstring,
|
||||
ungrouped-imports,
|
||||
invalid-name,
|
||||
cyclic-import,
|
||||
duplicate-code,
|
||||
superfluous-parens,
|
||||
invalid-name,
|
||||
too-few-public-methods,
|
||||
useless-object-inheritance,
|
||||
useless-import-alias,
|
||||
bad-option-value,
|
||||
|
||||
; PY2 Compat
|
||||
super-with-arguments,
|
||||
raise-missing-from
|
||||
consider-using-f-string,
|
||||
cyclic-import
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/index.html
|
||||
|
||||
version: 2
|
||||
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
||||
formats:
|
||||
- pdf
|
||||
|
||||
submodules:
|
||||
include: all
|
||||
@@ -3,7 +3,7 @@ Contributing
|
||||
|
||||
To get started, <a href="https://cla-assistant.io/platformio/platformio-core">sign the Contributor License Agreement</a>.
|
||||
|
||||
1. Fork the repository on GitHub.
|
||||
1. Fork the repository on GitHub
|
||||
2. Clone repository `git clone --recursive https://github.com/YourGithubUsername/platformio-core.git`
|
||||
3. Run `pip install tox`
|
||||
4. Go to the root of project where is located `tox.ini` and run `tox -e py37`
|
||||
@@ -18,4 +18,4 @@ To get started, <a href="https://cla-assistant.io/platformio/platformio-core">si
|
||||
8. Run the tests `make test`
|
||||
9. Build documentation `tox -e docs` (creates a directory _build under docs where you can find the html)
|
||||
10. Commit changes to your forked repository
|
||||
11. Submit a Pull Request on GitHub.
|
||||
11. Submit a Pull Request on GitHub
|
||||
|
||||
368
HISTORY.rst
368
HISTORY.rst
@@ -1,213 +1,215 @@
|
||||
Release Notes
|
||||
=============
|
||||
|
||||
.. |PIOCONF| replace:: `"platformio.ini" <https://docs.platformio.org/en/latest/projectconf.html>`__ configuration file
|
||||
.. |LDF| replace:: `LDF <https://docs.platformio.org/en/latest/librarymanager/ldf.html>`__
|
||||
.. |INTERPOLATION| replace:: `Interpolation of Values <https://docs.platformio.org/en/latest/projectconf/interpolation.html>`__
|
||||
.. |UNITTESTING| replace:: `Unit Testing <https://docs.platformio.org/en/latest/advanced/unit-testing/index.html>`__
|
||||
|
||||
.. _release_notes_6:
|
||||
|
||||
PlatformIO Core 6
|
||||
-----------------
|
||||
|
||||
**A professional collaborative platform for declarative, safety-critical, and test-driven embedded development.**
|
||||
|
||||
6.1.1 (2022-07-11)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Added new ``monitor_encoding`` project configuration option to configure `Device Monitor <https://docs.platformio.org/en/latest/core/userguide/device/cmd_monitor.html>`__ (`issue #4350 <https://github.com/platformio/platformio-core/issues/4350>`_)
|
||||
* Allowed specifying project environments for `pio ci <https://docs.platformio.org/en/latest/core/userguide/cmd_ci.html>`__ command (`issue #4347 <https://github.com/platformio/platformio-core/issues/4347>`_)
|
||||
* Show "TimeoutError" only in the verbose mode when can not find a serial port
|
||||
* Fixed an issue when a serial port was not automatically detected if the board has predefined HWIDs
|
||||
* Fixed an issue with endless scanning of project dependencies (`issue #4349 <https://github.com/platformio/platformio-core/issues/4349>`_)
|
||||
* Fixed an issue with |LDF| when incompatible libraries were used for the working project environment with the missed framework (`pull #4346 <https://github.com/platformio/platformio-core/pull/4346>`_)
|
||||
|
||||
6.1.0 (2022-07-06)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* **Device Manager**
|
||||
|
||||
- Automatically reconnect device monitor if a connection fails
|
||||
- Added new `pio device monitor --no-reconnect <https://docs.platformio.org/en/latest/core/userguide/device/cmd_monitor.html#cmdoption-pio-device-monitor-no-reconnect>`__ option to disable automatic reconnection
|
||||
- Handle device monitor disconnects more gracefully (`issue #3939 <https://github.com/platformio/platformio-core/issues/3939>`_)
|
||||
- Improved a serial port finder for `Black Magic Probe <https://docs.platformio.org/en/latest/plus/debug-tools/blackmagic.html>`__ (`issue #4023 <https://github.com/platformio/platformio-core/issues/4023>`_)
|
||||
- Improved a serial port finder for a board with predefined HWIDs
|
||||
- Replaced ``monitor_flags`` with independent project configuration options: `monitor_parity <https://docs.platformio.org/en/latest/projectconf/section_env_monitor.html#monitor-parity>`__, `monitor_eol <https://docs.platformio.org/en/latest/projectconf/section_env_monitor.html#monitor-eol>`__, `monitor_raw <https://docs.platformio.org/en/latest/projectconf/section_env_monitor.html#monitor-raw>`__, `monitor_echo <https://docs.platformio.org/en/latest/projectconf/section_env_monitor.html#monitor-echo>`__
|
||||
- Fixed an issue when the monitor filters were not applied in their order (`issue #4320 <https://github.com/platformio/platformio-core/issues/4320>`_)
|
||||
|
||||
* **Unit Testing**
|
||||
|
||||
- Updated "Getting Started" documentation for `GoogleTest <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/googletest.html>`__ testing and mocking framework
|
||||
- Export |UNITTESTING| flags only to the project build environment (``projenv``, files in "src" folder)
|
||||
- Merged the "building" stage with "uploading" for the embedded target (`issue #4307 <https://github.com/platformio/platformio-core/issues/4307>`_)
|
||||
- Do not resolve dependencies from the project "src" folder when the `test_build_src <https://docs.platformio.org/en/latest//projectconf/section_env_test.html#test-build-src>`__ option is not enabled
|
||||
- Do not immediately terminate a testing program when results are received
|
||||
- Fixed an issue when a custom `pio test --project-config <https://docs.platformio.org/en/latest/core/userguide/cmd_test.html#cmdoption-pio-test-c>`__ was not handled properly (`issue #4299 <https://github.com/platformio/platformio-core/issues/4299>`_)
|
||||
- Fixed an issue when testing results were wrong in the verbose mode (`issue #4336 <https://github.com/platformio/platformio-core/issues/4336>`_)
|
||||
|
||||
* **Build System**
|
||||
|
||||
- Significantly improved support for `Pre & Post Actions <https://docs.platformio.org/en/latest/scripting/actions.html>`__
|
||||
|
||||
* Allowed to declare actions in the `PRE-type scripts <https://docs.platformio.org/en/latest/scripting/launch_types.html>`__ even if the target is not ready yet
|
||||
* Allowed library maintainers to use Pre & Post Actions in the library `extraScript <https://docs.platformio.org/en/latest/manifests/library-json/fields/build/extrascript.html>`__
|
||||
|
||||
- Documented `Stringification <https://docs.platformio.org/en/latest/projectconf/section_env_build.html#stringification>`__ – converting a macro argument into a string constant (`issue #4310 <https://github.com/platformio/platformio-core/issues/4310>`_)
|
||||
- Added new `pio run --monitor-port <https://docs.platformio.org/en/latest/core/userguide/cmd_run.html#cmdoption-pio-run-monitor-port>`__ option to specify custom device monitor port to the ``monitor`` target (`issue #4337 <https://github.com/platformio/platformio-core/issues/4337>`_)
|
||||
- Added ``env.StringifyMacro(value)`` helper function for the `Advanced Scripting <https://docs.platformio.org/en/latest/scripting/index.html>`__
|
||||
- Allowed to ``Import("projenv")`` in a library extra script (`issue #4305 <https://github.com/platformio/platformio-core/issues/4305>`_)
|
||||
- Fixed an issue when the `build_unflags <https://docs.platformio.org/en/latest/projectconf/section_env_build.html#build-unflags>`__ operation ignores a flag value (`issue #4309 <https://github.com/platformio/platformio-core/issues/4309>`_)
|
||||
- Fixed an issue when the `build_unflags <https://docs.platformio.org/en/latest/projectconf/section_env_build.html#build-unflags>`__ option was not applied to the ``ASPPFLAGS`` scope
|
||||
- Fixed an issue on Windows OS when flags were wrapped to the temporary file while generating the `Compilation database "compile_commands.json" <https://docs.platformio.org/en/latest/integration/compile_commands.html>`__
|
||||
- Fixed an issue with the |LDF| when recursively scanning dependencies in the ``chain`` mode
|
||||
- Fixed a "PermissionError" on Windows when running "clean" or "cleanall" targets (`issue #4331 <https://github.com/platformio/platformio-core/issues/4331>`_)
|
||||
|
||||
* **Package Management**
|
||||
|
||||
- Fixed an issue when library dependencies were installed for the incompatible project environment (`issue #4338 <https://github.com/platformio/platformio-core/issues/4338>`_)
|
||||
|
||||
* **Miscellaneous**
|
||||
|
||||
- Warn about incompatible Bash version for the `Shell Completion <https://docs.platformio.org/en/latest/core/userguide/system/completion/index.html>`__ (`issue #4326 <https://github.com/platformio/platformio-core/issues/4326>`_)
|
||||
|
||||
6.0.2 (2022-06-01)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Control |UNITTESTING| verbosity with a new multilevel `pio test -v <https://docs.platformio.org/en/latest/core/userguide/cmd_test.html#cmdoption-pio-test-v>`__ command option (`issue #4276 <https://github.com/platformio/platformio-core/issues/4276>`_)
|
||||
* Follow symbolic links during searching for the unit test suites (`issue #4288 <https://github.com/platformio/platformio-core/issues/4288>`_)
|
||||
* Show a warning when testing an empty project without a test suite (`issue #4278 <https://github.com/platformio/platformio-core/issues/4278>`_)
|
||||
* Improved support for `Asking for input (prompts) <https://docs.platformio.org/en/latest/scripting/examples/asking_for_input.html>`_
|
||||
* Fixed an issue when the `build_src_flags <https://docs.platformio.org/en/latest/projectconf/section_env_build.html#build-src-flags>`__ option was applied outside the project scope (`issue #4277 <https://github.com/platformio/platformio-core/issues/4277>`_)
|
||||
* Fixed an issue with debugging assembly files without preprocessor (".s")
|
||||
|
||||
6.0.1 (2022-05-17)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Improved support for the renamed configuration options (`issue #4270 <https://github.com/platformio/platformio-core/issues/4270>`_)
|
||||
* Fixed an issue when calling the built-in `pio device monitor <https://docs.platformio.org/en/latest/core/userguide/device/cmd_monitor.html#filters>`__ filters
|
||||
* Fixed an issue when using |INTERPOLATION| and merging str+int options (`issue #4271 <https://github.com/platformio/platformio-core/issues/4271>`_)
|
||||
|
||||
6.0.0 (2022-05-16)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Please check the `Migration guide from 5.x to 6.0 <https://docs.platformio.org/en/latest/core/migration.html>`__.
|
||||
|
||||
* **Package Management**
|
||||
|
||||
- New unified Package Management CLI (``pio pkg``):
|
||||
|
||||
* `pio pkg exec <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_exec.html>`_ - run command from package tool (`issue #4163 <https://github.com/platformio/platformio-core/issues/4163>`_)
|
||||
* `pio pkg install <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_install.html>`_ - install the project dependencies or custom packages
|
||||
* `pio pkg list <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_list.html>`__ - list installed packages
|
||||
* `pio pkg outdated <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_outdated.html>`__ - check for project outdated packages
|
||||
* `pio pkg search <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_search.html>`__ - search for packages
|
||||
* `pio pkg show <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_show.html>`__ - show package information
|
||||
* `pio pkg uninstall <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_uninstall.html>`_ - uninstall the project dependencies or custom packages
|
||||
* `pio pkg update <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_update.html>`__ - update the project dependencies or custom packages
|
||||
|
||||
- Package Manifest
|
||||
|
||||
* Added support for `"scripts" <https://docs.platformio.org/en/latest/librarymanager/config.html#scripts>`__ (`issue #485 <https://github.com/platformio/platformio-core/issues/485>`_)
|
||||
* Added support for `multi-licensed <https://docs.platformio.org/en/latest/librarymanager/config.html#license>`__ packages using SPDX Expressions (`issue #4037 <https://github.com/platformio/platformio-core/issues/4037>`_)
|
||||
* Added support for `"dependencies" <https://docs.platformio.org/en/latest/librarymanager/config.html#dependencies>`__ declared in a "tool" package manifest
|
||||
|
||||
- Added support for `symbolic links <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_install.html#local-folder>`__ allowing pointing the local source folder to the Package Manager (`issue #3348 <https://github.com/platformio/platformio-core/issues/3348>`_)
|
||||
- Automatically install dependencies of the local (private) project libraries (`issue #2910 <https://github.com/platformio/platformio-core/issues/2910>`_)
|
||||
- Improved detection of a package type from the tarball archive (`issue #3828 <https://github.com/platformio/platformio-core/issues/3828>`_)
|
||||
- Ignore files according to the patterns declared in ".gitignore" when using the `pio package pack <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_pack.html>`__ command (`issue #4188 <https://github.com/platformio/platformio-core/issues/4188>`_)
|
||||
- Dropped automatic updates of global libraries and development platforms (`issue #4179 <https://github.com/platformio/platformio-core/issues/4179>`_)
|
||||
- Dropped support for the "pythonPackages" field in "platform.json" manifest in favor of `Extra Python Dependencies <https://docs.platformio.org/en/latest/scripting/examples/extra_python_packages.html>`__
|
||||
- Fixed an issue when manually removed dependencies from the |PIOCONF| were not uninstalled from the storage (`issue #3076 <https://github.com/platformio/platformio-core/issues/3076>`_)
|
||||
|
||||
* **Unit Testing**
|
||||
|
||||
- Refactored from scratch |UNITTESTING| solution and its documentation
|
||||
- New: `Test Hierarchy <https://docs.platformio.org/en/latest/advanced/unit-testing/structure.html>`_ (`issue #4135 <https://github.com/platformio/platformio-core/issues/4135>`_)
|
||||
- New: `Doctest <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/doctest.html>`__ testing framework (`issue #4240 <https://github.com/platformio/platformio-core/issues/4240>`_)
|
||||
- New: `GoogleTest <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/googletest.html>`__ testing and mocking framework (`issue #3572 <https://github.com/platformio/platformio-core/issues/3572>`_)
|
||||
- New: `Semihosting <https://docs.platformio.org/en/latest/advanced/unit-testing/semihosting.html>`__ (`issue #3516 <https://github.com/platformio/platformio-core/issues/3516>`_)
|
||||
- New: Hardware `Simulators <https://docs.platformio.org/en/latest/advanced/unit-testing/simulators/index.html>`__ for Unit Testing (QEMU, Renode, SimAVR, and custom solutions)
|
||||
- New: ``test`` `build configuration <https://docs.platformio.org/en/latest/projectconf/build_configurations.html>`__
|
||||
- Added support for a `custom testing framework <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/custom/index.html>`_
|
||||
- Added support for a custom `testing command <https://docs.platformio.org/en/latest/projectconf/section_env_test.html#test-testing-command>`__
|
||||
- Added support for a `custom Unity library <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/custom/examples/custom_unity_library.html>`__ (`issue #3980 <https://github.com/platformio/platformio-core/issues/3980>`_)
|
||||
- Added support for the ``socket://`` and ``rfc2217://`` protocols using `test_port <https://docs.platformio.org/en/latest/projectconf/section_env_test.html#test-port>`__ option (`issue #4229 <https://github.com/platformio/platformio-core/issues/4229>`_)
|
||||
- List available project tests with a new `pio test --list-tests <https://docs.platformio.org/en/latest/core/userguide/cmd_test.html#cmdoption-pio-test-list-tests>`__ option
|
||||
- Pass extra arguments to the testing program with a new `pio test --program-arg <https://docs.platformio.org/en/latest/core/userguide/cmd_test.html#cmdoption-pio-test-a>`__ option (`issue #3132 <https://github.com/platformio/platformio-core/issues/3132>`_)
|
||||
- Generate reports in JUnit and JSON formats using the `pio test <https://docs.platformio.org/en/latest/core/userguide/cmd_test.html>`__ command (`issue #2891 <https://github.com/platformio/platformio-core/issues/2891>`_)
|
||||
- Provide more information when the native program crashed on a host (errored with a non-zero return code) (`issue #3429 <https://github.com/platformio/platformio-core/issues/3429>`_)
|
||||
- Improved automatic detection of a testing serial port (`issue #4076 <https://github.com/platformio/platformio-core/issues/4076>`_)
|
||||
- Fixed an issue when command line parameters (``--ignore``, ``--filter``) do not override values defined in the |PIOCONF| (`issue #3845 <https://github.com/platformio/platformio-core/issues/3845>`_)
|
||||
- Renamed the "test_build_project_src" project configuration option to the `test_build_src <https://docs.platformio.org/en/latest//projectconf/section_env_test.html#test-build-src>`__
|
||||
- Removed the "test_transport" option in favor of the `Custom "unity_config.h" <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/unity.html>`_
|
||||
|
||||
* **Static Code Analysis**
|
||||
|
||||
- Updated analysis tools:
|
||||
|
||||
* `Cppcheck <https://docs.platformio.org/en/latest/plus/check-tools/cppcheck.html>`__ v2.7 with various checker improvements and fixed false positives
|
||||
* `PVS-Studio <https://docs.platformio.org/en/latest/plus/check-tools/pvs-studio.html>`__ v7.18 with improved and updated semantic analysis system
|
||||
|
||||
- Added support for the custom `Clang-Tidy <https://docs.platformio.org/en/latest/plus/check-tools/clang-tidy.html>`__ configuration file (`issue #4186 <https://github.com/platformio/platformio-core/issues/4186>`_)
|
||||
- Added ability to override a tool version using the `platform_packages <https://docs.platformio.org/en/latest/projectconf/section_env_platform.html#platform-packages>`__ option (`issue #3798 <https://github.com/platformio/platformio-core/issues/3798>`_)
|
||||
- Fixed an issue with improper handling of defects that don't specify a source file (`issue #4237 <https://github.com/platformio/platformio-core/issues/4237>`_)
|
||||
|
||||
* **Build System**
|
||||
|
||||
- Show project dependency licenses when building in the verbose mode
|
||||
- Fixed an issue when |LDF| ignores the project `lib_deps <https://docs.platformio.org/en/latest/projectconf/section_env_library.html#lib-deps>`__ while resolving library dependencies (`issue #3598 <https://github.com/platformio/platformio-core/issues/3598>`_)
|
||||
- Fixed an issue with calling an extra script located outside a project (`issue #4220 <https://github.com/platformio/platformio-core/issues/4220>`_)
|
||||
- Fixed an issue when GCC preprocessor was applied to the ".s" assembly files on case-sensitive OS such as Window OS (`issue #3917 <https://github.com/platformio/platformio-core/issues/3917>`_)
|
||||
- Fixed an issue when |LDF| ignores `build_src_flags <https://docs.platformio.org/en/latest/projectconf/section_env_build.html#build-src-flags>`__ in the "deep+" mode (`issue #4253 <https://github.com/platformio/platformio-core/issues/4253>`_)
|
||||
|
||||
* **Integration**
|
||||
|
||||
- Added a new build variable (``COMPILATIONDB_INCLUDE_TOOLCHAIN``) to include toolchain paths in the compilation database (`issue #3735 <https://github.com/platformio/platformio-core/issues/3735>`_)
|
||||
- Changed a default path for compilation database `compile_commands.json <https://docs.platformio.org/en/latest/integration/compile_commands.html>`__ to the project root
|
||||
- Enhanced integration for Qt Creator (`issue #3046 <https://github.com/platformio/platformio-core/issues/3046>`_)
|
||||
|
||||
* **Project Configuration**
|
||||
|
||||
- Extended |INTERPOLATION| with ``${this}`` pattern (`issue #3953 <https://github.com/platformio/platformio-core/issues/3953>`_)
|
||||
- Embed environment name of the current section in the |PIOCONF| using ``${this.__env__}`` pattern
|
||||
- Renamed the "src_build_flags" project configuration option to the `build_src_flags <https://docs.platformio.org/en/latest/projectconf/section_env_build.html#build-src-flags>`__
|
||||
- Renamed the "src_filter" project configuration option to the `build_src_filter <https://docs.platformio.org/en/latest/projectconf/section_env_build.html#build-src-filter>`__
|
||||
|
||||
* **Miscellaneous**
|
||||
|
||||
- Pass extra arguments to the `native <https://docs.platformio.org/en/latest/platforms/native.html>`__ program with a new `pio run --program-arg <https://docs.platformio.org/en/latest/core/userguide/cmd_run.html#cmdoption-pio-run-a>`__ option (`issue #4246 <https://github.com/platformio/platformio-core/issues/4246>`_)
|
||||
- Improved PIO Remote setup on credit-card sized computers (Raspberry Pi, BeagleBon, etc) (`issue #3865 <https://github.com/platformio/platformio-core/issues/3865>`_)
|
||||
- Finally removed all tracks to the Python 2.7, the Python 3.6 is the minimum supported version.
|
||||
|
||||
.. _release_notes_5:
|
||||
|
||||
PlatformIO Core 5
|
||||
-----------------
|
||||
|
||||
**A professional collaborative platform for embedded development**
|
||||
|
||||
5.1.0 (2021-01-28)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* **PlatformIO Home**
|
||||
|
||||
- Boosted PlatformIO Home performance thanks to migrating the codebase to the pure Python 3 Asynchronous I/O stack
|
||||
- Added a new ``--session-id`` option to `pio home <https://docs.platformio.org/page/core/userguide/cmd_home.html>`__ command that helps to keep PlatformIO Home isolated from other instances and protect from 3rd party access (`issue #3397 <https://github.com/platformio/platformio-core/issues/3397>`_)
|
||||
|
||||
* **Build System**
|
||||
|
||||
- Upgraded build engine to the SCons 4.1 (`release notes <https://scons.org/scons-410-is-available.html>`_)
|
||||
- Refactored a workaround for a maximum command line character limitation (`issue #3792 <https://github.com/platformio/platformio-core/issues/3792>`_)
|
||||
- Fixed an issue with Python 3.8+ on Windows when a network drive is used (`issue #3417 <https://github.com/platformio/platformio-core/issues/3417>`_)
|
||||
|
||||
* **Package Management**
|
||||
|
||||
- New options for `pio system prune <https://docs.platformio.org/page/core/userguide/system/cmd_prune.html>`__ command:
|
||||
|
||||
+ ``--dry-run`` option to show data that will be removed
|
||||
+ ``--core-packages`` option to remove unnecessary core packages
|
||||
+ ``--platform-packages`` option to remove unnecessary development platform packages (`issue #923 <https://github.com/platformio/platformio-core/issues/923>`_)
|
||||
|
||||
- Added new `check_prune_system_threshold <https://docs.platformio.org/page/core/userguide/cmd_settings.html#check-prune-system-threshold>`__ setting
|
||||
- Disabled automatic removal of unnecessary development platform packages (`issue #3708 <https://github.com/platformio/platformio-core/issues/3708>`_, `issue #3770 <https://github.com/platformio/platformio-core/issues/3770>`_)
|
||||
- Fixed an issue when unnecessary packages were removed in ``update --dry-run`` mode (`issue #3809 <https://github.com/platformio/platformio-core/issues/3809>`_)
|
||||
- Fixed a "ValueError: Invalid simple block" when uninstalling a package with a custom name and external source (`issue #3816 <https://github.com/platformio/platformio-core/issues/3816>`_)
|
||||
|
||||
* **Debugging**
|
||||
|
||||
- Configure a custom debug adapter speed using a new `debug_speed <https://docs.platformio.org/page/projectconf/section_env_debug.html#debug-speed>`__ option (`issue #3799 <https://github.com/platformio/platformio-core/issues/3799>`_)
|
||||
- Handle debugging server's "ready_pattern" in "stderr" output
|
||||
|
||||
* **Miscellaneous**
|
||||
|
||||
- Improved listing of `multicast DNS services <https://docs.platformio.org/page/core/userguide/device/cmd_list.html>`_
|
||||
- Fixed a "UnicodeDecodeError: 'utf-8' codec can't decode byte" when using J-Link for firmware uploading on Linux (`issue #3804 <https://github.com/platformio/platformio-core/issues/3804>`_)
|
||||
- Fixed an issue with a compiler driver for ".ccls" language server (`issue #3808 <https://github.com/platformio/platformio-core/issues/3808>`_)
|
||||
- Fixed an issue when `pio device monitor --eol <https://docs.platformio.org/en/latest/core/userguide/device/cmd_monitor.html#cmdoption-pio-device-monitor-eol>`__ and "send_on_enter" filter do not work properly (`issue #3787 <https://github.com/platformio/platformio-core/issues/3787>`_)
|
||||
|
||||
5.0.4 (2020-12-30)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Added "Core" suffix when showing PlatformIO Core version using ``pio --version`` command
|
||||
- Improved ".ccls" configuration file for Emacs, Vim, and Sublime Text integrations
|
||||
- Updated analysis tools:
|
||||
|
||||
* `Cppcheck <https://docs.platformio.org/page/plus/check-tools/cppcheck.html>`__ v2.3 with improved C++ parser and several new MISRA rules
|
||||
* `PVS-Studio <https://docs.platformio.org/page/plus/check-tools/pvs-studio.html>`__ v7.11 with new diagnostics and updated mass suppression mechanism
|
||||
|
||||
- Show a warning message about deprecated support for Python 2 and Python 3.5
|
||||
- Do not provide "intelliSenseMode" option when generating configuration for VSCode C/C++ extension
|
||||
- Fixed a "git-sh-setup: file not found" error when installing project dependencies from Git VCS (`issue #3740 <https://github.com/platformio/platformio-core/issues/3740>`_)
|
||||
- Fixed an issue with package publishing on Windows when Unix permissions are not preserved (`issue #3776 <https://github.com/platformio/platformio-core/issues/3776>`_)
|
||||
|
||||
5.0.3 (2020-11-12)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Added an error selector for `Sublime Text <https://docs.platformio.org/page/integration/ide/sublimetext.html>`__ build runner (`issue #3733 <https://github.com/platformio/platformio-core/issues/3733>`_)
|
||||
- Generate a working "projectEnvName" for PlatformIO IDE's debugger for VSCode
|
||||
- Force VSCode's intelliSenseMode to "gcc-x64" when GCC toolchain is used
|
||||
- Print ignored test suites and environments in the test summary report only in verbose mode (`issue #3726 <https://github.com/platformio/platformio-core/issues/3726>`_)
|
||||
- Fixed an issue when the package manager tries to install a built-in library from the registry (`issue #3662 <https://github.com/platformio/platformio-core/issues/3662>`_)
|
||||
- Fixed an issue when `pio package pack <https://docs.platformio.org/page/core/userguide/package/cmd_pack.html>`__ ignores some folders (`issue #3730 <https://github.com/platformio/platformio-core/issues/3730>`_)
|
||||
|
||||
5.0.2 (2020-10-30)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Initialize a new project or update the existing passing working environment name and its options (`issue #3686 <https://github.com/platformio/platformio-core/issues/3686>`_)
|
||||
- Automatically build PlatformIO Core extra Python dependencies on a host machine if they are missed in the registry (`issue #3700 <https://github.com/platformio/platformio-core/issues/3700>`_)
|
||||
- Improved "core.call" RPC for PlatformIO Home (`issue #3671 <https://github.com/platformio/platformio-core/issues/3671>`_)
|
||||
- Fixed a "PermissionError: [WinError 5]" on Windows when an external repository is used with `lib_deps <https://docs.platformio.org/page/projectconf/section_env_library.html#lib-deps>`__ option (`issue #3664 <https://github.com/platformio/platformio-core/issues/3664>`_)
|
||||
- Fixed a "KeyError: 'versions'" when dependency does not exist in the registry (`issue #3666 <https://github.com/platformio/platformio-core/issues/3666>`_)
|
||||
- Fixed an issue with GCC linker when "native" dev-platform is used in pair with library dependencies (`issue #3669 <https://github.com/platformio/platformio-core/issues/3669>`_)
|
||||
- Fixed an "AssertionError: ensure_dir_exists" when checking library updates from simultaneous subprocesses (`issue #3677 <https://github.com/platformio/platformio-core/issues/3677>`_)
|
||||
- Fixed an issue when `pio package publish <https://docs.platformio.org/page/core/userguide/package/cmd_publish.html>`__ command removes original archive after submitting to the registry (`issue #3716 <https://github.com/platformio/platformio-core/issues/3716>`_)
|
||||
- Fixed an issue when multiple `pio lib install <https://docs.platformio.org/page/core/userguide/lib/cmd_install.html>`__ command with the same local library results in duplicates in ``lib_deps`` (`issue #3715 <https://github.com/platformio/platformio-core/issues/3715>`_)
|
||||
- Fixed an issue with a "wrong" timestamp in device monitor output using `"time" filter <https://docs.platformio.org/page/core/userguide/device/cmd_monitor.html#filters>`__ (`issue #3712 <https://github.com/platformio/platformio-core/issues/3712>`_)
|
||||
|
||||
5.0.1 (2020-09-10)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Added support for "owner" requirement when declaring ``dependencies`` using `library.json <https://docs.platformio.org/page/librarymanager/config.html#dependencies>`__
|
||||
- Fixed an issue when using a custom git/ssh package with `platform_packages <https://docs.platformio.org/page/projectconf/section_env_platform.html#platform-packages>`__ option (`issue #3624 <https://github.com/platformio/platformio-core/issues/3624>`_)
|
||||
- Fixed an issue with "ImportError: cannot import name '_get_backend' from 'cryptography.hazmat.backends'" when using `Remote Development <https://docs.platformio.org/page/plus/pio-remote.html>`__ on RaspberryPi device (`issue #3652 <https://github.com/platformio/platformio-core/issues/3652>`_)
|
||||
- Fixed an issue when `pio package unpublish <https://docs.platformio.org/page/core/userguide/package/cmd_unpublish.html>`__ command crashes (`issue #3660 <https://github.com/platformio/platformio-core/issues/3660>`_)
|
||||
- Fixed an issue when the package manager tries to install a built-in library from the registry (`issue #3662 <https://github.com/platformio/platformio-core/issues/3662>`_)
|
||||
- Fixed an issue with incorrect value for C++ language standard in IDE projects when an in-progress language standard is used (`issue #3653 <https://github.com/platformio/platformio-core/issues/3653>`_)
|
||||
- Fixed an issue with "Invalid simple block (semantic_version)" from library dependency that refs to an external source (repository, ZIP/Tar archives) (`issue #3658 <https://github.com/platformio/platformio-core/issues/3658>`_)
|
||||
- Fixed an issue when can not remove update or remove external dev-platform using PlatformIO Home (`issue #3663 <https://github.com/platformio/platformio-core/issues/3663>`_)
|
||||
|
||||
5.0.0 (2020-09-03)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Please check `Migration guide from 4.x to 5.0 <https://docs.platformio.org/page/core/migration.html>`__.
|
||||
|
||||
* Integration with the new **PlatformIO Trusted Registry**
|
||||
|
||||
- Enterprise-grade package storage with high availability (multi replicas)
|
||||
- Secure, fast, and reliable global content delivery network (CDN)
|
||||
- Universal support for all packages:
|
||||
|
||||
* Libraries
|
||||
* Development platforms
|
||||
* Toolchains
|
||||
|
||||
- Built-in fine-grained access control (role-based, teams, organizations)
|
||||
- New CLI commands:
|
||||
|
||||
* `pio package <https://docs.platformio.org/page/core/userguide/package/index.html>`__ – manage packages in the registry
|
||||
* `pio access <https://docs.platformio.org/page/core/userguide/access/index.html>`__ – manage package access for users, teams, and maintainers
|
||||
|
||||
* Integration with the new **Account Management System**
|
||||
|
||||
- `Manage organizations <https://docs.platformio.org/page/core/userguide/org/index.html>`__
|
||||
- `Manage teams and team memberships <https://docs.platformio.org/page/core/userguide/team/index.html>`__
|
||||
|
||||
* New **Package Management System**
|
||||
|
||||
- Integrated PlatformIO Core with the new PlatformIO Registry
|
||||
- Support for owner-based dependency declaration (resolves name conflicts) (`issue #1824 <https://github.com/platformio/platformio-core/issues/1824>`_)
|
||||
- Automatically save dependencies to `"platformio.ini" <https://docs.platformio.org/page/projectconf.html>`__ when installing using PlatformIO CLI (`issue #2964 <https://github.com/platformio/platformio-core/issues/2964>`_)
|
||||
- Follow SemVer complaint version constraints when checking library updates `issue #1281 <https://github.com/platformio/platformio-core/issues/1281>`_)
|
||||
- Dropped support for "packageRepositories" section in "platform.json" manifest (please publish packages directly to the registry)
|
||||
|
||||
* **Build System**
|
||||
|
||||
- Upgraded build engine to the `SCons 4.0 - a next-generation software construction tool <https://scons.org/>`__
|
||||
|
||||
* `Configuration files are Python scripts <https://docs.platformio.org/page/projectconf/advanced_scripting.html>`__ – use the power of a real programming language to solve build problems
|
||||
* Built-in reliable and automatic dependency analysis
|
||||
* Improved support for parallel builds
|
||||
* Ability to `share built files in a cache <https://docs.platformio.org/page/projectconf/section_platformio.html#projectconf-pio-build-cache-dir>`__ to speed up multiple builds
|
||||
|
||||
- New `Custom Targets <https://docs.platformio.org/page/projectconf/advanced_scripting.html#custom-targets>`__
|
||||
|
||||
* Pre/Post processing based on dependent sources (another target, source file, etc.)
|
||||
* Command launcher with own arguments
|
||||
* Launch command with custom options declared in `"platformio.ini" <https://docs.platformio.org/page/projectconf.html>`__
|
||||
* Python callback as a target (use the power of Python interpreter and PlatformIO Build API)
|
||||
* List available project targets (including dev-platform specific and custom targets) with a new `pio run --list-targets <https://docs.platformio.org/page/core/userguide/cmd_run.html#cmdoption-platformio-run-list-targets>`__ command (`issue #3544 <https://github.com/platformio/platformio-core/issues/3544>`_)
|
||||
|
||||
- Enable "cyclic reference" for GCC linker only for the embedded dev-platforms (`issue #3570 <https://github.com/platformio/platformio-core/issues/3570>`_)
|
||||
- Automatically enable LDF dependency `chain+ mode (evaluates C/C++ Preprocessor conditional syntax) <https://docs.platformio.org/page/librarymanager/ldf.html#dependency-finder-mode>`__ for Arduino library when "library.property" has "depends" field (`issue #3607 <https://github.com/platformio/platformio-core/issues/3607>`_)
|
||||
- Fixed an issue with improper processing of source files added via multiple Build Middlewares (`issue #3531 <https://github.com/platformio/platformio-core/issues/3531>`_)
|
||||
- Fixed an issue with the ``clean`` target on Windows when project and build directories are located on different logical drives (`issue #3542 <https://github.com/platformio/platformio-core/issues/3542>`_)
|
||||
|
||||
* **Project Management**
|
||||
|
||||
- Added support for "globstar/`**`" (recursive) pattern for the different commands and configuration options (`pio ci <https://docs.platformio.org/page/core/userguide/cmd_ci.html>`__, `src_filter <https://docs.platformio.org/page/projectconf/section_env_build.html#src-filter>`__, `check_patterns <https://docs.platformio.org/page/projectconf/section_env_check.html#check-patterns>`__, `library.json > srcFilter <https://docs.platformio.org/page/librarymanager/config.html#srcfilter>`__). Python 3.5+ is required
|
||||
- Added a new ``-e, --environment`` option to `pio project init <https://docs.platformio.org/page/core/userguide/project/cmd_init.html#cmdoption-platformio-project-init-e>`__ command that helps to update a PlatformIO project using the existing environment
|
||||
- Dump build system data intended for IDE extensions/plugins using a new `pio project data <https://docs.platformio.org/page/core/userguide/project/cmd_data.html>`__ command
|
||||
- Do not generate ".travis.yml" for a new project, let the user have a choice
|
||||
|
||||
* **Unit Testing**
|
||||
|
||||
- Updated PIO Unit Testing support for Mbed framework and added compatibility with Mbed OS 6
|
||||
- Fixed an issue when running multiple test environments (`issue #3523 <https://github.com/platformio/platformio-core/issues/3523>`_)
|
||||
- Fixed an issue when Unit Testing engine fails with a custom project configuration file (`issue #3583 <https://github.com/platformio/platformio-core/issues/3583>`_)
|
||||
|
||||
* **Static Code Analysis**
|
||||
|
||||
- Updated analysis tools:
|
||||
|
||||
* `Cppcheck <https://docs.platformio.org/page/plus/check-tools/cppcheck.html>`__ v2.1 with a new "soundy" analysis option and improved code parser
|
||||
* `PVS-Studio <https://docs.platformio.org/page/plus/check-tools/pvs-studio.html>`__ v7.09 with a new file list analysis mode and an extended list of analysis diagnostics
|
||||
|
||||
- Added Cppcheck package for ARM-based single-board computers (`issue #3559 <https://github.com/platformio/platformio-core/issues/3559>`_)
|
||||
- Fixed an issue with PIO Check when a defect with a multiline error message is not reported in verbose mode (`issue #3631 <https://github.com/platformio/platformio-core/issues/3631>`_)
|
||||
|
||||
* **Miscellaneous**
|
||||
|
||||
- Display system-wide information using a new `pio system info <https://docs.platformio.org/page/core/userguide/system/cmd_info.html>`__ command (`issue #3521 <https://github.com/platformio/platformio-core/issues/3521>`_)
|
||||
- Remove unused data using a new `pio system prune <https://docs.platformio.org/page/core/userguide/system/cmd_prune.html>`__ command (`issue #3522 <https://github.com/platformio/platformio-core/issues/3522>`_)
|
||||
- Show ignored project environments only in the verbose mode (`issue #3641 <https://github.com/platformio/platformio-core/issues/3641>`_)
|
||||
- Do not escape compiler arguments in VSCode template on Windows
|
||||
- Drop support for Python 2 and 3.5.
|
||||
See `PlatformIO Core 5.0 history <https://github.com/platformio/platformio-core/blob/v5.2.5/HISTORY.rst>`__.
|
||||
|
||||
.. _release_notes_4:
|
||||
|
||||
PlatformIO Core 4
|
||||
-----------------
|
||||
|
||||
See `PlatformIO Core 4.0 history <https://docs.platformio.org/en/v4.3.4/core/history.html#platformio-core-4>`__.
|
||||
See `PlatformIO Core 4.0 history <https://github.com/platformio/platformio-core/blob/v4.3.4/HISTORY.rst>`__.
|
||||
|
||||
PlatformIO Core 3
|
||||
-----------------
|
||||
|
||||
See `PlatformIO Core 3.0 history <https://docs.platformio.org/en/v4.3.4/core/history.html#platformio-core-3>`__.
|
||||
See `PlatformIO Core 3.0 history <https://github.com/platformio/platformio-core/blob/v3.6.7/HISTORY.rst>`__.
|
||||
|
||||
PlatformIO Core 2
|
||||
-----------------
|
||||
|
||||
See `PlatformIO Core 2.0 history <https://docs.platformio.org/en/v4.3.4/core/history.html#platformio-core-2>`__.
|
||||
See `PlatformIO Core 2.0 history <https://github.com/platformio/platformio-core/blob/v2.11.2/HISTORY.rst>`__.
|
||||
|
||||
PlatformIO Core 1
|
||||
-----------------
|
||||
|
||||
See `PlatformIO Core 1.0 history <https://docs.platformio.org/en/v4.3.4/core/history.html#platformio-core-1>`__.
|
||||
See `PlatformIO Core 1.0 history <https://github.com/platformio/platformio-core/blob/v1.5.0/HISTORY.rst>`__.
|
||||
|
||||
PlatformIO Core Preview
|
||||
-----------------------
|
||||
|
||||
See `PlatformIO Core Preview history <https://docs.platformio.org/en/v4.3.4/core/history.html#platformio-core-preview>`__.
|
||||
See `PlatformIO Core Preview history <https://github.com/platformio/platformio-core/blob/v0.10.2/HISTORY.rst>`__.
|
||||
|
||||
10
Makefile
10
Makefile
@@ -1,17 +1,17 @@
|
||||
lint:
|
||||
pylint -j 6 --rcfile=./.pylintrc ./platformio
|
||||
pylint -j 6 --rcfile=./.pylintrc ./tests
|
||||
pylint --rcfile=./.pylintrc ./tests
|
||||
pylint --rcfile=./.pylintrc ./platformio
|
||||
|
||||
isort:
|
||||
isort ./platformio
|
||||
isort ./tests
|
||||
|
||||
format:
|
||||
black --target-version py27 ./platformio
|
||||
black --target-version py27 ./tests
|
||||
black ./platformio
|
||||
black ./tests
|
||||
|
||||
test:
|
||||
py.test --verbose --capture=no --exitfirst -n 6 --dist=loadscope tests --ignore tests/test_examples.py
|
||||
py.test --verbose --exitfirst -n 6 --dist=loadscope tests --ignore tests/test_examples.py
|
||||
|
||||
before-commit: isort format lint
|
||||
|
||||
|
||||
114
README.rst
114
README.rst
@@ -1,27 +1,32 @@
|
||||
PlatformIO
|
||||
==========
|
||||
.. image:: https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg
|
||||
:target: https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md
|
||||
:alt: SWUbanner
|
||||
|
||||
PlatformIO Core
|
||||
===============
|
||||
|
||||
.. image:: https://github.com/platformio/platformio-core/workflows/Core/badge.svg
|
||||
:target: https://docs.platformio.org/page/core/index.html
|
||||
:target: https://docs.platformio.org/en/latest/core/index.html
|
||||
:alt: CI Build for PlatformIO Core
|
||||
.. image:: https://github.com/platformio/platformio-core/workflows/Examples/badge.svg
|
||||
:target: https://github.com/platformio/platformio-examples
|
||||
:alt: CI Build for dev-platform examples
|
||||
.. image:: https://github.com/platformio/platformio-core/workflows/Docs/badge.svg
|
||||
:target: https://docs.platformio.org?utm_source=github&utm_medium=core
|
||||
:alt: CI Build for Docs
|
||||
.. image:: https://github.com/platformio/platformio-core/workflows/Examples/badge.svg
|
||||
:target: https://github.com/platformio/platformio-examples
|
||||
:alt: CI Build for dev-platform examples
|
||||
.. image:: https://github.com/platformio/platformio-core/workflows/Projects/badge.svg
|
||||
:target: https://docs.platformio.org/en/latest/tutorials/index.html#projects
|
||||
:alt: CI Build for the Community Projects
|
||||
.. image:: https://img.shields.io/pypi/v/platformio.svg
|
||||
:target: https://pypi.python.org/pypi/platformio/
|
||||
:alt: Latest Version
|
||||
.. image:: https://img.shields.io/badge/license-Apache%202.0-blue.svg
|
||||
:target: https://pypi.python.org/pypi/platformio/
|
||||
:alt: License
|
||||
.. image:: https://img.shields.io/badge/PlatformIO-Labs-orange.svg
|
||||
:alt: Community Labs
|
||||
:alt: PlatformIO Labs
|
||||
:target: https://piolabs.com/?utm_source=github&utm_medium=core
|
||||
|
||||
**Quick Links:** `Web <https://platformio.org?utm_source=github&utm_medium=core>`_ |
|
||||
**Quick Links:** `Homepage <https://platformio.org?utm_source=github&utm_medium=core>`_ |
|
||||
`PlatformIO IDE <https://platformio.org/platformio-ide?utm_source=github&utm_medium=core>`_ |
|
||||
`Registry <https://registry.platformio.org?utm_source=github&utm_medium=core>`_ |
|
||||
`Project Examples <https://github.com/platformio/platformio-examples/>`__ |
|
||||
`Docs <https://docs.platformio.org?utm_source=github&utm_medium=core>`_ |
|
||||
`Donate <https://platformio.org/donate?utm_source=github&utm_medium=core>`_ |
|
||||
@@ -35,7 +40,7 @@ PlatformIO
|
||||
.. image:: https://raw.githubusercontent.com/platformio/platformio-web/develop/app/images/platformio-ide-laptop.png
|
||||
:target: https://platformio.org?utm_source=github&utm_medium=core
|
||||
|
||||
`PlatformIO <https://platformio.org?utm_source=github&utm_medium=core>`_ is a professional collaborative platform for embedded development
|
||||
`PlatformIO <https://platformio.org>`_ is a professional collaborative platform for embedded development.
|
||||
|
||||
**A place where Developers and Teams have true Freedom! No more vendor lock-in!**
|
||||
|
||||
@@ -43,93 +48,36 @@ PlatformIO
|
||||
* Cross-platform IDE and Unified Debugger
|
||||
* Static Code Analyzer and Remote Unit Testing
|
||||
* Multi-platform and Multi-architecture Build System
|
||||
* Firmware File Explorer and Memory Inspection.
|
||||
* Firmware File Explorer and Memory Inspection
|
||||
|
||||
Get Started
|
||||
-----------
|
||||
|
||||
* `What is PlatformIO? <https://docs.platformio.org/page/what-is-platformio.html?utm_source=github&utm_medium=core>`_
|
||||
* `What is PlatformIO? <https://docs.platformio.org/en/latest/what-is-platformio.html?utm_source=github&utm_medium=core>`_
|
||||
* `PlatformIO IDE <https://platformio.org/platformio-ide?utm_source=github&utm_medium=core>`_
|
||||
* `PlatformIO Core (CLI) <https://docs.platformio.org/page/core.html?utm_source=github&utm_medium=core>`_
|
||||
* `PlatformIO Core (CLI) <https://docs.platformio.org/en/latest/core.html?utm_source=github&utm_medium=core>`_
|
||||
* `Project Examples <https://github.com/platformio/platformio-examples?utm_source=github&utm_medium=core>`__
|
||||
|
||||
Solutions
|
||||
---------
|
||||
|
||||
* `Library Management <https://docs.platformio.org/page/librarymanager/index.html?utm_source=github&utm_medium=core>`_
|
||||
* `Desktop IDEs Integration <https://docs.platformio.org/page/ide.html?utm_source=github&utm_medium=core>`_
|
||||
* `Continuous Integration <https://docs.platformio.org/page/ci/index.html?utm_source=github&utm_medium=core>`_
|
||||
* `Library Management <https://docs.platformio.org/en/latest/librarymanager/index.html?utm_source=github&utm_medium=core>`_
|
||||
* `Desktop IDEs Integration <https://docs.platformio.org/en/latest/ide.html?utm_source=github&utm_medium=core>`_
|
||||
* `Continuous Integration <https://docs.platformio.org/en/latest/ci/index.html?utm_source=github&utm_medium=core>`_
|
||||
|
||||
**Advanced**
|
||||
|
||||
* `Debugging <https://docs.platformio.org/page/plus/debugging.html?utm_source=github&utm_medium=core>`_
|
||||
* `Unit Testing <https://docs.platformio.org/page/plus/unit-testing.html?utm_source=github&utm_medium=core>`_
|
||||
* `Static Code Analysis <https://docs.platformio.org/page/plus/pio-check.html?utm_source=github&utm_medium=core>`_
|
||||
* `Remote Development <https://docs.platformio.org/page/plus/pio-remote.html?utm_source=github&utm_medium=core>`_
|
||||
* `Debugging <https://docs.platformio.org/en/latest/plus/debugging.html?utm_source=github&utm_medium=core>`_
|
||||
* `Unit Testing <https://docs.platformio.org/en/latest/advanced/unit-testing/index.html?utm_source=github&utm_medium=core>`_
|
||||
* `Static Code Analysis <https://docs.platformio.org/en/latest/plus/pio-check.html?utm_source=github&utm_medium=core>`_
|
||||
* `Remote Development <https://docs.platformio.org/en/latest/plus/pio-remote.html?utm_source=github&utm_medium=core>`_
|
||||
|
||||
Registry
|
||||
--------
|
||||
|
||||
* `Libraries <https://platformio.org/lib?utm_source=github&utm_medium=core>`_
|
||||
* `Development Platforms <https://platformio.org/platforms?utm_source=github&utm_medium=core>`_
|
||||
* `Frameworks <https://platformio.org/frameworks?utm_source=github&utm_medium=core>`_
|
||||
* `Embedded Boards <https://platformio.org/boards?utm_source=github&utm_medium=core>`_
|
||||
|
||||
Development Platforms
|
||||
---------------------
|
||||
|
||||
* `Aceinna IMU <https://platformio.org/platforms/aceinna_imu?utm_source=github&utm_medium=core>`_
|
||||
* `ASR Microelectronics ASR605x <https://platformio.org/platforms/asrmicro650x?utm_source=github&utm_medium=core>`_
|
||||
* `Atmel AVR <https://platformio.org/platforms/atmelavr?utm_source=github&utm_medium=core>`_
|
||||
* `Atmel SAM <https://platformio.org/platforms/atmelsam?utm_source=github&utm_medium=core>`_
|
||||
* `Espressif 32 <https://platformio.org/platforms/espressif32?utm_source=github&utm_medium=core>`_
|
||||
* `Espressif 8266 <https://platformio.org/platforms/espressif8266?utm_source=github&utm_medium=core>`_
|
||||
* `Freescale Kinetis <https://platformio.org/platforms/freescalekinetis?utm_source=github&utm_medium=core>`_
|
||||
* `Infineon XMC <https://platformio.org/platforms/infineonxmc?utm_source=github&utm_medium=core>`_
|
||||
* `Intel ARC32 <https://platformio.org/platforms/intel_arc32?utm_source=github&utm_medium=core>`_
|
||||
* `Intel MCS-51 (8051) <https://platformio.org/platforms/intel_mcs51?utm_source=github&utm_medium=core>`_
|
||||
* `Kendryte K210 <https://platformio.org/platforms/kendryte210?utm_source=github&utm_medium=core>`_
|
||||
* `Lattice iCE40 <https://platformio.org/platforms/lattice_ice40?utm_source=github&utm_medium=core>`_
|
||||
* `Maxim 32 <https://platformio.org/platforms/maxim32?utm_source=github&utm_medium=core>`_
|
||||
* `Microchip PIC32 <https://platformio.org/platforms/microchippic32?utm_source=github&utm_medium=core>`_
|
||||
* `Nordic nRF51 <https://platformio.org/platforms/nordicnrf51?utm_source=github&utm_medium=core>`_
|
||||
* `Nordic nRF52 <https://platformio.org/platforms/nordicnrf52?utm_source=github&utm_medium=core>`_
|
||||
* `Nuclei <https://platformio.org/platforms/nuclei?utm_source=github&utm_medium=core>`_
|
||||
* `NXP LPC <https://platformio.org/platforms/nxplpc?utm_source=github&utm_medium=core>`_
|
||||
* `RISC-V <https://platformio.org/platforms/riscv?utm_source=github&utm_medium=core>`_
|
||||
* `RISC-V GAP <https://platformio.org/platforms/riscv_gap?utm_source=github&utm_medium=core>`_
|
||||
* `Shakti <https://platformio.org/platforms/shakti?utm_source=github&utm_medium=core>`_
|
||||
* `Silicon Labs EFM32 <https://platformio.org/platforms/siliconlabsefm32?utm_source=github&utm_medium=core>`_
|
||||
* `ST STM32 <https://platformio.org/platforms/ststm32?utm_source=github&utm_medium=core>`_
|
||||
* `ST STM8 <https://platformio.org/platforms/ststm8?utm_source=github&utm_medium=core>`_
|
||||
* `Teensy <https://platformio.org/platforms/teensy?utm_source=github&utm_medium=core>`_
|
||||
* `TI MSP430 <https://platformio.org/platforms/timsp430?utm_source=github&utm_medium=core>`_
|
||||
* `TI Tiva <https://platformio.org/platforms/titiva?utm_source=github&utm_medium=core>`_
|
||||
* `WIZNet W7500 <https://platformio.org/platforms/wiznet7500?utm_source=github&utm_medium=core>`_
|
||||
|
||||
Frameworks
|
||||
----------
|
||||
|
||||
* `Arduino <https://platformio.org/frameworks/arduino?utm_source=github&utm_medium=core>`_
|
||||
* `CMSIS <https://platformio.org/frameworks/cmsis?utm_source=github&utm_medium=core>`_
|
||||
* `ESP-IDF <https://platformio.org/frameworks/espidf?utm_source=github&utm_medium=core>`_
|
||||
* `ESP8266 Non-OS SDK <https://platformio.org/frameworks/esp8266-nonos-sdk?utm_source=github&utm_medium=core>`_
|
||||
* `ESP8266 RTOS SDK <https://platformio.org/frameworks/esp8266-rtos-sdk?utm_source=github&utm_medium=core>`_
|
||||
* `Freedom E SDK <https://platformio.org/frameworks/freedom-e-sdk?utm_source=github&utm_medium=core>`_
|
||||
* `GigaDevice GD32V SDK <https://platformio.org/frameworks/gd32vf103-sdk?utm_source=github&utm_medium=core>`_
|
||||
* `Kendryte Standalone SDK <https://platformio.org/frameworks/kendryte-standalone-sdk?utm_source=github&utm_medium=core>`_
|
||||
* `Kendryte FreeRTOS SDK <https://platformio.org/frameworks/kendryte-freertos-sdk?utm_source=github&utm_medium=core>`_
|
||||
* `libOpenCM3 <https://platformio.org/frameworks/libopencm3?utm_source=github&utm_medium=core>`_
|
||||
* `Mbed <https://platformio.org/frameworks/mbed?utm_source=github&utm_medium=core>`_
|
||||
* `Nuclei SDK <https://platformio.org/frameworks/nuclei-sdk?utm_source=github&utm_medium=core>`_
|
||||
* `PULP OS <https://platformio.org/frameworks/pulp-os?utm_source=github&utm_medium=core>`_
|
||||
* `Pumbaa <https://platformio.org/frameworks/pumbaa?utm_source=github&utm_medium=core>`_
|
||||
* `Shakti SDK <https://platformio.org/frameworks/shakti-sdk?utm_source=github&utm_medium=core>`_
|
||||
* `Simba <https://platformio.org/frameworks/simba?utm_source=github&utm_medium=core>`_
|
||||
* `SPL <https://platformio.org/frameworks/spl?utm_source=github&utm_medium=core>`_
|
||||
* `STM32Cube <https://platformio.org/frameworks/stm32cube?utm_source=github&utm_medium=core>`_
|
||||
* `WiringPi <https://platformio.org/frameworks/wiringpi?utm_source=github&utm_medium=core>`_
|
||||
* `Zephyr <https://platformio.org/frameworks/zephyr?utm_source=github&utm_medium=core>`_
|
||||
* `Libraries <https://registry.platformio.org/search?t=library&utm_source=github&utm_medium=core>`_
|
||||
* `Development Platforms <https://registry.platformio.org/search?t=platform&utm_source=github&utm_medium=core>`_
|
||||
* `Development Tools <https://registry.platformio.org/search?t=tool&utm_source=github&utm_medium=core>`_
|
||||
|
||||
Contributing
|
||||
------------
|
||||
@@ -142,7 +90,7 @@ Telemetry / Privacy Policy
|
||||
Share minimal diagnostics and usage information to help us make PlatformIO better.
|
||||
It is enabled by default. For more information see:
|
||||
|
||||
* `Telemetry Setting <https://docs.platformio.org/page/userguide/cmd_settings.html?utm_source=github&utm_medium=core#enable-telemetry>`_
|
||||
* `Telemetry Setting <https://docs.platformio.org/en/latest/userguide/cmd_settings.html?utm_source=github&utm_medium=core#enable-telemetry>`_
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
2
docs
2
docs
Submodule docs updated: 25edd66d55...f4accb77c8
2
examples
2
examples
Submodule examples updated: 8a6e639b2b...7fbb0ec153
@@ -14,7 +14,7 @@
|
||||
|
||||
import sys
|
||||
|
||||
VERSION = (5, 1, 0)
|
||||
VERSION = (6, 1, 1)
|
||||
__version__ = ".".join([str(s) for s in VERSION])
|
||||
|
||||
__title__ = "platformio"
|
||||
@@ -38,27 +38,25 @@ __license__ = "Apache Software License"
|
||||
__copyright__ = "Copyright 2014-present PlatformIO Labs"
|
||||
|
||||
__accounts_api__ = "https://api.accounts.platformio.org"
|
||||
__registry_api__ = [
|
||||
"https://api.registry.platformio.org",
|
||||
"https://api.registry.ns1.platformio.org",
|
||||
__registry_mirror_hosts__ = [
|
||||
"registry.platformio.org",
|
||||
"registry.nm1.platformio.org",
|
||||
]
|
||||
__pioremote_endpoint__ = "ssl:host=remote.platformio.org:port=4413"
|
||||
|
||||
__default_requests_timeout__ = (10, None) # (connect, read)
|
||||
|
||||
__core_packages__ = {
|
||||
"contrib-piohome": "~3.3.3",
|
||||
"contrib-piohome": "~3.4.2",
|
||||
"contrib-pysite": "~2.%d%d.0" % (sys.version_info.major, sys.version_info.minor),
|
||||
"tool-unity": "~1.20500.0",
|
||||
"tool-scons": "~2.20501.7" if sys.version_info.major == 2 else "~4.40100.2",
|
||||
"tool-cppcheck": "~1.230.0",
|
||||
"tool-clangtidy": "~1.100000.0",
|
||||
"tool-pvs-studio": "~7.11.0",
|
||||
"tool-scons": "~4.40300.0",
|
||||
"tool-cppcheck": "~1.270.0",
|
||||
"tool-clangtidy": "~1.120001.0",
|
||||
"tool-pvs-studio": "~7.18.0",
|
||||
}
|
||||
|
||||
__check_internet_hosts__ = [
|
||||
"185.199.110.153", # Github.com
|
||||
"88.198.170.159", # platformio.org
|
||||
"github.com",
|
||||
"platformio.org",
|
||||
]
|
||||
] + __registry_mirror_hosts__
|
||||
|
||||
@@ -18,23 +18,16 @@ from traceback import format_exc
|
||||
|
||||
import click
|
||||
|
||||
from platformio import __version__, exception, maintenance, util
|
||||
from platformio.commands import PlatformioCLI
|
||||
from platformio.compat import CYGWIN
|
||||
|
||||
try:
|
||||
import click_completion # pylint: disable=import-error
|
||||
|
||||
click_completion.init()
|
||||
except: # pylint: disable=bare-except
|
||||
pass
|
||||
from platformio import __version__, exception, maintenance
|
||||
from platformio.cli import PlatformioCLI
|
||||
from platformio.compat import IS_CYGWIN, ensure_python3
|
||||
|
||||
|
||||
@click.command(
|
||||
cls=PlatformioCLI, context_settings=dict(help_option_names=["-h", "--help"])
|
||||
)
|
||||
@click.version_option(__version__, prog_name="PlatformIO Core")
|
||||
@click.option("--force", "-f", is_flag=True, help="DEPRECATE")
|
||||
@click.option("--force", "-f", is_flag=True, help="DEPRECATED", hidden=True)
|
||||
@click.option("--caller", "-c", help="Caller ID (service)")
|
||||
@click.option("--no-ansi", is_flag=True, help="Do not print ANSI control characters")
|
||||
@click.pass_context
|
||||
@@ -63,15 +56,14 @@ def cli(ctx, force, caller, no_ansi):
|
||||
maintenance.on_platformio_start(ctx, force, caller)
|
||||
|
||||
|
||||
@cli.resultcallback()
|
||||
@cli.result_callback()
|
||||
@click.pass_context
|
||||
def process_result(ctx, result, *_, **__):
|
||||
maintenance.on_platformio_end(ctx, result)
|
||||
|
||||
|
||||
@util.memoized()
|
||||
def configure():
|
||||
if CYGWIN:
|
||||
if IS_CYGWIN:
|
||||
raise exception.CygwinEnvDetected()
|
||||
|
||||
# https://urllib3.readthedocs.org
|
||||
@@ -105,17 +97,18 @@ def main(argv=None):
|
||||
assert isinstance(argv, list)
|
||||
sys.argv = argv
|
||||
try:
|
||||
ensure_python3(raise_exception=True)
|
||||
configure()
|
||||
cli() # pylint: disable=no-value-for-parameter
|
||||
except SystemExit as e:
|
||||
if e.code and str(e.code).isdigit():
|
||||
exit_code = int(e.code)
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
if not isinstance(e, exception.ReturnErrorCode):
|
||||
maintenance.on_platformio_exception(e)
|
||||
except SystemExit as exc:
|
||||
if exc.code and str(exc.code).isdigit():
|
||||
exit_code = int(exc.code)
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
if not isinstance(exc, exception.ReturnErrorCode):
|
||||
maintenance.on_platformio_exception(exc)
|
||||
error_str = "Error: "
|
||||
if isinstance(e, exception.PlatformioException):
|
||||
error_str += str(e)
|
||||
if isinstance(exc, exception.PlatformioException):
|
||||
error_str += str(exc)
|
||||
else:
|
||||
error_str += format_exc()
|
||||
error_str += """
|
||||
@@ -127,7 +120,7 @@ An unexpected error occurred. Further steps:
|
||||
`pip install -U platformio` command
|
||||
|
||||
* Try to find answer in FAQ Troubleshooting section
|
||||
https://docs.platformio.org/page/faq.html
|
||||
https://docs.platformio.org/page/faq/index.html
|
||||
|
||||
* Report this problem to the developers
|
||||
https://github.com/platformio/platformio-core/issues
|
||||
@@ -135,7 +128,7 @@ An unexpected error occurred. Further steps:
|
||||
============================================================
|
||||
"""
|
||||
click.secho(error_str, fg="red", err=True)
|
||||
exit_code = int(str(e)) if str(e).isdigit() else 1
|
||||
exit_code = int(str(exc)) if str(exc).isdigit() else 1
|
||||
sys.argv = prev_sys_argv
|
||||
return exit_code
|
||||
|
||||
|
||||
44
platformio/account/cli.py
Normal file
44
platformio/account/cli.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.commands.destroy import account_destroy_cmd
|
||||
from platformio.account.commands.forgot import account_forgot_cmd
|
||||
from platformio.account.commands.login import account_login_cmd
|
||||
from platformio.account.commands.logout import account_logout_cmd
|
||||
from platformio.account.commands.password import account_password_cmd
|
||||
from platformio.account.commands.register import account_register_cmd
|
||||
from platformio.account.commands.show import account_show_cmd
|
||||
from platformio.account.commands.token import account_token_cmd
|
||||
from platformio.account.commands.update import account_update_cmd
|
||||
|
||||
|
||||
@click.group(
|
||||
"account",
|
||||
commands=[
|
||||
account_destroy_cmd,
|
||||
account_forgot_cmd,
|
||||
account_login_cmd,
|
||||
account_logout_cmd,
|
||||
account_password_cmd,
|
||||
account_register_cmd,
|
||||
account_show_cmd,
|
||||
account_token_cmd,
|
||||
account_update_cmd,
|
||||
],
|
||||
short_help="Manage PlatformIO account",
|
||||
)
|
||||
def cli():
|
||||
pass
|
||||
@@ -16,8 +16,8 @@ import os
|
||||
import time
|
||||
|
||||
from platformio import __accounts_api__, app
|
||||
from platformio.clients.http import HTTPClient
|
||||
from platformio.exception import PlatformioException
|
||||
from platformio.http import HTTPClient, HTTPClientError
|
||||
|
||||
|
||||
class AccountError(PlatformioException):
|
||||
@@ -40,14 +40,14 @@ class AccountClient(HTTPClient): # pylint:disable=too-many-public-methods
|
||||
SUMMARY_CACHE_TTL = 60 * 60 * 24 * 7
|
||||
|
||||
def __init__(self):
|
||||
super(AccountClient, self).__init__(__accounts_api__)
|
||||
super().__init__(__accounts_api__)
|
||||
|
||||
@staticmethod
|
||||
def get_refresh_token():
|
||||
try:
|
||||
return app.get_state_item("account").get("auth").get("refresh_token")
|
||||
except: # pylint:disable=bare-except
|
||||
raise AccountNotAuthorized()
|
||||
except Exception as exc:
|
||||
raise AccountNotAuthorized() from exc
|
||||
|
||||
@staticmethod
|
||||
def delete_local_session():
|
||||
@@ -61,13 +61,33 @@ class AccountClient(HTTPClient): # pylint:disable=too-many-public-methods
|
||||
del account[key]
|
||||
app.set_state_item("account", account)
|
||||
|
||||
def send_auth_request(self, *args, **kwargs):
|
||||
headers = kwargs.get("headers", {})
|
||||
if "Authorization" not in headers:
|
||||
token = self.fetch_authentication_token()
|
||||
headers["Authorization"] = "Bearer %s" % token
|
||||
kwargs["headers"] = headers
|
||||
return self.fetch_json_data(*args, **kwargs)
|
||||
def fetch_json_data(self, *args, **kwargs):
|
||||
try:
|
||||
return super().fetch_json_data(*args, **kwargs)
|
||||
except HTTPClientError as exc:
|
||||
raise AccountError(exc) from exc
|
||||
|
||||
def fetch_authentication_token(self):
|
||||
if os.environ.get("PLATFORMIO_AUTH_TOKEN"):
|
||||
return os.environ.get("PLATFORMIO_AUTH_TOKEN")
|
||||
auth = app.get_state_item("account", {}).get("auth", {})
|
||||
if auth.get("access_token") and auth.get("access_token_expire"):
|
||||
if auth.get("access_token_expire") > time.time():
|
||||
return auth.get("access_token")
|
||||
if auth.get("refresh_token"):
|
||||
try:
|
||||
data = self.fetch_json_data(
|
||||
"post",
|
||||
"/v1/login",
|
||||
headers={
|
||||
"Authorization": "Bearer %s" % auth.get("refresh_token")
|
||||
},
|
||||
)
|
||||
app.set_state_item("account", data)
|
||||
return data.get("auth").get("access_token")
|
||||
except AccountError:
|
||||
self.delete_local_session()
|
||||
raise AccountNotAuthorized()
|
||||
|
||||
def login(self, username, password):
|
||||
try:
|
||||
@@ -119,10 +139,11 @@ class AccountClient(HTTPClient): # pylint:disable=too-many-public-methods
|
||||
return True
|
||||
|
||||
def change_password(self, old_password, new_password):
|
||||
return self.send_auth_request(
|
||||
return self.fetch_json_data(
|
||||
"post",
|
||||
"/v1/password",
|
||||
data={"old_password": old_password, "new_password": new_password},
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def registration(
|
||||
@@ -150,10 +171,11 @@ class AccountClient(HTTPClient): # pylint:disable=too-many-public-methods
|
||||
)
|
||||
|
||||
def auth_token(self, password, regenerate):
|
||||
return self.send_auth_request(
|
||||
return self.fetch_json_data(
|
||||
"post",
|
||||
"/v1/token",
|
||||
data={"password": password, "regenerate": 1 if regenerate else 0},
|
||||
x_with_authorization=True,
|
||||
).get("auth_token")
|
||||
|
||||
def forgot_password(self, username):
|
||||
@@ -164,18 +186,20 @@ class AccountClient(HTTPClient): # pylint:disable=too-many-public-methods
|
||||
)
|
||||
|
||||
def get_profile(self):
|
||||
return self.send_auth_request(
|
||||
return self.fetch_json_data(
|
||||
"get",
|
||||
"/v1/profile",
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def update_profile(self, profile, current_password):
|
||||
profile["current_password"] = current_password
|
||||
self.delete_local_state("summary")
|
||||
response = self.send_auth_request(
|
||||
response = self.fetch_json_data(
|
||||
"put",
|
||||
"/v1/profile",
|
||||
data=profile,
|
||||
x_with_authorization=True,
|
||||
)
|
||||
return response
|
||||
|
||||
@@ -193,9 +217,10 @@ class AccountClient(HTTPClient): # pylint:disable=too-many-public-methods
|
||||
"username": account.get("username"),
|
||||
}
|
||||
}
|
||||
result = self.send_auth_request(
|
||||
result = self.fetch_json_data(
|
||||
"get",
|
||||
"/v1/summary",
|
||||
x_with_authorization=True,
|
||||
)
|
||||
account["summary"] = dict(
|
||||
profile=result.get("profile"),
|
||||
@@ -207,120 +232,125 @@ class AccountClient(HTTPClient): # pylint:disable=too-many-public-methods
|
||||
app.set_state_item("account", account)
|
||||
return result
|
||||
|
||||
def get_logged_username(self):
|
||||
return self.get_account_info(offline=True).get("profile").get("username")
|
||||
|
||||
def destroy_account(self):
|
||||
return self.send_auth_request("delete", "/v1/account")
|
||||
return self.fetch_json_data(
|
||||
"delete",
|
||||
"/v1/account",
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def create_org(self, orgname, email, displayname):
|
||||
return self.send_auth_request(
|
||||
return self.fetch_json_data(
|
||||
"post",
|
||||
"/v1/orgs",
|
||||
data={"orgname": orgname, "email": email, "displayname": displayname},
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def get_org(self, orgname):
|
||||
return self.send_auth_request("get", "/v1/orgs/%s" % orgname)
|
||||
return self.fetch_json_data(
|
||||
"get",
|
||||
"/v1/orgs/%s" % orgname,
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def list_orgs(self):
|
||||
return self.send_auth_request(
|
||||
return self.fetch_json_data(
|
||||
"get",
|
||||
"/v1/orgs",
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def update_org(self, orgname, data):
|
||||
return self.send_auth_request(
|
||||
"put", "/v1/orgs/%s" % orgname, data={k: v for k, v in data.items() if v}
|
||||
return self.fetch_json_data(
|
||||
"put",
|
||||
"/v1/orgs/%s" % orgname,
|
||||
data={k: v for k, v in data.items() if v},
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def destroy_org(self, orgname):
|
||||
return self.send_auth_request(
|
||||
return self.fetch_json_data(
|
||||
"delete",
|
||||
"/v1/orgs/%s" % orgname,
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def add_org_owner(self, orgname, username):
|
||||
return self.send_auth_request(
|
||||
return self.fetch_json_data(
|
||||
"post",
|
||||
"/v1/orgs/%s/owners" % orgname,
|
||||
data={"username": username},
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def list_org_owners(self, orgname):
|
||||
return self.send_auth_request(
|
||||
return self.fetch_json_data(
|
||||
"get",
|
||||
"/v1/orgs/%s/owners" % orgname,
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def remove_org_owner(self, orgname, username):
|
||||
return self.send_auth_request(
|
||||
return self.fetch_json_data(
|
||||
"delete",
|
||||
"/v1/orgs/%s/owners" % orgname,
|
||||
data={"username": username},
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def create_team(self, orgname, teamname, description):
|
||||
return self.send_auth_request(
|
||||
return self.fetch_json_data(
|
||||
"post",
|
||||
"/v1/orgs/%s/teams" % orgname,
|
||||
data={"name": teamname, "description": description},
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def destroy_team(self, orgname, teamname):
|
||||
return self.send_auth_request(
|
||||
return self.fetch_json_data(
|
||||
"delete",
|
||||
"/v1/orgs/%s/teams/%s" % (orgname, teamname),
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def get_team(self, orgname, teamname):
|
||||
return self.send_auth_request(
|
||||
return self.fetch_json_data(
|
||||
"get",
|
||||
"/v1/orgs/%s/teams/%s" % (orgname, teamname),
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def list_teams(self, orgname):
|
||||
return self.send_auth_request(
|
||||
return self.fetch_json_data(
|
||||
"get",
|
||||
"/v1/orgs/%s/teams" % orgname,
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def update_team(self, orgname, teamname, data):
|
||||
return self.send_auth_request(
|
||||
return self.fetch_json_data(
|
||||
"put",
|
||||
"/v1/orgs/%s/teams/%s" % (orgname, teamname),
|
||||
data={k: v for k, v in data.items() if v},
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def add_team_member(self, orgname, teamname, username):
|
||||
return self.send_auth_request(
|
||||
return self.fetch_json_data(
|
||||
"post",
|
||||
"/v1/orgs/%s/teams/%s/members" % (orgname, teamname),
|
||||
data={"username": username},
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def remove_team_member(self, orgname, teamname, username):
|
||||
return self.send_auth_request(
|
||||
return self.fetch_json_data(
|
||||
"delete",
|
||||
"/v1/orgs/%s/teams/%s/members" % (orgname, teamname),
|
||||
data={"username": username},
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def fetch_authentication_token(self):
|
||||
if os.environ.get("PLATFORMIO_AUTH_TOKEN"):
|
||||
return os.environ.get("PLATFORMIO_AUTH_TOKEN")
|
||||
auth = app.get_state_item("account", {}).get("auth", {})
|
||||
if auth.get("access_token") and auth.get("access_token_expire"):
|
||||
if auth.get("access_token_expire") > time.time():
|
||||
return auth.get("access_token")
|
||||
if auth.get("refresh_token"):
|
||||
try:
|
||||
data = self.fetch_json_data(
|
||||
"post",
|
||||
"/v1/login",
|
||||
headers={
|
||||
"Authorization": "Bearer %s" % auth.get("refresh_token")
|
||||
},
|
||||
)
|
||||
app.set_state_item("account", data)
|
||||
return data.get("auth").get("access_token")
|
||||
except AccountError:
|
||||
self.delete_local_session()
|
||||
raise AccountNotAuthorized()
|
||||
37
platformio/account/commands/destroy.py
Normal file
37
platformio/account/commands/destroy.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient, AccountNotAuthorized
|
||||
|
||||
|
||||
@click.command("destroy", short_help="Destroy account")
|
||||
def account_destroy_cmd():
|
||||
client = AccountClient()
|
||||
click.confirm(
|
||||
"Are you sure you want to delete the %s user account?\n"
|
||||
"Warning! All linked data will be permanently removed and can not be restored."
|
||||
% client.get_logged_username(),
|
||||
abort=True,
|
||||
)
|
||||
client.destroy_account()
|
||||
try:
|
||||
client.logout()
|
||||
except AccountNotAuthorized:
|
||||
pass
|
||||
click.secho(
|
||||
"User account has been destroyed.",
|
||||
fg="green",
|
||||
)
|
||||
@@ -12,16 +12,18 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
import click
|
||||
|
||||
from platformio.commands.update import cli as cmd_update
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
def test_update(clirunner, validate_cliresult, isolated_pio_core):
|
||||
matches = ("Platform Manager", "Library Manager")
|
||||
result = clirunner.invoke(cmd_update, ["--only-check"])
|
||||
validate_cliresult(result)
|
||||
assert all([m in result.output for m in matches])
|
||||
result = clirunner.invoke(cmd_update)
|
||||
validate_cliresult(result)
|
||||
assert all([m in result.output for m in matches])
|
||||
@click.command("forgot", short_help="Forgot password")
|
||||
@click.option("--username", prompt="Username or email")
|
||||
def account_forgot_cmd(username):
|
||||
client = AccountClient()
|
||||
client.forgot_password(username)
|
||||
click.secho(
|
||||
"If this account is registered, we will send the "
|
||||
"further instructions to your email.",
|
||||
fg="green",
|
||||
)
|
||||
26
platformio/account/commands/login.py
Normal file
26
platformio/account/commands/login.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("login", short_help="Log in to PlatformIO Account")
|
||||
@click.option("-u", "--username", prompt="Username or email")
|
||||
@click.option("-p", "--password", prompt=True, hide_input=True)
|
||||
def account_login_cmd(username, password):
|
||||
client = AccountClient()
|
||||
client.login(username, password)
|
||||
click.secho("Successfully logged in!", fg="green")
|
||||
24
platformio/account/commands/logout.py
Normal file
24
platformio/account/commands/logout.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("logout", short_help="Log out of PlatformIO Account")
|
||||
def account_logout_cmd():
|
||||
client = AccountClient()
|
||||
client.logout()
|
||||
click.secho("Successfully logged out!", fg="green")
|
||||
26
platformio/account/commands/password.py
Normal file
26
platformio/account/commands/password.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("password", short_help="Change password")
|
||||
@click.option("--old-password", prompt=True, hide_input=True)
|
||||
@click.option("--new-password", prompt=True, hide_input=True, confirmation_prompt=True)
|
||||
def account_password_cmd(old_password, new_password):
|
||||
client = AccountClient()
|
||||
client.change_password(old_password, new_password)
|
||||
click.secho("Password successfully changed!", fg="green")
|
||||
52
platformio/account/commands/register.py
Normal file
52
platformio/account/commands/register.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
from platformio.account.validate import (
|
||||
validate_email,
|
||||
validate_password,
|
||||
validate_username,
|
||||
)
|
||||
|
||||
|
||||
@click.command("register", short_help="Create new PlatformIO Account")
|
||||
@click.option(
|
||||
"-u",
|
||||
"--username",
|
||||
prompt=True,
|
||||
callback=lambda _, __, value: validate_username(value),
|
||||
)
|
||||
@click.option(
|
||||
"-e", "--email", prompt=True, callback=lambda _, __, value: validate_email(value)
|
||||
)
|
||||
@click.option(
|
||||
"-p",
|
||||
"--password",
|
||||
prompt=True,
|
||||
hide_input=True,
|
||||
confirmation_prompt=True,
|
||||
callback=lambda _, __, value: validate_password(value),
|
||||
)
|
||||
@click.option("--firstname", prompt=True)
|
||||
@click.option("--lastname", prompt=True)
|
||||
def account_register_cmd(username, email, password, firstname, lastname):
|
||||
client = AccountClient()
|
||||
client.registration(username, email, password, firstname, lastname)
|
||||
click.secho(
|
||||
"An account has been successfully created. "
|
||||
"Please check your mail to activate your account and verify your email address.",
|
||||
fg="green",
|
||||
)
|
||||
116
platformio/account/commands/show.py
Normal file
116
platformio/account/commands/show.py
Normal file
@@ -0,0 +1,116 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
|
||||
import click
|
||||
from tabulate import tabulate
|
||||
|
||||
from platformio import util
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("show", short_help="PlatformIO Account information")
|
||||
@click.option("--offline", is_flag=True)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def account_show_cmd(offline, json_output):
|
||||
client = AccountClient()
|
||||
info = client.get_account_info(offline)
|
||||
if json_output:
|
||||
click.echo(json.dumps(info))
|
||||
return
|
||||
click.echo()
|
||||
if info.get("profile"):
|
||||
print_profile(info["profile"])
|
||||
if info.get("packages"):
|
||||
print_packages(info["packages"])
|
||||
if info.get("subscriptions"):
|
||||
print_subscriptions(info["subscriptions"])
|
||||
click.echo()
|
||||
|
||||
|
||||
def print_profile(profile):
|
||||
click.secho("Profile", fg="cyan", bold=True)
|
||||
click.echo("=" * len("Profile"))
|
||||
data = []
|
||||
if profile.get("username"):
|
||||
data.append(("Username:", profile["username"]))
|
||||
if profile.get("email"):
|
||||
data.append(("Email:", profile["email"]))
|
||||
if profile.get("firstname"):
|
||||
data.append(("First name:", profile["firstname"]))
|
||||
if profile.get("lastname"):
|
||||
data.append(("Last name:", profile["lastname"]))
|
||||
click.echo(tabulate(data, tablefmt="plain"))
|
||||
|
||||
|
||||
def print_packages(packages):
|
||||
click.echo()
|
||||
click.secho("Packages", fg="cyan")
|
||||
click.echo("=" * len("Packages"))
|
||||
for package in packages:
|
||||
click.echo()
|
||||
click.secho(package.get("name"), bold=True)
|
||||
click.echo("-" * len(package.get("name")))
|
||||
if package.get("description"):
|
||||
click.echo(package.get("description"))
|
||||
data = []
|
||||
expire = "-"
|
||||
if "subscription" in package:
|
||||
expire = util.parse_datetime(
|
||||
package["subscription"].get("end_at")
|
||||
or package["subscription"].get("next_bill_at")
|
||||
).strftime("%Y-%m-%d")
|
||||
data.append(("Expire:", expire))
|
||||
services = []
|
||||
for key in package:
|
||||
if not key.startswith("service."):
|
||||
continue
|
||||
if isinstance(package[key], dict):
|
||||
services.append(package[key].get("title"))
|
||||
else:
|
||||
services.append(package[key])
|
||||
if services:
|
||||
data.append(("Services:", ", ".join(services)))
|
||||
click.echo(tabulate(data, tablefmt="plain"))
|
||||
|
||||
|
||||
def print_subscriptions(subscriptions):
|
||||
click.echo()
|
||||
click.secho("Subscriptions", fg="cyan")
|
||||
click.echo("=" * len("Subscriptions"))
|
||||
for subscription in subscriptions:
|
||||
click.echo()
|
||||
click.secho(subscription.get("product_name"), bold=True)
|
||||
click.echo("-" * len(subscription.get("product_name")))
|
||||
data = [("State:", subscription.get("status"))]
|
||||
begin_at = util.parse_datetime(subscription.get("begin_at")).strftime("%c")
|
||||
data.append(("Start date:", begin_at or "-"))
|
||||
end_at = subscription.get("end_at")
|
||||
if end_at:
|
||||
end_at = util.parse_datetime(subscription.get("end_at")).strftime("%c")
|
||||
data.append(("End date:", end_at or "-"))
|
||||
next_bill_at = subscription.get("next_bill_at")
|
||||
if next_bill_at:
|
||||
next_bill_at = util.parse_datetime(
|
||||
subscription.get("next_bill_at")
|
||||
).strftime("%c")
|
||||
data.append(("Next payment:", next_bill_at or "-"))
|
||||
data.append(
|
||||
("Edit:", click.style(subscription.get("update_url"), fg="blue") or "-")
|
||||
)
|
||||
data.append(
|
||||
("Cancel:", click.style(subscription.get("cancel_url"), fg="blue") or "-")
|
||||
)
|
||||
click.echo(tabulate(data, tablefmt="plain"))
|
||||
32
platformio/account/commands/token.py
Normal file
32
platformio/account/commands/token.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("token", short_help="Get or regenerate Authentication Token")
|
||||
@click.option("-p", "--password", prompt=True, hide_input=True)
|
||||
@click.option("--regenerate", is_flag=True)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def account_token_cmd(password, regenerate, json_output):
|
||||
client = AccountClient()
|
||||
auth_token = client.auth_token(password, regenerate)
|
||||
if json_output:
|
||||
click.echo(json.dumps({"status": "success", "result": auth_token}))
|
||||
return
|
||||
click.secho("Personal Authentication Token: %s" % auth_token, fg="green")
|
||||
59
platformio/account/commands/update.py
Normal file
59
platformio/account/commands/update.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient, AccountNotAuthorized
|
||||
from platformio.account.validate import validate_email, validate_username
|
||||
|
||||
|
||||
@click.command("update", short_help="Update profile information")
|
||||
@click.option("--current-password", prompt=True, hide_input=True)
|
||||
@click.option("--username")
|
||||
@click.option("--email")
|
||||
@click.option("--firstname")
|
||||
@click.option("--lastname")
|
||||
def account_update_cmd(current_password, **kwargs):
|
||||
client = AccountClient()
|
||||
profile = client.get_profile()
|
||||
new_profile = profile.copy()
|
||||
if not any(kwargs.values()):
|
||||
for field in profile:
|
||||
new_profile[field] = click.prompt(
|
||||
field.replace("_", " ").capitalize(), default=profile[field]
|
||||
)
|
||||
if field == "email":
|
||||
validate_email(new_profile[field])
|
||||
if field == "username":
|
||||
validate_username(new_profile[field])
|
||||
else:
|
||||
new_profile.update({key: value for key, value in kwargs.items() if value})
|
||||
client.update_profile(new_profile, current_password)
|
||||
click.secho("Profile successfully updated!", fg="green")
|
||||
username_changed = new_profile["username"] != profile["username"]
|
||||
email_changed = new_profile["email"] != profile["email"]
|
||||
if not username_changed and not email_changed:
|
||||
return None
|
||||
try:
|
||||
client.logout()
|
||||
except AccountNotAuthorized:
|
||||
pass
|
||||
if email_changed:
|
||||
click.secho(
|
||||
"Please check your mail to verify your new email address and re-login. ",
|
||||
fg="yellow",
|
||||
)
|
||||
return None
|
||||
click.secho("Please re-login.", fg="yellow")
|
||||
return None
|
||||
38
platformio/account/org/cli.py
Normal file
38
platformio/account/org/cli.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.org.commands.add import org_add_cmd
|
||||
from platformio.account.org.commands.create import org_create_cmd
|
||||
from platformio.account.org.commands.destroy import org_destroy_cmd
|
||||
from platformio.account.org.commands.list import org_list_cmd
|
||||
from platformio.account.org.commands.remove import org_remove_cmd
|
||||
from platformio.account.org.commands.update import org_update_cmd
|
||||
|
||||
|
||||
@click.group(
|
||||
"account",
|
||||
commands=[
|
||||
org_add_cmd,
|
||||
org_create_cmd,
|
||||
org_destroy_cmd,
|
||||
org_list_cmd,
|
||||
org_remove_cmd,
|
||||
org_update_cmd,
|
||||
],
|
||||
short_help="Manage organizations",
|
||||
)
|
||||
def cli():
|
||||
pass
|
||||
34
platformio/account/org/commands/add.py
Normal file
34
platformio/account/org/commands/add.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("add", short_help="Add a new owner to organization")
|
||||
@click.argument(
|
||||
"orgname",
|
||||
)
|
||||
@click.argument(
|
||||
"username",
|
||||
)
|
||||
def org_add_cmd(orgname, username):
|
||||
client = AccountClient()
|
||||
client.add_org_owner(orgname, username)
|
||||
return click.secho(
|
||||
"The new owner `%s` has been successfully added to the `%s` organization."
|
||||
% (username, orgname),
|
||||
fg="green",
|
||||
)
|
||||
38
platformio/account/org/commands/create.py
Normal file
38
platformio/account/org/commands/create.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
from platformio.account.validate import validate_email, validate_orgname
|
||||
|
||||
|
||||
@click.command("create", short_help="Create a new organization")
|
||||
@click.argument(
|
||||
"orgname",
|
||||
callback=lambda _, __, value: validate_orgname(value),
|
||||
)
|
||||
@click.option(
|
||||
"--email", callback=lambda _, __, value: validate_email(value) if value else value
|
||||
)
|
||||
@click.option(
|
||||
"--displayname",
|
||||
)
|
||||
def org_create_cmd(orgname, email, displayname):
|
||||
client = AccountClient()
|
||||
client.create_org(orgname, email, displayname)
|
||||
return click.secho(
|
||||
"The organization `%s` has been successfully created." % orgname,
|
||||
fg="green",
|
||||
)
|
||||
34
platformio/account/org/commands/destroy.py
Normal file
34
platformio/account/org/commands/destroy.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("destroy", short_help="Destroy organization")
|
||||
@click.argument("orgname")
|
||||
def org_destroy_cmd(orgname):
|
||||
client = AccountClient()
|
||||
click.confirm(
|
||||
"Are you sure you want to delete the `%s` organization account?\n"
|
||||
"Warning! All linked data will be permanently removed and can not be restored."
|
||||
% orgname,
|
||||
abort=True,
|
||||
)
|
||||
client.destroy_org(orgname)
|
||||
return click.secho(
|
||||
"Organization `%s` has been destroyed." % orgname,
|
||||
fg="green",
|
||||
)
|
||||
48
platformio/account/org/commands/list.py
Normal file
48
platformio/account/org/commands/list.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
|
||||
import click
|
||||
from tabulate import tabulate
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("list", short_help="List organizations and their members")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def org_list_cmd(json_output):
|
||||
client = AccountClient()
|
||||
orgs = client.list_orgs()
|
||||
if json_output:
|
||||
return click.echo(json.dumps(orgs))
|
||||
if not orgs:
|
||||
return click.echo("You do not have any organization")
|
||||
for org in orgs:
|
||||
click.echo()
|
||||
click.secho(org.get("orgname"), fg="cyan")
|
||||
click.echo("-" * len(org.get("orgname")))
|
||||
data = []
|
||||
if org.get("displayname"):
|
||||
data.append(("Display Name:", org.get("displayname")))
|
||||
if org.get("email"):
|
||||
data.append(("Email:", org.get("email")))
|
||||
data.append(
|
||||
(
|
||||
"Owners:",
|
||||
", ".join((owner.get("username") for owner in org.get("owners"))),
|
||||
)
|
||||
)
|
||||
click.echo(tabulate(data, tablefmt="plain"))
|
||||
return click.echo()
|
||||
34
platformio/account/org/commands/remove.py
Normal file
34
platformio/account/org/commands/remove.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("remove", short_help="Remove an owner from organization")
|
||||
@click.argument(
|
||||
"orgname",
|
||||
)
|
||||
@click.argument(
|
||||
"username",
|
||||
)
|
||||
def org_remove_cmd(orgname, username):
|
||||
client = AccountClient()
|
||||
client.remove_org_owner(orgname, username)
|
||||
return click.secho(
|
||||
"The `%s` owner has been successfully removed from the `%s` organization."
|
||||
% (username, orgname),
|
||||
fg="green",
|
||||
)
|
||||
52
platformio/account/org/commands/update.py
Normal file
52
platformio/account/org/commands/update.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
from platformio.account.validate import validate_email, validate_orgname
|
||||
|
||||
|
||||
@click.command("update", short_help="Update organization")
|
||||
@click.argument("cur_orgname")
|
||||
@click.option(
|
||||
"--orgname",
|
||||
callback=lambda _, __, value: validate_orgname(value),
|
||||
help="A new orgname",
|
||||
)
|
||||
@click.option("--email")
|
||||
@click.option("--displayname")
|
||||
def org_update_cmd(cur_orgname, **kwargs):
|
||||
client = AccountClient()
|
||||
org = client.get_org(cur_orgname)
|
||||
del org["owners"]
|
||||
new_org = org.copy()
|
||||
if not any(kwargs.values()):
|
||||
for field in org:
|
||||
new_org[field] = click.prompt(
|
||||
field.replace("_", " ").capitalize(), default=org[field]
|
||||
)
|
||||
if field == "email":
|
||||
validate_email(new_org[field])
|
||||
if field == "orgname":
|
||||
validate_orgname(new_org[field])
|
||||
else:
|
||||
new_org.update(
|
||||
{key.replace("new_", ""): value for key, value in kwargs.items() if value}
|
||||
)
|
||||
client.update_org(cur_orgname, new_org)
|
||||
return click.secho(
|
||||
"The organization `%s` has been successfully updated." % cur_orgname,
|
||||
fg="green",
|
||||
)
|
||||
38
platformio/account/team/cli.py
Normal file
38
platformio/account/team/cli.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.team.commands.add import team_add_cmd
|
||||
from platformio.account.team.commands.create import team_create_cmd
|
||||
from platformio.account.team.commands.destroy import team_destroy_cmd
|
||||
from platformio.account.team.commands.list import team_list_cmd
|
||||
from platformio.account.team.commands.remove import team_remove_cmd
|
||||
from platformio.account.team.commands.update import team_update_cmd
|
||||
|
||||
|
||||
@click.group(
|
||||
"team",
|
||||
commands=[
|
||||
team_add_cmd,
|
||||
team_create_cmd,
|
||||
team_destroy_cmd,
|
||||
team_list_cmd,
|
||||
team_remove_cmd,
|
||||
team_update_cmd,
|
||||
],
|
||||
short_help="Manage organization teams",
|
||||
)
|
||||
def cli():
|
||||
pass
|
||||
38
platformio/account/team/commands/add.py
Normal file
38
platformio/account/team/commands/add.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
from platformio.account.validate import validate_orgname_teamname
|
||||
|
||||
|
||||
@click.command("add", short_help="Add a new member to team")
|
||||
@click.argument(
|
||||
"orgname_teamname",
|
||||
metavar="ORGNAME:TEAMNAME",
|
||||
callback=lambda _, __, value: validate_orgname_teamname(value),
|
||||
)
|
||||
@click.argument(
|
||||
"username",
|
||||
)
|
||||
def team_add_cmd(orgname_teamname, username):
|
||||
orgname, teamname = orgname_teamname.split(":", 1)
|
||||
client = AccountClient()
|
||||
client.add_team_member(orgname, teamname, username)
|
||||
return click.secho(
|
||||
"The new member %s has been successfully added to the %s team."
|
||||
% (username, teamname),
|
||||
fg="green",
|
||||
)
|
||||
39
platformio/account/team/commands/create.py
Normal file
39
platformio/account/team/commands/create.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
from platformio.account.validate import validate_orgname_teamname
|
||||
|
||||
|
||||
@click.command("create", short_help="Create a new team")
|
||||
@click.argument(
|
||||
"orgname_teamname",
|
||||
metavar="ORGNAME:TEAMNAME",
|
||||
callback=lambda _, __, value: validate_orgname_teamname(
|
||||
value, teamname_validate=True
|
||||
),
|
||||
)
|
||||
@click.option(
|
||||
"--description",
|
||||
)
|
||||
def team_create_cmd(orgname_teamname, description):
|
||||
orgname, teamname = orgname_teamname.split(":", 1)
|
||||
client = AccountClient()
|
||||
client.create_team(orgname, teamname, description)
|
||||
return click.secho(
|
||||
"The team %s has been successfully created." % teamname,
|
||||
fg="green",
|
||||
)
|
||||
40
platformio/account/team/commands/destroy.py
Normal file
40
platformio/account/team/commands/destroy.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
from platformio.account.validate import validate_orgname_teamname
|
||||
|
||||
|
||||
@click.command("destroy", short_help="Destroy a team")
|
||||
@click.argument(
|
||||
"orgname_teamname",
|
||||
metavar="ORGNAME:TEAMNAME",
|
||||
callback=lambda _, __, value: validate_orgname_teamname(value),
|
||||
)
|
||||
def team_destroy_cmd(orgname_teamname):
|
||||
orgname, teamname = orgname_teamname.split(":", 1)
|
||||
click.confirm(
|
||||
click.style(
|
||||
"Are you sure you want to destroy the %s team?" % teamname, fg="yellow"
|
||||
),
|
||||
abort=True,
|
||||
)
|
||||
client = AccountClient()
|
||||
client.destroy_team(orgname, teamname)
|
||||
return click.secho(
|
||||
"The team %s has been successfully destroyed." % teamname,
|
||||
fg="green",
|
||||
)
|
||||
59
platformio/account/team/commands/list.py
Normal file
59
platformio/account/team/commands/list.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
|
||||
import click
|
||||
from tabulate import tabulate
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("list", short_help="List teams")
|
||||
@click.argument("orgname", required=False)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def team_list_cmd(orgname, json_output):
|
||||
client = AccountClient()
|
||||
data = {}
|
||||
if not orgname:
|
||||
for item in client.list_orgs():
|
||||
teams = client.list_teams(item.get("orgname"))
|
||||
data[item.get("orgname")] = teams
|
||||
else:
|
||||
teams = client.list_teams(orgname)
|
||||
data[orgname] = teams
|
||||
if json_output:
|
||||
return click.echo(json.dumps(data[orgname] if orgname else data))
|
||||
if not any(data.values()):
|
||||
return click.secho("You do not have any teams.", fg="yellow")
|
||||
for org_name, teams in data.items():
|
||||
for team in teams:
|
||||
click.echo()
|
||||
click.secho("%s:%s" % (org_name, team.get("name")), fg="cyan")
|
||||
click.echo("-" * len("%s:%s" % (org_name, team.get("name"))))
|
||||
table_data = []
|
||||
if team.get("description"):
|
||||
table_data.append(("Description:", team.get("description")))
|
||||
table_data.append(
|
||||
(
|
||||
"Members:",
|
||||
", ".join(
|
||||
(member.get("username") for member in team.get("members"))
|
||||
)
|
||||
if team.get("members")
|
||||
else "-",
|
||||
)
|
||||
)
|
||||
click.echo(tabulate(table_data, tablefmt="plain"))
|
||||
return click.echo()
|
||||
36
platformio/account/team/commands/remove.py
Normal file
36
platformio/account/team/commands/remove.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
from platformio.account.validate import validate_orgname_teamname
|
||||
|
||||
|
||||
@click.command("remove", short_help="Remove a member from team")
|
||||
@click.argument(
|
||||
"orgname_teamname",
|
||||
metavar="ORGNAME:TEAMNAME",
|
||||
callback=lambda _, __, value: validate_orgname_teamname(value),
|
||||
)
|
||||
@click.argument("username")
|
||||
def team_remove_cmd(orgname_teamname, username):
|
||||
orgname, teamname = orgname_teamname.split(":", 1)
|
||||
client = AccountClient()
|
||||
client.remove_team_member(orgname, teamname, username)
|
||||
return click.secho(
|
||||
"The %s member has been successfully removed from the %s team."
|
||||
% (username, teamname),
|
||||
fg="green",
|
||||
)
|
||||
55
platformio/account/team/commands/update.py
Normal file
55
platformio/account/team/commands/update.py
Normal file
@@ -0,0 +1,55 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
from platformio.account.validate import validate_orgname_teamname, validate_teamname
|
||||
|
||||
|
||||
@click.command("update", short_help="Update team")
|
||||
@click.argument(
|
||||
"orgname_teamname",
|
||||
metavar="ORGNAME:TEAMNAME",
|
||||
callback=lambda _, __, value: validate_orgname_teamname(value),
|
||||
)
|
||||
@click.option(
|
||||
"--name",
|
||||
callback=lambda _, __, value: validate_teamname(value),
|
||||
help="A new team name",
|
||||
)
|
||||
@click.option(
|
||||
"--description",
|
||||
)
|
||||
def team_update_cmd(orgname_teamname, **kwargs):
|
||||
orgname, teamname = orgname_teamname.split(":", 1)
|
||||
client = AccountClient()
|
||||
team = client.get_team(orgname, teamname)
|
||||
del team["id"]
|
||||
del team["members"]
|
||||
new_team = team.copy()
|
||||
if not any(kwargs.values()):
|
||||
for field in team:
|
||||
new_team[field] = click.prompt(
|
||||
field.replace("_", " ").capitalize(), default=team[field]
|
||||
)
|
||||
if field == "name":
|
||||
validate_teamname(new_team[field])
|
||||
else:
|
||||
new_team.update({key: value for key, value in kwargs.items() if value})
|
||||
client.update_team(orgname, teamname, new_team)
|
||||
return click.secho(
|
||||
"The team %s has been successfully updated." % teamname,
|
||||
fg="green",
|
||||
)
|
||||
79
platformio/account/validate.py
Normal file
79
platformio/account/validate.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import re
|
||||
|
||||
import click
|
||||
|
||||
|
||||
def validate_username(value, field="username"):
|
||||
value = str(value).strip()
|
||||
if not re.match(r"^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,37}$", value, flags=re.I):
|
||||
raise click.BadParameter(
|
||||
"Invalid %s format. "
|
||||
"%s must contain only alphanumeric characters "
|
||||
"or single hyphens, cannot begin or end with a hyphen, "
|
||||
"and must not be longer than 38 characters."
|
||||
% (field.lower(), field.capitalize())
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
def validate_email(value):
|
||||
value = str(value).strip()
|
||||
if not re.match(r"^[a-z\d_.+-]+@[a-z\d\-]+\.[a-z\d\-.]+$", value, flags=re.I):
|
||||
raise click.BadParameter("Invalid email address")
|
||||
return value
|
||||
|
||||
|
||||
def validate_password(value):
|
||||
value = str(value).strip()
|
||||
if not re.match(r"^(?=.*[a-z])(?=.*\d).{8,}$", value):
|
||||
raise click.BadParameter(
|
||||
"Invalid password format. "
|
||||
"Password must contain at least 8 characters"
|
||||
" including a number and a lowercase letter"
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
def validate_orgname(value):
|
||||
return validate_username(value, "Organization name")
|
||||
|
||||
|
||||
def validate_orgname_teamname(value, teamname_validate=False):
|
||||
if ":" not in value:
|
||||
raise click.BadParameter(
|
||||
"Please specify organization and team name in the next"
|
||||
" format - orgname:teamname. For example, mycompany:DreamTeam"
|
||||
)
|
||||
teamname = str(value.strip().split(":", 1)[1])
|
||||
if teamname_validate:
|
||||
validate_teamname(teamname)
|
||||
return value
|
||||
|
||||
|
||||
def validate_teamname(value):
|
||||
if not value:
|
||||
return value
|
||||
value = str(value).strip()
|
||||
if not re.match(r"^[a-z\d](?:[a-z\d]|[\-_ ](?=[a-z\d])){0,19}$", value, flags=re.I):
|
||||
raise click.BadParameter(
|
||||
"Invalid team name format. "
|
||||
"Team name must only contain alphanumeric characters, "
|
||||
"single hyphens, underscores, spaces. It can not "
|
||||
"begin or end with a hyphen or a underscore and must"
|
||||
" not be longer than 20 characters."
|
||||
)
|
||||
return value
|
||||
@@ -21,38 +21,22 @@ import os
|
||||
import platform
|
||||
import socket
|
||||
import uuid
|
||||
from os.path import dirname, isdir, isfile, join, realpath
|
||||
|
||||
from platformio import __version__, exception, fs, proc
|
||||
from platformio.compat import WINDOWS, dump_json_to_unicode, hashlib_encode_data
|
||||
from platformio.compat import IS_WINDOWS, hashlib_encode_data
|
||||
from platformio.package.lockfile import LockFile
|
||||
from platformio.project.helpers import get_default_projects_dir, get_project_core_dir
|
||||
from platformio.project.config import ProjectConfig
|
||||
from platformio.project.helpers import get_default_projects_dir
|
||||
|
||||
|
||||
def projects_dir_validate(projects_dir):
|
||||
assert isdir(projects_dir)
|
||||
return realpath(projects_dir)
|
||||
assert os.path.isdir(projects_dir)
|
||||
return os.path.abspath(projects_dir)
|
||||
|
||||
|
||||
DEFAULT_SETTINGS = {
|
||||
"auto_update_libraries": {
|
||||
"description": "Automatically update libraries (Yes/No)",
|
||||
"value": False,
|
||||
},
|
||||
"auto_update_platforms": {
|
||||
"description": "Automatically update platforms (Yes/No)",
|
||||
"value": False,
|
||||
},
|
||||
"check_libraries_interval": {
|
||||
"description": "Check for the library updates interval (days)",
|
||||
"value": 7,
|
||||
},
|
||||
"check_platformio_interval": {
|
||||
"description": "Check for the new PlatformIO interval (days)",
|
||||
"value": 3,
|
||||
},
|
||||
"check_platforms_interval": {
|
||||
"description": "Check for the platform updates interval (days)",
|
||||
"description": "Check for the new PlatformIO Core interval (days)",
|
||||
"value": 7,
|
||||
},
|
||||
"check_prune_system_threshold": {
|
||||
@@ -64,7 +48,7 @@ DEFAULT_SETTINGS = {
|
||||
"value": True,
|
||||
},
|
||||
"enable_telemetry": {
|
||||
"description": ("Telemetry service <http://bit.ly/pio-telemetry> (Yes/No)"),
|
||||
"description": ("Telemetry service <https://bit.ly/pio-telemetry> (Yes/No)"),
|
||||
"value": True,
|
||||
},
|
||||
"force_verbose": {
|
||||
@@ -86,12 +70,15 @@ SESSION_VARS = {
|
||||
}
|
||||
|
||||
|
||||
class State(object):
|
||||
class State:
|
||||
def __init__(self, path=None, lock=False):
|
||||
self.path = path
|
||||
self.lock = lock
|
||||
if not self.path:
|
||||
self.path = join(get_project_core_dir(), "appstate.json")
|
||||
core_dir = ProjectConfig.get_instance().get("platformio", "core_dir")
|
||||
if not os.path.isdir(core_dir):
|
||||
os.makedirs(core_dir)
|
||||
self.path = os.path.join(core_dir, "appstate.json")
|
||||
self._storage = {}
|
||||
self._lockfile = None
|
||||
self.modified = False
|
||||
@@ -99,7 +86,7 @@ class State(object):
|
||||
def __enter__(self):
|
||||
try:
|
||||
self._lock_state_file()
|
||||
if isfile(self.path):
|
||||
if os.path.isfile(self.path):
|
||||
self._storage = fs.load_json(self.path)
|
||||
assert isinstance(self._storage, dict)
|
||||
except (
|
||||
@@ -114,10 +101,12 @@ class State(object):
|
||||
def __exit__(self, type_, value, traceback):
|
||||
if self.modified:
|
||||
try:
|
||||
with open(self.path, "w") as fp:
|
||||
fp.write(dump_json_to_unicode(self._storage))
|
||||
except IOError:
|
||||
raise exception.HomeDirPermissionsError(get_project_core_dir())
|
||||
with open(self.path, mode="w", encoding="utf8") as fp:
|
||||
fp.write(json.dumps(self._storage))
|
||||
except IOError as exc:
|
||||
raise exception.HomeDirPermissionsError(
|
||||
os.path.dirname(self.path)
|
||||
) from exc
|
||||
self._unlock_state_file()
|
||||
|
||||
def _lock_state_file(self):
|
||||
@@ -126,8 +115,8 @@ class State(object):
|
||||
self._lockfile = LockFile(self.path)
|
||||
try:
|
||||
self._lockfile.acquire()
|
||||
except IOError:
|
||||
raise exception.HomeDirPermissionsError(dirname(self.path))
|
||||
except IOError as exc:
|
||||
raise exception.HomeDirPermissionsError(os.path.dirname(self.path)) from exc
|
||||
|
||||
def _unlock_state_file(self):
|
||||
if hasattr(self, "_lockfile") and self._lockfile:
|
||||
@@ -182,8 +171,8 @@ def sanitize_setting(name, value):
|
||||
value = str(value).lower() in ("true", "yes", "y", "1")
|
||||
elif isinstance(defdata["value"], int):
|
||||
value = int(value)
|
||||
except Exception:
|
||||
raise exception.InvalidSettingValue(value, name)
|
||||
except Exception as exc:
|
||||
raise exception.InvalidSettingValue(value, name) from exc
|
||||
return value
|
||||
|
||||
|
||||
@@ -250,34 +239,19 @@ def is_disabled_progressbar():
|
||||
|
||||
|
||||
def get_cid():
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from platformio.clients.http import fetch_remote_content
|
||||
|
||||
cid = get_state_item("cid")
|
||||
if cid:
|
||||
return cid
|
||||
uid = None
|
||||
if os.getenv("C9_UID"):
|
||||
uid = os.getenv("C9_UID")
|
||||
if os.getenv("GITHUB_USER"):
|
||||
uid = os.getenv("GITHUB_USER")
|
||||
elif os.getenv("GITPOD_GIT_USER_NAME"):
|
||||
uid = os.getenv("GITPOD_GIT_USER_NAME")
|
||||
elif os.getenv("CHE_API", os.getenv("CHE_API_ENDPOINT")):
|
||||
try:
|
||||
uid = json.loads(
|
||||
fetch_remote_content(
|
||||
"{api}/user?token={token}".format(
|
||||
api=os.getenv("CHE_API", os.getenv("CHE_API_ENDPOINT")),
|
||||
token=os.getenv("USER_TOKEN"),
|
||||
)
|
||||
)
|
||||
).get("id")
|
||||
except: # pylint: disable=bare-except
|
||||
pass
|
||||
if not uid:
|
||||
uid = uuid.getnode()
|
||||
cid = uuid.UUID(bytes=hashlib.md5(hashlib_encode_data(uid)).digest())
|
||||
cid = str(cid)
|
||||
if WINDOWS or os.getuid() > 0: # pylint: disable=no-member
|
||||
if IS_WINDOWS or os.getuid() > 0: # pylint: disable=no-member
|
||||
set_state_item("cid", cid)
|
||||
return cid
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from os import environ, makedirs
|
||||
from os.path import isdir, join
|
||||
from time import time
|
||||
|
||||
import click
|
||||
@@ -28,8 +28,7 @@ from SCons.Script import DefaultEnvironment # pylint: disable=import-error
|
||||
from SCons.Script import Import # pylint: disable=import-error
|
||||
from SCons.Script import Variables # pylint: disable=import-error
|
||||
|
||||
from platformio import compat, fs
|
||||
from platformio.compat import dump_json_to_unicode
|
||||
from platformio import app, compat, fs
|
||||
from platformio.platform.base import PlatformBase
|
||||
from platformio.proc import get_pythonexe_path
|
||||
from platformio.project.helpers import get_project_dir
|
||||
@@ -45,38 +44,43 @@ clivars.AddVariables(
|
||||
("PIOENV",),
|
||||
("PIOTEST_RUNNING_NAME",),
|
||||
("UPLOAD_PORT",),
|
||||
("PROGRAM_ARGS",),
|
||||
)
|
||||
|
||||
DEFAULT_ENV_OPTIONS = dict(
|
||||
tools=[
|
||||
"ar",
|
||||
"as",
|
||||
"cc",
|
||||
"c++",
|
||||
"link",
|
||||
"piohooks",
|
||||
"pioasm",
|
||||
"platformio",
|
||||
"piotarget",
|
||||
"pioplatform",
|
||||
"pioproject",
|
||||
"piomaxlen",
|
||||
"pioplatform",
|
||||
"piotest",
|
||||
"piotarget",
|
||||
"piolib",
|
||||
"pioupload",
|
||||
"piomisc",
|
||||
"pioide",
|
||||
"piosize",
|
||||
"pioino",
|
||||
"piomisc",
|
||||
"piointegration",
|
||||
"piomaxlen",
|
||||
],
|
||||
toolpath=[join(fs.get_source_dir(), "builder", "tools")],
|
||||
toolpath=[os.path.join(fs.get_source_dir(), "builder", "tools")],
|
||||
variables=clivars,
|
||||
# Propagating External Environment
|
||||
ENV=environ,
|
||||
ENV=os.environ,
|
||||
UNIX_TIME=int(time()),
|
||||
BUILD_DIR=join("$PROJECT_BUILD_DIR", "$PIOENV"),
|
||||
BUILD_SRC_DIR=join("$BUILD_DIR", "src"),
|
||||
BUILD_TEST_DIR=join("$BUILD_DIR", "test"),
|
||||
COMPILATIONDB_PATH=join("$BUILD_DIR", "compile_commands.json"),
|
||||
BUILD_DIR=os.path.join("$PROJECT_BUILD_DIR", "$PIOENV"),
|
||||
BUILD_SRC_DIR=os.path.join("$BUILD_DIR", "src"),
|
||||
BUILD_TEST_DIR=os.path.join("$BUILD_DIR", "test"),
|
||||
COMPILATIONDB_PATH=os.path.join("$PROJECT_DIR", "compile_commands.json"),
|
||||
LIBPATH=["$BUILD_DIR"],
|
||||
PROGNAME="program",
|
||||
PROG_PATH=join("$BUILD_DIR", "$PROGNAME$PROGSUFFIX"),
|
||||
PROGPATH=os.path.join("$BUILD_DIR", "$PROGNAME$PROGSUFFIX"),
|
||||
PROG_PATH="$PROGPATH", # deprecated
|
||||
PYTHONEXE=get_pythonexe_path(),
|
||||
IDE_EXTRA_DATA={},
|
||||
)
|
||||
@@ -108,67 +112,74 @@ env.Replace(
|
||||
|
||||
# Setup project optional directories
|
||||
config = env.GetProjectConfig()
|
||||
app.set_session_var("custom_project_conf", config.path)
|
||||
|
||||
env.Replace(
|
||||
PROJECT_DIR=get_project_dir(),
|
||||
PROJECT_CORE_DIR=config.get_optional_dir("core"),
|
||||
PROJECT_PACKAGES_DIR=config.get_optional_dir("packages"),
|
||||
PROJECT_WORKSPACE_DIR=config.get_optional_dir("workspace"),
|
||||
PROJECT_LIBDEPS_DIR=config.get_optional_dir("libdeps"),
|
||||
PROJECT_INCLUDE_DIR=config.get_optional_dir("include"),
|
||||
PROJECT_SRC_DIR=config.get_optional_dir("src"),
|
||||
PROJECTSRC_DIR=config.get_optional_dir("src"), # legacy for dev/platform
|
||||
PROJECT_TEST_DIR=config.get_optional_dir("test"),
|
||||
PROJECT_DATA_DIR=config.get_optional_dir("data"),
|
||||
PROJECTDATA_DIR=config.get_optional_dir("data"), # legacy for dev/platform
|
||||
PROJECT_BUILD_DIR=config.get_optional_dir("build"),
|
||||
BUILD_CACHE_DIR=config.get_optional_dir("build_cache"),
|
||||
PROJECT_CORE_DIR=config.get("platformio", "core_dir"),
|
||||
PROJECT_PACKAGES_DIR=config.get("platformio", "packages_dir"),
|
||||
PROJECT_WORKSPACE_DIR=config.get("platformio", "workspace_dir"),
|
||||
PROJECT_LIBDEPS_DIR=config.get("platformio", "libdeps_dir"),
|
||||
PROJECT_INCLUDE_DIR=config.get("platformio", "include_dir"),
|
||||
PROJECT_SRC_DIR=config.get("platformio", "src_dir"),
|
||||
PROJECTSRC_DIR="$PROJECT_SRC_DIR", # legacy for dev/platform
|
||||
PROJECT_TEST_DIR=config.get("platformio", "test_dir"),
|
||||
PROJECT_DATA_DIR=config.get("platformio", "data_dir"),
|
||||
PROJECTDATA_DIR="$PROJECT_DATA_DIR", # legacy for dev/platform
|
||||
PROJECT_BUILD_DIR=config.get("platformio", "build_dir"),
|
||||
BUILD_CACHE_DIR=config.get("platformio", "build_cache_dir"),
|
||||
LIBSOURCE_DIRS=[
|
||||
config.get_optional_dir("lib"),
|
||||
join("$PROJECT_LIBDEPS_DIR", "$PIOENV"),
|
||||
config.get_optional_dir("globallib"),
|
||||
config.get("platformio", "lib_dir"),
|
||||
os.path.join("$PROJECT_LIBDEPS_DIR", "$PIOENV"),
|
||||
config.get("platformio", "globallib_dir"),
|
||||
],
|
||||
)
|
||||
|
||||
if (
|
||||
compat.WINDOWS
|
||||
and sys.version_info >= (3, 8)
|
||||
and env["PROJECT_DIR"].startswith("\\\\")
|
||||
):
|
||||
click.secho(
|
||||
"There is a known issue with Python 3.8+ and mapped network drives on "
|
||||
"Windows.\nPlease downgrade Python to the latest 3.7. More details at:\n"
|
||||
"https://github.com/platformio/platformio-core/issues/3417",
|
||||
fg="yellow",
|
||||
)
|
||||
|
||||
if env.subst("$BUILD_CACHE_DIR"):
|
||||
if not isdir(env.subst("$BUILD_CACHE_DIR")):
|
||||
makedirs(env.subst("$BUILD_CACHE_DIR"))
|
||||
env.CacheDir("$BUILD_CACHE_DIR")
|
||||
|
||||
if int(ARGUMENTS.get("ISATTY", 0)):
|
||||
# pylint: disable=protected-access
|
||||
click._compat.isatty = lambda stream: True
|
||||
|
||||
if env.GetOption("clean"):
|
||||
env.PioClean(env.subst("$BUILD_DIR"))
|
||||
if compat.IS_WINDOWS and sys.version_info >= (3, 8) and os.getcwd().startswith("\\\\"):
|
||||
click.secho("!!! WARNING !!!\t\t" * 3, fg="red")
|
||||
click.secho(
|
||||
"Your project is located on a mapped network drive but the "
|
||||
"current command-line shell does not support the UNC paths.",
|
||||
fg="yellow",
|
||||
)
|
||||
click.secho(
|
||||
"Please move your project to a physical drive or check this workaround: "
|
||||
"https://bit.ly/3kuU5mP\n",
|
||||
fg="yellow",
|
||||
)
|
||||
|
||||
if env.subst("$BUILD_CACHE_DIR"):
|
||||
if not os.path.isdir(env.subst("$BUILD_CACHE_DIR")):
|
||||
os.makedirs(env.subst("$BUILD_CACHE_DIR"))
|
||||
env.CacheDir("$BUILD_CACHE_DIR")
|
||||
|
||||
is_clean_all = "cleanall" in COMMAND_LINE_TARGETS
|
||||
if env.GetOption("clean") or is_clean_all:
|
||||
env.PioClean(is_clean_all)
|
||||
env.Exit(0)
|
||||
elif not int(ARGUMENTS.get("PIOVERBOSE", 0)):
|
||||
|
||||
if not int(ARGUMENTS.get("PIOVERBOSE", 0)):
|
||||
click.echo("Verbose mode can be enabled via `-v, --verbose` option")
|
||||
|
||||
# Dynamically load dependent tools
|
||||
if "compiledb" in COMMAND_LINE_TARGETS:
|
||||
env.Tool("compilation_db")
|
||||
|
||||
if not isdir(env.subst("$BUILD_DIR")):
|
||||
makedirs(env.subst("$BUILD_DIR"))
|
||||
if not os.path.isdir(env.subst("$BUILD_DIR")):
|
||||
os.makedirs(env.subst("$BUILD_DIR"))
|
||||
|
||||
env.LoadProjectOptions()
|
||||
env.LoadPioPlatform()
|
||||
|
||||
env.SConscriptChdir(0)
|
||||
env.SConsignFile(
|
||||
join("$BUILD_DIR", ".sconsign%d%d" % (sys.version_info[0], sys.version_info[1]))
|
||||
os.path.join(
|
||||
"$BUILD_DIR", ".sconsign%d%d" % (sys.version_info[0], sys.version_info[1])
|
||||
)
|
||||
)
|
||||
|
||||
for item in env.GetExtraScripts("pre"):
|
||||
@@ -190,7 +201,7 @@ for item in env.GetExtraScripts("post"):
|
||||
if env.get("SIZETOOL") and not (
|
||||
set(["nobuild", "sizedata"]) & set(COMMAND_LINE_TARGETS)
|
||||
):
|
||||
env.Depends(["upload", "program"], "checkprogsize")
|
||||
env.Depends("upload", "checkprogsize")
|
||||
# Replace platform's "size" target with our
|
||||
_new_targets = [t for t in DEFAULT_TARGETS if str(t) != "size"]
|
||||
Default(None)
|
||||
@@ -202,33 +213,39 @@ if "compiledb" in COMMAND_LINE_TARGETS:
|
||||
|
||||
# Print configured protocols
|
||||
env.AddPreAction(
|
||||
["upload", "program"],
|
||||
"upload",
|
||||
env.VerboseAction(
|
||||
lambda source, target, env: env.PrintUploadInfo(),
|
||||
"Configuring upload protocol...",
|
||||
),
|
||||
)
|
||||
|
||||
AlwaysBuild(env.Alias("debug", DEFAULT_TARGETS))
|
||||
AlwaysBuild(env.Alias("__debug", DEFAULT_TARGETS))
|
||||
AlwaysBuild(env.Alias("__test", DEFAULT_TARGETS))
|
||||
|
||||
env.ProcessDelayedActions()
|
||||
|
||||
##############################################################################
|
||||
|
||||
if "envdump" in COMMAND_LINE_TARGETS:
|
||||
click.echo(env.Dump())
|
||||
env.Exit(0)
|
||||
|
||||
if "idedata" in COMMAND_LINE_TARGETS:
|
||||
if env.IsIntegrationDump():
|
||||
projenv = None
|
||||
try:
|
||||
Import("projenv")
|
||||
except: # pylint: disable=bare-except
|
||||
projenv = env
|
||||
click.echo(
|
||||
"\n%s\n"
|
||||
% dump_json_to_unicode(
|
||||
projenv.DumpIDEData(env) # pylint: disable=undefined-variable
|
||||
)
|
||||
)
|
||||
data = projenv.DumpIntegrationData(env)
|
||||
# dump to file for the further reading by project.helpers.load_build_metadata
|
||||
with open(
|
||||
projenv.subst(os.path.join("$BUILD_DIR", "idedata.json")),
|
||||
mode="w",
|
||||
encoding="utf8",
|
||||
) as fp:
|
||||
json.dump(data, fp)
|
||||
click.echo("\n%s\n" % json.dumps(data)) # pylint: disable=undefined-variable
|
||||
env.Exit(0)
|
||||
|
||||
if "sizedata" in COMMAND_LINE_TARGETS:
|
||||
|
||||
@@ -58,7 +58,7 @@ class __CompilationDbNode(SCons.Node.Python.Value):
|
||||
|
||||
|
||||
def changed_since_last_build_node(*args, **kwargs):
|
||||
""" Dummy decider to force always building"""
|
||||
"""Dummy decider to force always building"""
|
||||
return True
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ def WriteCompilationDb(target, source, env):
|
||||
item["file"] = os.path.abspath(item["file"])
|
||||
entries.append(item)
|
||||
|
||||
with open(str(target[0]), "w") as target_file:
|
||||
with open(str(target[0]), mode="w", encoding="utf8") as target_file:
|
||||
json.dump(
|
||||
entries, target_file, sort_keys=True, indent=4, separators=(",", ": ")
|
||||
)
|
||||
|
||||
31
platformio/builder/tools/pioasm.py
Normal file
31
platformio/builder/tools/pioasm.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import SCons.Tool.asm # pylint: disable=import-error
|
||||
|
||||
#
|
||||
# Resolve https://github.com/platformio/platformio-core/issues/3917
|
||||
# Avoid forcing .S to bare assembly on Windows OS
|
||||
#
|
||||
|
||||
if ".S" in SCons.Tool.asm.ASSuffixes:
|
||||
SCons.Tool.asm.ASSuffixes.remove(".S")
|
||||
if ".S" not in SCons.Tool.asm.ASPPSuffixes:
|
||||
SCons.Tool.asm.ASPPSuffixes.append(".S")
|
||||
|
||||
|
||||
generate = SCons.Tool.asm.generate
|
||||
exists = SCons.Tool.asm.exists
|
||||
52
platformio/builder/tools/piohooks.py
Normal file
52
platformio/builder/tools/piohooks.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
def AddActionWrapper(handler):
|
||||
def wraps(env, files, action):
|
||||
if not isinstance(files, (list, tuple, set)):
|
||||
files = [files]
|
||||
known_nodes = []
|
||||
unknown_files = []
|
||||
for item in files:
|
||||
nodes = env.arg2nodes(item, env.fs.Entry)
|
||||
if nodes and nodes[0].exists():
|
||||
known_nodes.extend(nodes)
|
||||
else:
|
||||
unknown_files.append(item)
|
||||
if unknown_files:
|
||||
env.Append(**{"_PIO_DELAYED_ACTIONS": [(handler, unknown_files, action)]})
|
||||
if known_nodes:
|
||||
return handler(known_nodes, action)
|
||||
return []
|
||||
|
||||
return wraps
|
||||
|
||||
|
||||
def ProcessDelayedActions(env):
|
||||
for func, nodes, action in env.get("_PIO_DELAYED_ACTIONS", []):
|
||||
func(nodes, action)
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.Replace(**{"_PIO_DELAYED_ACTIONS": []})
|
||||
env.AddMethod(AddActionWrapper(env.AddPreAction), "AddPreAction")
|
||||
env.AddMethod(AddActionWrapper(env.AddPostAction), "AddPostAction")
|
||||
env.AddMethod(ProcessDelayedActions)
|
||||
|
||||
|
||||
def exists(_):
|
||||
return True
|
||||
259
platformio/builder/tools/pioino.py
Normal file
259
platformio/builder/tools/pioino.py
Normal file
@@ -0,0 +1,259 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import atexit
|
||||
import glob
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
|
||||
import click
|
||||
|
||||
from platformio.compat import get_filesystem_encoding, get_locale_encoding
|
||||
|
||||
|
||||
class InoToCPPConverter:
|
||||
|
||||
PROTOTYPE_RE = re.compile(
|
||||
r"""^(
|
||||
(?:template\<.*\>\s*)? # template
|
||||
([a-z_\d\&]+\*?\s+){1,2} # return type
|
||||
([a-z_\d]+\s*) # name of prototype
|
||||
\([a-z_,\.\*\&\[\]\s\d]*\) # arguments
|
||||
)\s*(\{|;) # must end with `{` or `;`
|
||||
""",
|
||||
re.X | re.M | re.I,
|
||||
)
|
||||
DETECTMAIN_RE = re.compile(r"void\s+(setup|loop)\s*\(", re.M | re.I)
|
||||
PROTOPTRS_TPLRE = r"\([^&\(]*&(%s)[^\)]*\)"
|
||||
|
||||
def __init__(self, env):
|
||||
self.env = env
|
||||
self._main_ino = None
|
||||
self._safe_encoding = None
|
||||
|
||||
def read_safe_contents(self, path):
|
||||
error_reported = False
|
||||
for encoding in (
|
||||
"utf-8",
|
||||
None,
|
||||
get_filesystem_encoding(),
|
||||
get_locale_encoding(),
|
||||
"latin-1",
|
||||
):
|
||||
try:
|
||||
with io.open(path, encoding=encoding) as fp:
|
||||
contents = fp.read()
|
||||
self._safe_encoding = encoding
|
||||
return contents
|
||||
except UnicodeDecodeError:
|
||||
if not error_reported:
|
||||
error_reported = True
|
||||
click.secho(
|
||||
"Unicode decode error has occurred, please remove invalid "
|
||||
"(non-ASCII or non-UTF8) characters from %s file or convert it to UTF-8"
|
||||
% path,
|
||||
fg="yellow",
|
||||
err=True,
|
||||
)
|
||||
return ""
|
||||
|
||||
def write_safe_contents(self, path, contents):
|
||||
with io.open(
|
||||
path, "w", encoding=self._safe_encoding, errors="backslashreplace"
|
||||
) as fp:
|
||||
return fp.write(contents)
|
||||
|
||||
def is_main_node(self, contents):
|
||||
return self.DETECTMAIN_RE.search(contents)
|
||||
|
||||
def convert(self, nodes):
|
||||
contents = self.merge(nodes)
|
||||
if not contents:
|
||||
return None
|
||||
return self.process(contents)
|
||||
|
||||
def merge(self, nodes):
|
||||
assert nodes
|
||||
lines = []
|
||||
for node in nodes:
|
||||
contents = self.read_safe_contents(node.get_path())
|
||||
_lines = ['# 1 "%s"' % node.get_path().replace("\\", "/"), contents]
|
||||
if self.is_main_node(contents):
|
||||
lines = _lines + lines
|
||||
self._main_ino = node.get_path()
|
||||
else:
|
||||
lines.extend(_lines)
|
||||
|
||||
if not self._main_ino:
|
||||
self._main_ino = nodes[0].get_path()
|
||||
|
||||
return "\n".join(["#include <Arduino.h>"] + lines) if lines else None
|
||||
|
||||
def process(self, contents):
|
||||
out_file = self._main_ino + ".cpp"
|
||||
assert self._gcc_preprocess(contents, out_file)
|
||||
contents = self.read_safe_contents(out_file)
|
||||
contents = self._join_multiline_strings(contents)
|
||||
self.write_safe_contents(out_file, self.append_prototypes(contents))
|
||||
return out_file
|
||||
|
||||
def _gcc_preprocess(self, contents, out_file):
|
||||
tmp_path = tempfile.mkstemp()[1]
|
||||
self.write_safe_contents(tmp_path, contents)
|
||||
self.env.Execute(
|
||||
self.env.VerboseAction(
|
||||
'$CXX -o "{0}" -x c++ -fpreprocessed -dD -E "{1}"'.format(
|
||||
out_file, tmp_path
|
||||
),
|
||||
"Converting " + os.path.basename(out_file[:-4]),
|
||||
)
|
||||
)
|
||||
atexit.register(_delete_file, tmp_path)
|
||||
return os.path.isfile(out_file)
|
||||
|
||||
def _join_multiline_strings(self, contents):
|
||||
if "\\\n" not in contents:
|
||||
return contents
|
||||
newlines = []
|
||||
linenum = 0
|
||||
stropen = False
|
||||
for line in contents.split("\n"):
|
||||
_linenum = self._parse_preproc_line_num(line)
|
||||
if _linenum is not None:
|
||||
linenum = _linenum
|
||||
else:
|
||||
linenum += 1
|
||||
|
||||
if line.endswith("\\"):
|
||||
if line.startswith('"'):
|
||||
stropen = True
|
||||
newlines.append(line[:-1])
|
||||
continue
|
||||
if stropen:
|
||||
newlines[len(newlines) - 1] += line[:-1]
|
||||
continue
|
||||
elif stropen and line.endswith(('",', '";')):
|
||||
newlines[len(newlines) - 1] += line
|
||||
stropen = False
|
||||
newlines.append(
|
||||
'#line %d "%s"' % (linenum, self._main_ino.replace("\\", "/"))
|
||||
)
|
||||
continue
|
||||
|
||||
newlines.append(line)
|
||||
|
||||
return "\n".join(newlines)
|
||||
|
||||
@staticmethod
|
||||
def _parse_preproc_line_num(line):
|
||||
if not line.startswith("#"):
|
||||
return None
|
||||
tokens = line.split(" ", 3)
|
||||
if len(tokens) > 2 and tokens[1].isdigit():
|
||||
return int(tokens[1])
|
||||
return None
|
||||
|
||||
def _parse_prototypes(self, contents):
|
||||
prototypes = []
|
||||
reserved_keywords = set(["if", "else", "while"])
|
||||
for match in self.PROTOTYPE_RE.finditer(contents):
|
||||
if (
|
||||
set([match.group(2).strip(), match.group(3).strip()])
|
||||
& reserved_keywords
|
||||
):
|
||||
continue
|
||||
prototypes.append(match)
|
||||
return prototypes
|
||||
|
||||
def _get_total_lines(self, contents):
|
||||
total = 0
|
||||
if contents.endswith("\n"):
|
||||
contents = contents[:-1]
|
||||
for line in contents.split("\n")[::-1]:
|
||||
linenum = self._parse_preproc_line_num(line)
|
||||
if linenum is not None:
|
||||
return total + linenum
|
||||
total += 1
|
||||
return total
|
||||
|
||||
def append_prototypes(self, contents):
|
||||
prototypes = self._parse_prototypes(contents) or []
|
||||
|
||||
# skip already declared prototypes
|
||||
declared = set(m.group(1).strip() for m in prototypes if m.group(4) == ";")
|
||||
prototypes = [m for m in prototypes if m.group(1).strip() not in declared]
|
||||
|
||||
if not prototypes:
|
||||
return contents
|
||||
|
||||
prototype_names = set(m.group(3).strip() for m in prototypes)
|
||||
split_pos = prototypes[0].start()
|
||||
match_ptrs = re.search(
|
||||
self.PROTOPTRS_TPLRE % ("|".join(prototype_names)),
|
||||
contents[:split_pos],
|
||||
re.M,
|
||||
)
|
||||
if match_ptrs:
|
||||
split_pos = contents.rfind("\n", 0, match_ptrs.start()) + 1
|
||||
|
||||
result = []
|
||||
result.append(contents[:split_pos].strip())
|
||||
result.append("%s;" % ";\n".join([m.group(1) for m in prototypes]))
|
||||
result.append(
|
||||
'#line %d "%s"'
|
||||
% (
|
||||
self._get_total_lines(contents[:split_pos]),
|
||||
self._main_ino.replace("\\", "/"),
|
||||
)
|
||||
)
|
||||
result.append(contents[split_pos:].strip())
|
||||
return "\n".join(result)
|
||||
|
||||
|
||||
def FindInoNodes(env):
|
||||
src_dir = glob.escape(env.subst("$PROJECT_SRC_DIR"))
|
||||
return env.Glob(os.path.join(src_dir, "*.ino")) + env.Glob(
|
||||
os.path.join(src_dir, "*.pde")
|
||||
)
|
||||
|
||||
|
||||
def ConvertInoToCpp(env):
|
||||
ino_nodes = env.FindInoNodes()
|
||||
if not ino_nodes:
|
||||
return
|
||||
c = InoToCPPConverter(env)
|
||||
out_file = c.convert(ino_nodes)
|
||||
|
||||
atexit.register(_delete_file, out_file)
|
||||
|
||||
|
||||
def _delete_file(path):
|
||||
try:
|
||||
if os.path.isfile(path):
|
||||
os.remove(path)
|
||||
except: # pylint: disable=bare-except
|
||||
pass
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.AddMethod(FindInoNodes)
|
||||
env.AddMethod(ConvertInoToCpp)
|
||||
|
||||
|
||||
def exists(_):
|
||||
return True
|
||||
@@ -14,42 +14,45 @@
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import glob
|
||||
import os
|
||||
from glob import glob
|
||||
|
||||
import SCons.Defaults # pylint: disable=import-error
|
||||
import SCons.Subst # pylint: disable=import-error
|
||||
from SCons.Script import COMMAND_LINE_TARGETS # pylint: disable=import-error
|
||||
|
||||
from platformio.compat import glob_escape
|
||||
from platformio.package.manager.core import get_core_package_dir
|
||||
from platformio.proc import exec_command, where_is_program
|
||||
|
||||
|
||||
def _dump_includes(env):
|
||||
includes = {}
|
||||
def IsIntegrationDump(_):
|
||||
return set(["_idedata", "idedata"]) & set(COMMAND_LINE_TARGETS)
|
||||
|
||||
includes["build"] = [
|
||||
env.subst("$PROJECT_INCLUDE_DIR"),
|
||||
env.subst("$PROJECT_SRC_DIR"),
|
||||
]
|
||||
includes["build"].extend(
|
||||
[os.path.realpath(env.subst(item)) for item in env.get("CPPPATH", [])]
|
||||
|
||||
def DumpIntegrationIncludes(env):
|
||||
result = dict(build=[], compatlib=[], toolchain=[])
|
||||
|
||||
result["build"].extend(
|
||||
[
|
||||
env.subst("$PROJECT_INCLUDE_DIR"),
|
||||
env.subst("$PROJECT_SRC_DIR"),
|
||||
]
|
||||
)
|
||||
result["build"].extend(
|
||||
[os.path.abspath(env.subst(item)) for item in env.get("CPPPATH", [])]
|
||||
)
|
||||
|
||||
# installed libs
|
||||
includes["compatlib"] = []
|
||||
for lb in env.GetLibBuilders():
|
||||
includes["compatlib"].extend(
|
||||
[os.path.realpath(inc) for inc in lb.get_include_dirs()]
|
||||
result["compatlib"].extend(
|
||||
[os.path.abspath(inc) for inc in lb.get_include_dirs()]
|
||||
)
|
||||
|
||||
# includes from toolchains
|
||||
p = env.PioPlatform()
|
||||
includes["toolchain"] = []
|
||||
for pkg in p.get_installed_packages():
|
||||
for pkg in p.get_installed_packages(with_optional=False):
|
||||
if p.get_package_type(pkg.metadata.name) != "toolchain":
|
||||
continue
|
||||
toolchain_dir = glob_escape(pkg.path)
|
||||
toolchain_dir = glob.escape(pkg.path)
|
||||
toolchain_incglobs = [
|
||||
os.path.join(toolchain_dir, "*", "include", "c++", "*"),
|
||||
os.path.join(toolchain_dir, "*", "include", "c++", "*", "*-*-*"),
|
||||
@@ -57,25 +60,12 @@ def _dump_includes(env):
|
||||
os.path.join(toolchain_dir, "*", "include*"),
|
||||
]
|
||||
for g in toolchain_incglobs:
|
||||
includes["toolchain"].extend([os.path.realpath(inc) for inc in glob(g)])
|
||||
result["toolchain"].extend([os.path.abspath(inc) for inc in glob.glob(g)])
|
||||
|
||||
# include Unity framework if there are tests in project
|
||||
includes["unity"] = []
|
||||
auto_install_unity = False
|
||||
test_dir = env.GetProjectConfig().get_optional_dir("test")
|
||||
if os.path.isdir(test_dir) and os.listdir(test_dir) != ["README"]:
|
||||
auto_install_unity = True
|
||||
unity_dir = get_core_package_dir(
|
||||
"tool-unity",
|
||||
auto_install=auto_install_unity,
|
||||
)
|
||||
if unity_dir:
|
||||
includes["unity"].append(unity_dir)
|
||||
|
||||
return includes
|
||||
return result
|
||||
|
||||
|
||||
def _get_gcc_defines(env):
|
||||
def get_gcc_defines(env):
|
||||
items = []
|
||||
try:
|
||||
sysenv = os.environ.copy()
|
||||
@@ -98,7 +88,7 @@ def _get_gcc_defines(env):
|
||||
return items
|
||||
|
||||
|
||||
def _dump_defines(env):
|
||||
def dump_defines(env):
|
||||
defines = []
|
||||
# global symbols
|
||||
for item in SCons.Defaults.processDefines(env.get("CPPDEFINES", [])):
|
||||
@@ -123,15 +113,15 @@ def _dump_defines(env):
|
||||
|
||||
# built-in GCC marcos
|
||||
# if env.GetCompilerType() == "gcc":
|
||||
# defines.extend(_get_gcc_defines(env))
|
||||
# defines.extend(get_gcc_defines(env))
|
||||
|
||||
return defines
|
||||
|
||||
|
||||
def _get_svd_path(env):
|
||||
def dump_svd_path(env):
|
||||
svd_path = env.GetProjectOption("debug_svd_path")
|
||||
if svd_path:
|
||||
return os.path.realpath(svd_path)
|
||||
return os.path.abspath(svd_path)
|
||||
|
||||
if "BOARD" not in env:
|
||||
return None
|
||||
@@ -146,7 +136,7 @@ def _get_svd_path(env):
|
||||
# default file from ./platform/misc/svd folder
|
||||
p = env.PioPlatform()
|
||||
if os.path.isfile(os.path.join(p.get_dir(), "misc", "svd", svd_path)):
|
||||
return os.path.realpath(os.path.join(p.get_dir(), "misc", "svd", svd_path))
|
||||
return os.path.abspath(os.path.join(p.get_dir(), "misc", "svd", svd_path))
|
||||
return None
|
||||
|
||||
|
||||
@@ -155,19 +145,19 @@ def _subst_cmd(env, cmd):
|
||||
return " ".join([SCons.Subst.quote_spaces(arg) for arg in args])
|
||||
|
||||
|
||||
def DumpIDEData(env, globalenv):
|
||||
""" env here is `projenv`"""
|
||||
def DumpIntegrationData(env, globalenv):
|
||||
"""env here is `projenv`"""
|
||||
|
||||
data = {
|
||||
"env_name": env["PIOENV"],
|
||||
"libsource_dirs": [env.subst(item) for item in env.GetLibSourceDirs()],
|
||||
"defines": _dump_defines(env),
|
||||
"includes": _dump_includes(env),
|
||||
"defines": dump_defines(env),
|
||||
"includes": env.DumpIntegrationIncludes(),
|
||||
"cc_path": where_is_program(env.subst("$CC"), env.subst("${ENV['PATH']}")),
|
||||
"cxx_path": where_is_program(env.subst("$CXX"), env.subst("${ENV['PATH']}")),
|
||||
"gdb_path": where_is_program(env.subst("$GDB"), env.subst("${ENV['PATH']}")),
|
||||
"prog_path": env.subst("$PROG_PATH"),
|
||||
"svd_path": _get_svd_path(env),
|
||||
"svd_path": dump_svd_path(env),
|
||||
"compiler_type": env.GetCompilerType(),
|
||||
"targets": globalenv.DumpTargets(),
|
||||
"extra": dict(
|
||||
@@ -177,7 +167,9 @@ def DumpIDEData(env, globalenv):
|
||||
]
|
||||
),
|
||||
}
|
||||
data["extra"].update(env.get("IDE_EXTRA_DATA", {}))
|
||||
data["extra"].update(
|
||||
env.get("INTEGRATION_EXTRA_DATA", env.get("IDE_EXTRA_DATA", {}))
|
||||
)
|
||||
|
||||
env_ = env.Clone()
|
||||
# https://github.com/platformio/platformio-atom-ide/issues/34
|
||||
@@ -206,5 +198,7 @@ def exists(_):
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.AddMethod(DumpIDEData)
|
||||
env.AddMethod(IsIntegrationDump)
|
||||
env.AddMethod(DumpIntegrationIncludes)
|
||||
env.AddMethod(DumpIntegrationData)
|
||||
return env
|
||||
@@ -12,9 +12,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# pylint: disable=no-self-use, unused-argument, too-many-lines
|
||||
# pylint: disable=too-many-instance-attributes, too-many-public-methods
|
||||
# pylint: disable=assignment-from-no-return
|
||||
# pylint: disable=assignment-from-no-return, unused-argument, too-many-lines
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
@@ -27,24 +26,26 @@ import sys
|
||||
import click
|
||||
import SCons.Scanner # pylint: disable=import-error
|
||||
from SCons.Script import ARGUMENTS # pylint: disable=import-error
|
||||
from SCons.Script import COMMAND_LINE_TARGETS # pylint: disable=import-error
|
||||
from SCons.Script import DefaultEnvironment # pylint: disable=import-error
|
||||
|
||||
from platformio import exception, fs, util
|
||||
from platformio import exception, fs
|
||||
from platformio.builder.tools import platformio as piotool
|
||||
from platformio.clients.http import InternetIsOffline
|
||||
from platformio.compat import WINDOWS, hashlib_encode_data, string_types
|
||||
from platformio.package.exception import UnknownPackageError
|
||||
from platformio.compat import IS_WINDOWS, MISSING, hashlib_encode_data, string_types
|
||||
from platformio.http import HTTPClientError, InternetIsOffline
|
||||
from platformio.package.exception import (
|
||||
MissingPackageManifestError,
|
||||
UnknownPackageError,
|
||||
)
|
||||
from platformio.package.manager.library import LibraryPackageManager
|
||||
from platformio.package.manifest.parser import (
|
||||
ManifestParserError,
|
||||
ManifestParserFactory,
|
||||
)
|
||||
from platformio.package.meta import PackageItem
|
||||
from platformio.package.meta import PackageCompatibility, PackageItem
|
||||
from platformio.project.options import ProjectOptions
|
||||
|
||||
|
||||
class LibBuilderFactory(object):
|
||||
class LibBuilderFactory:
|
||||
@staticmethod
|
||||
def new(env, path, verbose=int(ARGUMENTS.get("PIOVERBOSE", 0))):
|
||||
clsname = "UnknownLibBuilder"
|
||||
@@ -54,11 +55,21 @@ class LibBuilderFactory(object):
|
||||
used_frameworks = LibBuilderFactory.get_used_frameworks(env, path)
|
||||
common_frameworks = set(env.get("PIOFRAMEWORK", [])) & set(used_frameworks)
|
||||
if common_frameworks:
|
||||
clsname = "%sLibBuilder" % list(common_frameworks)[0].title()
|
||||
clsname = "%sLibBuilder" % list(common_frameworks)[0].capitalize()
|
||||
elif used_frameworks:
|
||||
clsname = "%sLibBuilder" % used_frameworks[0].title()
|
||||
clsname = "%sLibBuilder" % used_frameworks[0].capitalize()
|
||||
|
||||
obj = globals()[clsname](env, path, verbose=verbose)
|
||||
|
||||
# Handle PlatformIOLibBuilder.manifest.build.builder
|
||||
# pylint: disable=protected-access
|
||||
if isinstance(obj, PlatformIOLibBuilder) and obj._manifest.get("build", {}).get(
|
||||
"builder"
|
||||
):
|
||||
obj = globals()[obj._manifest.get("build", {}).get("builder")](
|
||||
env, path, verbose=verbose
|
||||
)
|
||||
|
||||
obj = getattr(sys.modules[__name__], clsname)(env, path, verbose=verbose)
|
||||
assert isinstance(obj, LibBuilderBase)
|
||||
return obj
|
||||
|
||||
@@ -86,7 +97,9 @@ class LibBuilderFactory(object):
|
||||
fname, piotool.SRC_BUILD_EXT + piotool.SRC_HEADER_EXT
|
||||
):
|
||||
continue
|
||||
with io.open(os.path.join(root, fname), errors="ignore") as fp:
|
||||
with io.open(
|
||||
os.path.join(root, fname), encoding="utf8", errors="ignore"
|
||||
) as fp:
|
||||
content = fp.read()
|
||||
if not content:
|
||||
continue
|
||||
@@ -97,7 +110,7 @@ class LibBuilderFactory(object):
|
||||
return []
|
||||
|
||||
|
||||
class LibBuilderBase(object):
|
||||
class LibBuilderBase:
|
||||
|
||||
CLASSIC_SCANNER = SCons.Scanner.C.CScanner()
|
||||
CCONDITIONAL_SCANNER = SCons.Scanner.C.CConditionalScanner()
|
||||
@@ -113,7 +126,7 @@ class LibBuilderBase(object):
|
||||
def __init__(self, env, path, manifest=None, verbose=False):
|
||||
self.env = env.Clone()
|
||||
self.envorigin = env.Clone()
|
||||
self.path = os.path.realpath(env.subst(path))
|
||||
self.path = os.path.abspath(env.subst(path))
|
||||
self.verbose = verbose
|
||||
|
||||
try:
|
||||
@@ -124,11 +137,13 @@ class LibBuilderBase(object):
|
||||
)
|
||||
self._manifest = {}
|
||||
|
||||
self._is_dependent = False
|
||||
self._is_built = False
|
||||
self._depbuilders = list()
|
||||
self._circular_deps = list()
|
||||
self._processed_files = list()
|
||||
self.is_dependent = False
|
||||
self.is_built = False
|
||||
self.depbuilders = []
|
||||
|
||||
self._deps_are_processed = False
|
||||
self._circular_deps = []
|
||||
self._processed_search_files = []
|
||||
|
||||
# reset source filter, could be overridden with extra script
|
||||
self.env["SRC_FILTER"] = ""
|
||||
@@ -139,15 +154,27 @@ class LibBuilderBase(object):
|
||||
def __repr__(self):
|
||||
return "%s(%r)" % (self.__class__, self.path)
|
||||
|
||||
def __contains__(self, path):
|
||||
p1 = self.path
|
||||
p2 = path
|
||||
if WINDOWS:
|
||||
p1 = p1.lower()
|
||||
p2 = p2.lower()
|
||||
if p1 == p2:
|
||||
def __contains__(self, child_path):
|
||||
return self.is_common_builder(self.path, child_path)
|
||||
|
||||
def is_common_builder(self, root_path, child_path):
|
||||
if IS_WINDOWS:
|
||||
root_path = root_path.lower()
|
||||
child_path = child_path.lower()
|
||||
if root_path == child_path:
|
||||
return True
|
||||
return os.path.commonprefix((p1 + os.path.sep, p2)) == p1 + os.path.sep
|
||||
if (
|
||||
os.path.commonprefix([root_path + os.path.sep, child_path])
|
||||
== root_path + os.path.sep
|
||||
):
|
||||
return True
|
||||
# try to resolve paths
|
||||
root_path = os.path.realpath(root_path)
|
||||
child_path = os.path.realpath(child_path)
|
||||
return (
|
||||
os.path.commonprefix([root_path + os.path.sep, child_path])
|
||||
== root_path + os.path.sep
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -157,6 +184,11 @@ class LibBuilderBase(object):
|
||||
def version(self):
|
||||
return self._manifest.get("version")
|
||||
|
||||
@property
|
||||
def dependent(self):
|
||||
"""Backward compatibility with ESP-IDF"""
|
||||
return self.is_dependent
|
||||
|
||||
@property
|
||||
def dependencies(self):
|
||||
return self._manifest.get("dependencies")
|
||||
@@ -172,19 +204,19 @@ class LibBuilderBase(object):
|
||||
|
||||
@property
|
||||
def include_dir(self):
|
||||
if not all(
|
||||
os.path.isdir(os.path.join(self.path, d)) for d in ("include", "src")
|
||||
):
|
||||
return None
|
||||
return os.path.join(self.path, "include")
|
||||
for name in ("include", "Include"):
|
||||
d = os.path.join(self.path, name)
|
||||
if os.path.isdir(d):
|
||||
return d
|
||||
return None
|
||||
|
||||
@property
|
||||
def src_dir(self):
|
||||
return (
|
||||
os.path.join(self.path, "src")
|
||||
if os.path.isdir(os.path.join(self.path, "src"))
|
||||
else self.path
|
||||
)
|
||||
for name in ("src", "Src"):
|
||||
d = os.path.join(self.path, name)
|
||||
if os.path.isdir(d):
|
||||
return d
|
||||
return self.path
|
||||
|
||||
def get_include_dirs(self):
|
||||
items = []
|
||||
@@ -213,18 +245,6 @@ class LibBuilderBase(object):
|
||||
def extra_script(self):
|
||||
return None
|
||||
|
||||
@property
|
||||
def depbuilders(self):
|
||||
return self._depbuilders
|
||||
|
||||
@property
|
||||
def dependent(self):
|
||||
return self._is_dependent
|
||||
|
||||
@property
|
||||
def is_built(self):
|
||||
return self._is_built
|
||||
|
||||
@property
|
||||
def lib_archive(self):
|
||||
return self.env.GetProjectOption("lib_archive")
|
||||
@@ -278,14 +298,15 @@ class LibBuilderBase(object):
|
||||
if self.extra_script:
|
||||
self.env.SConscriptChdir(1)
|
||||
self.env.SConscript(
|
||||
os.path.realpath(self.extra_script),
|
||||
os.path.abspath(self.extra_script),
|
||||
exports={"env": self.env, "pio_lib_builder": self},
|
||||
)
|
||||
self.env.ProcessUnFlags(self.build_unflags)
|
||||
|
||||
def process_dependencies(self):
|
||||
if not self.dependencies:
|
||||
if not self.dependencies or self._deps_are_processed:
|
||||
return
|
||||
self._deps_are_processed = True
|
||||
for item in self.dependencies:
|
||||
found = False
|
||||
for lb in self.env.GetLibBuilders():
|
||||
@@ -293,7 +314,7 @@ class LibBuilderBase(object):
|
||||
continue
|
||||
found = True
|
||||
if lb not in self.depbuilders:
|
||||
self.depend_recursive(lb)
|
||||
self.depend_on(lb)
|
||||
break
|
||||
|
||||
if not found and self.verbose:
|
||||
@@ -303,21 +324,14 @@ class LibBuilderBase(object):
|
||||
)
|
||||
|
||||
def get_search_files(self):
|
||||
items = [
|
||||
return [
|
||||
os.path.join(self.src_dir, item)
|
||||
for item in self.env.MatchSourceFiles(self.src_dir, self.src_filter)
|
||||
]
|
||||
include_dir = self.include_dir
|
||||
if include_dir:
|
||||
items.extend(
|
||||
[
|
||||
os.path.join(include_dir, item)
|
||||
for item in self.env.MatchSourceFiles(include_dir)
|
||||
]
|
||||
for item in self.env.MatchSourceFiles(
|
||||
self.src_dir, self.src_filter, piotool.SRC_BUILD_EXT
|
||||
)
|
||||
return items
|
||||
]
|
||||
|
||||
def _get_found_includes( # pylint: disable=too-many-branches
|
||||
def get_implicit_includes( # pylint: disable=too-many-branches
|
||||
self, search_files=None
|
||||
):
|
||||
# all include directories
|
||||
@@ -325,7 +339,7 @@ class LibBuilderBase(object):
|
||||
LibBuilderBase._INCLUDE_DIRS_CACHE = [
|
||||
self.env.Dir(d)
|
||||
for d in ProjectAsLibBuilder(
|
||||
self.envorigin, "$PROJECT_DIR"
|
||||
self.envorigin, "$PROJECT_DIR", export_projenv=False
|
||||
).get_include_dirs()
|
||||
]
|
||||
for lb in self.env.GetLibBuilders():
|
||||
@@ -338,57 +352,82 @@ class LibBuilderBase(object):
|
||||
include_dirs.extend(LibBuilderBase._INCLUDE_DIRS_CACHE)
|
||||
|
||||
result = []
|
||||
for path in search_files or []:
|
||||
if path in self._processed_files:
|
||||
search_files = search_files or []
|
||||
while search_files:
|
||||
node = self.env.File(search_files.pop(0))
|
||||
if node.get_abspath() in self._processed_search_files:
|
||||
continue
|
||||
self._processed_files.append(path)
|
||||
self._processed_search_files.append(node.get_abspath())
|
||||
|
||||
try:
|
||||
assert "+" in self.lib_ldf_mode
|
||||
candidates = LibBuilderBase.CCONDITIONAL_SCANNER(
|
||||
self.env.File(path),
|
||||
node,
|
||||
self.env,
|
||||
tuple(include_dirs),
|
||||
depth=self.CCONDITIONAL_SCANNER_DEPTH,
|
||||
)
|
||||
# mark candidates already processed via Conditional Scanner
|
||||
self._processed_files.extend(
|
||||
[
|
||||
c.get_abspath()
|
||||
for c in candidates
|
||||
if c.get_abspath() not in self._processed_files
|
||||
]
|
||||
)
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
if self.verbose and "+" in self.lib_ldf_mode:
|
||||
sys.stderr.write(
|
||||
"Warning! Classic Pre Processor is used for `%s`, "
|
||||
"advanced has failed with `%s`\n" % (path, e)
|
||||
"advanced has failed with `%s`\n" % (node.get_abspath(), exc)
|
||||
)
|
||||
candidates = LibBuilderBase.CLASSIC_SCANNER(
|
||||
self.env.File(path), self.env, tuple(include_dirs)
|
||||
node, self.env, tuple(include_dirs)
|
||||
)
|
||||
|
||||
# print(path, [c.get_abspath() for c in candidates])
|
||||
# print(node.get_abspath(), [c.get_abspath() for c in candidates])
|
||||
for item in candidates:
|
||||
item_path = item.get_abspath()
|
||||
# process internal files recursively
|
||||
if (
|
||||
item_path not in self._processed_search_files
|
||||
and item_path not in search_files
|
||||
and item_path in self
|
||||
):
|
||||
search_files.append(item_path)
|
||||
if item not in result:
|
||||
result.append(item)
|
||||
if not self.PARSE_SRC_BY_H_NAME:
|
||||
continue
|
||||
_h_path = item.get_abspath()
|
||||
if not fs.path_endswith_ext(_h_path, piotool.SRC_HEADER_EXT):
|
||||
if not fs.path_endswith_ext(item_path, piotool.SRC_HEADER_EXT):
|
||||
continue
|
||||
_f_part = _h_path[: _h_path.rindex(".")]
|
||||
item_fname = item_path[: item_path.rindex(".")]
|
||||
for ext in piotool.SRC_C_EXT + piotool.SRC_CXX_EXT:
|
||||
if not os.path.isfile("%s.%s" % (_f_part, ext)):
|
||||
if not os.path.isfile("%s.%s" % (item_fname, ext)):
|
||||
continue
|
||||
_c_path = self.env.File("%s.%s" % (_f_part, ext))
|
||||
if _c_path not in result:
|
||||
result.append(_c_path)
|
||||
item_c_node = self.env.File("%s.%s" % (item_fname, ext))
|
||||
if item_c_node not in result:
|
||||
result.append(item_c_node)
|
||||
|
||||
return result
|
||||
|
||||
def depend_recursive(self, lb, search_files=None):
|
||||
def search_deps_recursive(self, search_files=None):
|
||||
self.process_dependencies()
|
||||
|
||||
# when LDF is disabled
|
||||
if self.lib_ldf_mode == "off":
|
||||
return
|
||||
|
||||
if self.lib_ldf_mode.startswith("deep"):
|
||||
search_files = self.get_search_files()
|
||||
|
||||
lib_inc_map = {}
|
||||
for inc in self.get_implicit_includes(search_files):
|
||||
inc_path = inc.get_abspath()
|
||||
for lb in self.env.GetLibBuilders():
|
||||
if inc_path in lb:
|
||||
if lb not in lib_inc_map:
|
||||
lib_inc_map[lb] = []
|
||||
lib_inc_map[lb].append(inc_path)
|
||||
break
|
||||
|
||||
for lb, lb_search_files in lib_inc_map.items():
|
||||
self.depend_on(lb, search_files=lb_search_files)
|
||||
|
||||
def depend_on(self, lb, search_files=None, recursive=True):
|
||||
def _already_depends(_lb):
|
||||
if self in _lb.depbuilders:
|
||||
return True
|
||||
@@ -406,49 +445,34 @@ class LibBuilderBase(object):
|
||||
"between `%s` and `%s`\n" % (self.path, lb.path)
|
||||
)
|
||||
self._circular_deps.append(lb)
|
||||
elif lb not in self._depbuilders:
|
||||
self._depbuilders.append(lb)
|
||||
elif lb not in self.depbuilders:
|
||||
self.depbuilders.append(lb)
|
||||
lb.is_dependent = True
|
||||
LibBuilderBase._INCLUDE_DIRS_CACHE = None
|
||||
lb.search_deps_recursive(search_files)
|
||||
|
||||
def search_deps_recursive(self, search_files=None):
|
||||
if not self._is_dependent:
|
||||
self._is_dependent = True
|
||||
self.process_dependencies()
|
||||
|
||||
if self.lib_ldf_mode.startswith("deep"):
|
||||
search_files = self.get_search_files()
|
||||
|
||||
# when LDF is disabled
|
||||
if self.lib_ldf_mode == "off":
|
||||
return
|
||||
|
||||
lib_inc_map = {}
|
||||
for inc in self._get_found_includes(search_files):
|
||||
for lb in self.env.GetLibBuilders():
|
||||
if inc.get_abspath() in lb:
|
||||
if lb not in lib_inc_map:
|
||||
lib_inc_map[lb] = []
|
||||
lib_inc_map[lb].append(inc.get_abspath())
|
||||
break
|
||||
|
||||
for lb, lb_search_files in lib_inc_map.items():
|
||||
self.depend_recursive(lb, lb_search_files)
|
||||
if recursive:
|
||||
lb.search_deps_recursive(search_files)
|
||||
|
||||
def build(self):
|
||||
libs = []
|
||||
for lb in self._depbuilders:
|
||||
shared_scopes = ("CPPPATH", "LIBPATH", "LIBS", "LINKFLAGS")
|
||||
for lb in self.depbuilders:
|
||||
libs.extend(lb.build())
|
||||
# copy shared information to self env
|
||||
for key in ("CPPPATH", "LIBPATH", "LIBS", "LINKFLAGS"):
|
||||
self.env.PrependUnique(**{key: lb.env.get(key)})
|
||||
self.env.PrependUnique(
|
||||
**{
|
||||
scope: lb.env.get(scope)
|
||||
for scope in shared_scopes
|
||||
if lb.env.get(scope)
|
||||
}
|
||||
)
|
||||
|
||||
for lb in self._circular_deps:
|
||||
self.env.PrependUnique(CPPPATH=lb.get_include_dirs())
|
||||
|
||||
if self._is_built:
|
||||
if self.is_built:
|
||||
return libs
|
||||
self._is_built = True
|
||||
self.is_built = True
|
||||
|
||||
self.env.PrependUnique(CPPPATH=self.get_include_dirs())
|
||||
|
||||
@@ -456,15 +480,30 @@ class LibBuilderBase(object):
|
||||
for lb in self.env.GetLibBuilders():
|
||||
if self == lb or not lb.is_built:
|
||||
continue
|
||||
for key in ("CPPPATH", "LIBPATH", "LIBS", "LINKFLAGS"):
|
||||
self.env.PrependUnique(**{key: lb.env.get(key)})
|
||||
self.env.PrependUnique(
|
||||
**{
|
||||
scope: lb.env.get(scope)
|
||||
for scope in shared_scopes
|
||||
if lb.env.get(scope)
|
||||
}
|
||||
)
|
||||
|
||||
if self.lib_archive:
|
||||
libs.append(
|
||||
self.env.BuildLibrary(self.build_dir, self.src_dir, self.src_filter)
|
||||
do_not_archive = not self.lib_archive
|
||||
if not do_not_archive:
|
||||
nodes = self.env.CollectBuildFiles(
|
||||
self.build_dir, self.src_dir, self.src_filter
|
||||
)
|
||||
else:
|
||||
if nodes:
|
||||
libs.append(
|
||||
self.env.BuildLibrary(
|
||||
self.build_dir, self.src_dir, self.src_filter, nodes
|
||||
)
|
||||
)
|
||||
else:
|
||||
do_not_archive = True
|
||||
if do_not_archive:
|
||||
self.env.BuildSources(self.build_dir, self.src_dir, self.src_filter)
|
||||
|
||||
return libs
|
||||
|
||||
|
||||
@@ -479,8 +518,16 @@ class ArduinoLibBuilder(LibBuilderBase):
|
||||
return {}
|
||||
return ManifestParserFactory.new_from_file(manifest_path).as_dict()
|
||||
|
||||
@property
|
||||
def include_dir(self):
|
||||
if not all(
|
||||
os.path.isdir(os.path.join(self.path, d)) for d in ("include", "src")
|
||||
):
|
||||
return None
|
||||
return os.path.join(self.path, "include")
|
||||
|
||||
def get_include_dirs(self):
|
||||
include_dirs = LibBuilderBase.get_include_dirs(self)
|
||||
include_dirs = super().get_include_dirs()
|
||||
if os.path.isdir(os.path.join(self.path, "src")):
|
||||
return include_dirs
|
||||
if os.path.isdir(os.path.join(self.path, "utility")):
|
||||
@@ -529,21 +576,42 @@ class ArduinoLibBuilder(LibBuilderBase):
|
||||
# pylint: disable=no-member
|
||||
if not self._manifest.get("dependencies"):
|
||||
return LibBuilderBase.lib_ldf_mode.fget(self)
|
||||
missing = object()
|
||||
global_value = self.env.GetProjectConfig().getraw(
|
||||
"env:" + self.env["PIOENV"], "lib_ldf_mode", missing
|
||||
"env:" + self.env["PIOENV"], "lib_ldf_mode", MISSING
|
||||
)
|
||||
if global_value != missing:
|
||||
if global_value != MISSING:
|
||||
return LibBuilderBase.lib_ldf_mode.fget(self)
|
||||
# automatically enable C++ Preprocessing in runtime
|
||||
# (Arduino IDE has this behavior)
|
||||
return "chain+"
|
||||
|
||||
def is_frameworks_compatible(self, frameworks):
|
||||
return util.items_in_list(frameworks, ["arduino", "energia"])
|
||||
return PackageCompatibility(frameworks=frameworks).is_compatible(
|
||||
PackageCompatibility(frameworks=["arduino", "energia"])
|
||||
)
|
||||
|
||||
def is_platforms_compatible(self, platforms):
|
||||
return util.items_in_list(platforms, self._manifest.get("platforms") or ["*"])
|
||||
return PackageCompatibility(platforms=platforms).is_compatible(
|
||||
PackageCompatibility(platforms=self._manifest.get("platforms"))
|
||||
)
|
||||
|
||||
@property
|
||||
def build_flags(self):
|
||||
ldflags = [
|
||||
LibBuilderBase.build_flags.fget(self), # pylint: disable=no-member
|
||||
self._manifest.get("ldflags"),
|
||||
]
|
||||
if self._manifest.get("precompiled") in ("true", "full"):
|
||||
# add to LDPATH {build.mcu} folder
|
||||
board_config = self.env.BoardConfig()
|
||||
for key in ("build.mcu", "build.cpu"):
|
||||
libpath = os.path.join(self.src_dir, board_config.get(key, ""))
|
||||
if not os.path.isdir(libpath):
|
||||
continue
|
||||
self.env.PrependUnique(LIBPATH=libpath)
|
||||
break
|
||||
ldflags = [flag for flag in ldflags if flag] # remove empty
|
||||
return " ".join(ldflags) if ldflags else None
|
||||
|
||||
|
||||
class MbedLibBuilder(LibBuilderBase):
|
||||
@@ -553,12 +621,6 @@ class MbedLibBuilder(LibBuilderBase):
|
||||
return {}
|
||||
return ManifestParserFactory.new_from_file(manifest_path).as_dict()
|
||||
|
||||
@property
|
||||
def include_dir(self):
|
||||
if os.path.isdir(os.path.join(self.path, "include")):
|
||||
return os.path.join(self.path, "include")
|
||||
return None
|
||||
|
||||
@property
|
||||
def src_dir(self):
|
||||
if os.path.isdir(os.path.join(self.path, "source")):
|
||||
@@ -566,7 +628,7 @@ class MbedLibBuilder(LibBuilderBase):
|
||||
return LibBuilderBase.src_dir.fget(self) # pylint: disable=no-member
|
||||
|
||||
def get_include_dirs(self):
|
||||
include_dirs = LibBuilderBase.get_include_dirs(self)
|
||||
include_dirs = super().get_include_dirs()
|
||||
if self.path not in include_dirs:
|
||||
include_dirs.append(self.path)
|
||||
|
||||
@@ -586,11 +648,13 @@ class MbedLibBuilder(LibBuilderBase):
|
||||
return include_dirs
|
||||
|
||||
def is_frameworks_compatible(self, frameworks):
|
||||
return util.items_in_list(frameworks, ["mbed"])
|
||||
return PackageCompatibility(frameworks=frameworks).is_compatible(
|
||||
PackageCompatibility(frameworks=["mbed"])
|
||||
)
|
||||
|
||||
def process_extra_options(self):
|
||||
self._process_mbed_lib_confs()
|
||||
return super(MbedLibBuilder, self).process_extra_options()
|
||||
return super().process_extra_options()
|
||||
|
||||
def _process_mbed_lib_confs(self):
|
||||
mbed_lib_paths = [
|
||||
@@ -671,7 +735,7 @@ class MbedLibBuilder(LibBuilderBase):
|
||||
|
||||
def _mbed_conf_append_macros(self, mbed_config_path, macros):
|
||||
lines = []
|
||||
with open(mbed_config_path) as fp:
|
||||
with open(mbed_config_path, encoding="utf8") as fp:
|
||||
for line in fp.readlines():
|
||||
line = line.strip()
|
||||
if line == "#endif":
|
||||
@@ -690,7 +754,7 @@ class MbedLibBuilder(LibBuilderBase):
|
||||
if len(tokens) < 2 or tokens[1] not in macros:
|
||||
lines.append(line)
|
||||
lines.append("")
|
||||
with open(mbed_config_path, "w") as fp:
|
||||
with open(mbed_config_path, mode="w", encoding="utf8") as fp:
|
||||
fp.write("\n".join(lines))
|
||||
|
||||
|
||||
@@ -708,14 +772,32 @@ class PlatformIOLibBuilder(LibBuilderBase):
|
||||
def include_dir(self):
|
||||
if "includeDir" in self._manifest.get("build", {}):
|
||||
with fs.cd(self.path):
|
||||
return os.path.realpath(self._manifest.get("build").get("includeDir"))
|
||||
return os.path.abspath(self._manifest.get("build").get("includeDir"))
|
||||
return LibBuilderBase.include_dir.fget(self) # pylint: disable=no-member
|
||||
|
||||
def get_include_dirs(self):
|
||||
include_dirs = super().get_include_dirs()
|
||||
|
||||
# backwards compatibility with PlatformIO 2.0
|
||||
if (
|
||||
"build" not in self._manifest
|
||||
and self._has_arduino_manifest()
|
||||
and not os.path.isdir(os.path.join(self.path, "src"))
|
||||
and os.path.isdir(os.path.join(self.path, "utility"))
|
||||
):
|
||||
include_dirs.append(os.path.join(self.path, "utility"))
|
||||
|
||||
for path in self.env.get("CPPPATH", []):
|
||||
if path not in self.envorigin.get("CPPPATH", []):
|
||||
include_dirs.append(self.env.subst(path))
|
||||
|
||||
return include_dirs
|
||||
|
||||
@property
|
||||
def src_dir(self):
|
||||
if "srcDir" in self._manifest.get("build", {}):
|
||||
with fs.cd(self.path):
|
||||
return os.path.realpath(self._manifest.get("build").get("srcDir"))
|
||||
return os.path.abspath(self._manifest.get("build").get("srcDir"))
|
||||
return LibBuilderBase.src_dir.fget(self) # pylint: disable=no-member
|
||||
|
||||
@property
|
||||
@@ -749,11 +831,10 @@ class PlatformIOLibBuilder(LibBuilderBase):
|
||||
|
||||
@property
|
||||
def lib_archive(self):
|
||||
missing = object()
|
||||
global_value = self.env.GetProjectConfig().getraw(
|
||||
"env:" + self.env["PIOENV"], "lib_archive", missing
|
||||
"env:" + self.env["PIOENV"], "lib_archive", MISSING
|
||||
)
|
||||
if global_value != missing:
|
||||
if global_value != MISSING:
|
||||
return self.env.GetProjectConfig().get(
|
||||
"env:" + self.env["PIOENV"], "lib_archive"
|
||||
)
|
||||
@@ -781,36 +862,33 @@ class PlatformIOLibBuilder(LibBuilderBase):
|
||||
)
|
||||
|
||||
def is_platforms_compatible(self, platforms):
|
||||
return util.items_in_list(platforms, self._manifest.get("platforms") or ["*"])
|
||||
return PackageCompatibility(platforms=platforms).is_compatible(
|
||||
PackageCompatibility(platforms=self._manifest.get("platforms"))
|
||||
)
|
||||
|
||||
def is_frameworks_compatible(self, frameworks):
|
||||
return util.items_in_list(frameworks, self._manifest.get("frameworks") or ["*"])
|
||||
|
||||
def get_include_dirs(self):
|
||||
include_dirs = LibBuilderBase.get_include_dirs(self)
|
||||
|
||||
# backwards compatibility with PlatformIO 2.0
|
||||
if (
|
||||
"build" not in self._manifest
|
||||
and self._has_arduino_manifest()
|
||||
and not os.path.isdir(os.path.join(self.path, "src"))
|
||||
and os.path.isdir(os.path.join(self.path, "utility"))
|
||||
):
|
||||
include_dirs.append(os.path.join(self.path, "utility"))
|
||||
|
||||
for path in self.env.get("CPPPATH", []):
|
||||
if path not in self.envorigin.get("CPPPATH", []):
|
||||
include_dirs.append(self.env.subst(path))
|
||||
|
||||
return include_dirs
|
||||
return PackageCompatibility(frameworks=frameworks).is_compatible(
|
||||
PackageCompatibility(frameworks=self._manifest.get("frameworks"))
|
||||
)
|
||||
|
||||
|
||||
class ProjectAsLibBuilder(LibBuilderBase):
|
||||
def __init__(self, env, *args, **kwargs):
|
||||
export_projenv = kwargs.get("export_projenv", True)
|
||||
if "export_projenv" in kwargs:
|
||||
del kwargs["export_projenv"]
|
||||
# backup original value, will be reset in base.__init__
|
||||
project_src_filter = env.get("SRC_FILTER")
|
||||
super(ProjectAsLibBuilder, self).__init__(env, *args, **kwargs)
|
||||
super().__init__(env, *args, **kwargs)
|
||||
self.env["SRC_FILTER"] = project_src_filter
|
||||
if export_projenv:
|
||||
env.Export(dict(projenv=self.env))
|
||||
|
||||
def __contains__(self, child_path):
|
||||
for root_path in (self.include_dir, self.src_dir, self.test_dir):
|
||||
if root_path and self.is_common_builder(root_path, child_path):
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def include_dir(self):
|
||||
@@ -821,21 +899,18 @@ class ProjectAsLibBuilder(LibBuilderBase):
|
||||
def src_dir(self):
|
||||
return self.env.subst("$PROJECT_SRC_DIR")
|
||||
|
||||
def get_include_dirs(self):
|
||||
include_dirs = []
|
||||
project_include_dir = self.env.subst("$PROJECT_INCLUDE_DIR")
|
||||
if os.path.isdir(project_include_dir):
|
||||
include_dirs.append(project_include_dir)
|
||||
for include_dir in LibBuilderBase.get_include_dirs(self):
|
||||
if include_dir not in include_dirs:
|
||||
include_dirs.append(include_dir)
|
||||
return include_dirs
|
||||
@property
|
||||
def test_dir(self):
|
||||
return self.env.subst("$PROJECT_TEST_DIR")
|
||||
|
||||
def get_search_files(self):
|
||||
items = []
|
||||
build_type = self.env.GetBuildType()
|
||||
# project files
|
||||
items = LibBuilderBase.get_search_files(self)
|
||||
if "test" not in build_type or self.env.GetProjectOption("test_build_src"):
|
||||
items.extend(super().get_search_files())
|
||||
# test files
|
||||
if "__test" in COMMAND_LINE_TARGETS:
|
||||
if "test" in build_type:
|
||||
items.extend(
|
||||
[
|
||||
os.path.join("$PROJECT_TEST_DIR", item)
|
||||
@@ -859,13 +934,19 @@ class ProjectAsLibBuilder(LibBuilderBase):
|
||||
# pylint: disable=no-member
|
||||
return self.env.get("SRC_FILTER") or LibBuilderBase.src_filter.fget(self)
|
||||
|
||||
@property
|
||||
def build_flags(self):
|
||||
# pylint: disable=no-member
|
||||
return self.env.get("SRC_BUILD_FLAGS") or LibBuilderBase.build_flags.fget(self)
|
||||
|
||||
@property
|
||||
def dependencies(self):
|
||||
return self.env.GetProjectOption("lib_deps", [])
|
||||
|
||||
def process_extra_options(self):
|
||||
# skip for project, options are already processed
|
||||
pass
|
||||
with fs.cd(self.path):
|
||||
self.env.ProcessFlags(self.build_flags)
|
||||
self.env.ProcessUnFlags(self.build_unflags)
|
||||
|
||||
def install_dependencies(self):
|
||||
def _is_builtin(spec):
|
||||
@@ -897,14 +978,15 @@ class ProjectAsLibBuilder(LibBuilderBase):
|
||||
try:
|
||||
lm.install(spec)
|
||||
did_install = True
|
||||
except (UnknownPackageError, InternetIsOffline) as e:
|
||||
click.secho("Warning! %s" % e, fg="yellow")
|
||||
except (HTTPClientError, UnknownPackageError, InternetIsOffline) as exc:
|
||||
click.secho("Warning! %s" % exc, fg="yellow")
|
||||
|
||||
# reset cache
|
||||
if did_install:
|
||||
DefaultEnvironment().Replace(__PIO_LIB_BUILDERS=None)
|
||||
|
||||
def process_dependencies(self): # pylint: disable=too-many-branches
|
||||
found_lbs = []
|
||||
for spec in self.dependencies:
|
||||
found = False
|
||||
for storage_dir in self.env.GetLibSourceDirs():
|
||||
@@ -918,7 +1000,8 @@ class ProjectAsLibBuilder(LibBuilderBase):
|
||||
if pkg.path != lb.path:
|
||||
continue
|
||||
if lb not in self.depbuilders:
|
||||
self.depend_recursive(lb)
|
||||
self.depend_on(lb, recursive=False)
|
||||
found_lbs.append(lb)
|
||||
found = True
|
||||
break
|
||||
if found:
|
||||
@@ -930,13 +1013,17 @@ class ProjectAsLibBuilder(LibBuilderBase):
|
||||
if lb.name != spec:
|
||||
continue
|
||||
if lb not in self.depbuilders:
|
||||
self.depend_recursive(lb)
|
||||
self.depend_on(lb)
|
||||
found = True
|
||||
break
|
||||
|
||||
# process library dependencies
|
||||
for lb in found_lbs:
|
||||
lb.search_deps_recursive()
|
||||
|
||||
def build(self):
|
||||
self._is_built = True # do not build Project now
|
||||
result = LibBuilderBase.build(self)
|
||||
self.is_built = True # do not build Project now
|
||||
result = super().build()
|
||||
self.env.PrependUnique(CPPPATH=self.get_include_dirs())
|
||||
return result
|
||||
|
||||
@@ -961,7 +1048,7 @@ def IsCompatibleLibBuilder(env, lb, verbose=int(ARGUMENTS.get("PIOVERBOSE", 0)))
|
||||
sys.stderr.write("Platform incompatible library %s\n" % lb.path)
|
||||
return False
|
||||
if compat_mode in ("soft", "strict") and not lb.is_frameworks_compatible(
|
||||
env.get("PIOFRAMEWORK", [])
|
||||
env.get("PIOFRAMEWORK", "__noframework__")
|
||||
):
|
||||
if verbose:
|
||||
sys.stderr.write("Framework incompatible library %s\n" % lb.path)
|
||||
@@ -969,25 +1056,30 @@ def IsCompatibleLibBuilder(env, lb, verbose=int(ARGUMENTS.get("PIOVERBOSE", 0)))
|
||||
return True
|
||||
|
||||
|
||||
def GetLibBuilders(env): # pylint: disable=too-many-branches
|
||||
if DefaultEnvironment().get("__PIO_LIB_BUILDERS", None) is not None:
|
||||
def GetLibBuilders(_): # pylint: disable=too-many-branches
|
||||
env = DefaultEnvironment()
|
||||
if env.get("__PIO_LIB_BUILDERS", None) is not None:
|
||||
return sorted(
|
||||
DefaultEnvironment()["__PIO_LIB_BUILDERS"],
|
||||
key=lambda lb: 0 if lb.dependent else 1,
|
||||
env["__PIO_LIB_BUILDERS"],
|
||||
key=lambda lb: 0 if lb.is_dependent else 1,
|
||||
)
|
||||
|
||||
DefaultEnvironment().Replace(__PIO_LIB_BUILDERS=[])
|
||||
env.Replace(__PIO_LIB_BUILDERS=[])
|
||||
|
||||
verbose = int(ARGUMENTS.get("PIOVERBOSE", 0))
|
||||
found_incompat = False
|
||||
|
||||
for storage_dir in env.GetLibSourceDirs():
|
||||
storage_dir = os.path.realpath(storage_dir)
|
||||
storage_dir = os.path.abspath(storage_dir)
|
||||
if not os.path.isdir(storage_dir):
|
||||
continue
|
||||
for item in sorted(os.listdir(storage_dir)):
|
||||
lib_dir = os.path.join(storage_dir, item)
|
||||
if item == "__cores__" or not os.path.isdir(lib_dir):
|
||||
if item == "__cores__":
|
||||
continue
|
||||
if LibraryPackageManager.is_symlink(lib_dir):
|
||||
lib_dir, _ = LibraryPackageManager.resolve_symlink(lib_dir)
|
||||
if not lib_dir or not os.path.isdir(lib_dir):
|
||||
continue
|
||||
try:
|
||||
lb = LibBuilderFactory.new(env, lib_dir)
|
||||
@@ -998,13 +1090,13 @@ def GetLibBuilders(env): # pylint: disable=too-many-branches
|
||||
)
|
||||
continue
|
||||
if env.IsCompatibleLibBuilder(lb):
|
||||
DefaultEnvironment().Append(__PIO_LIB_BUILDERS=[lb])
|
||||
env.Append(__PIO_LIB_BUILDERS=[lb])
|
||||
else:
|
||||
found_incompat = True
|
||||
|
||||
for lb in env.get("EXTRA_LIB_BUILDERS", []):
|
||||
if env.IsCompatibleLibBuilder(lb):
|
||||
DefaultEnvironment().Append(__PIO_LIB_BUILDERS=[lb])
|
||||
env.Append(__PIO_LIB_BUILDERS=[lb])
|
||||
else:
|
||||
found_incompat = True
|
||||
|
||||
@@ -1015,13 +1107,25 @@ def GetLibBuilders(env): # pylint: disable=too-many-branches
|
||||
"ldf-compat-mode\n"
|
||||
)
|
||||
|
||||
return DefaultEnvironment()["__PIO_LIB_BUILDERS"]
|
||||
return env["__PIO_LIB_BUILDERS"]
|
||||
|
||||
|
||||
def ConfigureProjectLibBuilder(env):
|
||||
_pm_storage = {}
|
||||
|
||||
def _get_lib_license(pkg):
|
||||
storage_dir = os.path.dirname(os.path.dirname(pkg.path))
|
||||
if storage_dir not in _pm_storage:
|
||||
_pm_storage[storage_dir] = LibraryPackageManager(storage_dir)
|
||||
try:
|
||||
return (_pm_storage[storage_dir].load_manifest(pkg) or {}).get("license")
|
||||
except MissingPackageManifestError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def _correct_found_libs(lib_builders):
|
||||
# build full dependency graph
|
||||
found_lbs = [lb for lb in lib_builders if lb.dependent]
|
||||
found_lbs = [lb for lb in lib_builders if lb.is_dependent]
|
||||
for lb in lib_builders:
|
||||
if lb in found_lbs:
|
||||
lb.search_deps_recursive(lb.get_search_files())
|
||||
@@ -1033,27 +1137,33 @@ def ConfigureProjectLibBuilder(env):
|
||||
def _print_deps_tree(root, level=0):
|
||||
margin = "| " * (level)
|
||||
for lb in root.depbuilders:
|
||||
title = "<%s>" % lb.name
|
||||
title = lb.name
|
||||
pkg = PackageItem(lb.path)
|
||||
if pkg.metadata:
|
||||
title += " %s" % pkg.metadata.version
|
||||
title += " @ %s" % pkg.metadata.version
|
||||
elif lb.version:
|
||||
title += " %s" % lb.version
|
||||
title += " @ %s" % lb.version
|
||||
click.echo("%s|-- %s" % (margin, title), nl=False)
|
||||
if int(ARGUMENTS.get("PIOVERBOSE", 0)):
|
||||
click.echo(
|
||||
" (License: %s, " % (_get_lib_license(pkg) or "Unknown"), nl=False
|
||||
)
|
||||
if pkg.metadata and pkg.metadata.spec.external:
|
||||
click.echo(" [%s]" % pkg.metadata.spec.url, nl=False)
|
||||
click.echo(" (", nl=False)
|
||||
click.echo(lb.path, nl=False)
|
||||
click.echo("URI: %s, " % pkg.metadata.spec.uri, nl=False)
|
||||
click.echo("Path: %s" % lb.path, nl=False)
|
||||
click.echo(")", nl=False)
|
||||
click.echo("")
|
||||
if lb.depbuilders:
|
||||
_print_deps_tree(lb, level + 1)
|
||||
|
||||
project = ProjectAsLibBuilder(env, "$PROJECT_DIR")
|
||||
|
||||
if "test" in env.GetBuildType():
|
||||
project.env.ConfigureTestTarget()
|
||||
|
||||
ldf_mode = LibBuilderBase.lib_ldf_mode.fget(project) # pylint: disable=no-member
|
||||
|
||||
click.echo("LDF: Library Dependency Finder -> http://bit.ly/configure-pio-ldf")
|
||||
click.echo("LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf")
|
||||
click.echo(
|
||||
"LDF Modes: Finder ~ %s, Compatibility ~ %s"
|
||||
% (ldf_mode, project.lib_compat_mode)
|
||||
|
||||
@@ -19,22 +19,23 @@ import os
|
||||
import re
|
||||
|
||||
from SCons.Platform import TempFileMunge # pylint: disable=import-error
|
||||
from SCons.Script import COMMAND_LINE_TARGETS # pylint: disable=import-error
|
||||
from SCons.Subst import quote_spaces # pylint: disable=import-error
|
||||
|
||||
from platformio.compat import WINDOWS, hashlib_encode_data
|
||||
from platformio.compat import IS_WINDOWS, hashlib_encode_data
|
||||
|
||||
# There are the next limits depending on a platform:
|
||||
# - Windows = 8192
|
||||
# - Unix = 131072
|
||||
# We need ~256 characters for a temporary file path
|
||||
MAX_LINE_LENGTH = (8192 if WINDOWS else 131072) - 256
|
||||
# We need ~512 characters for compiler and temporary file paths
|
||||
MAX_LINE_LENGTH = (8192 if IS_WINDOWS else 131072) - 512
|
||||
|
||||
WINPATHSEP_RE = re.compile(r"\\([^\"'\\]|$)")
|
||||
|
||||
|
||||
def tempfile_arg_esc_func(arg):
|
||||
arg = quote_spaces(arg)
|
||||
if not WINDOWS:
|
||||
if not IS_WINDOWS:
|
||||
return arg
|
||||
# GCC requires double Windows slashes, let's use UNIX separator
|
||||
return WINPATHSEP_RE.sub(r"/\1", arg)
|
||||
@@ -65,16 +66,18 @@ def _file_long_data(env, data):
|
||||
)
|
||||
if os.path.isfile(tmp_file):
|
||||
return tmp_file
|
||||
with open(tmp_file, "w") as fp:
|
||||
with open(tmp_file, mode="w", encoding="utf8") as fp:
|
||||
fp.write(data)
|
||||
return tmp_file
|
||||
|
||||
|
||||
def exists(_):
|
||||
return True
|
||||
def exists(env):
|
||||
return "compiledb" not in COMMAND_LINE_TARGETS and not env.IsIntegrationDump()
|
||||
|
||||
|
||||
def generate(env):
|
||||
if not exists(env):
|
||||
return env
|
||||
kwargs = dict(
|
||||
_long_sources_hook=long_sources_hook,
|
||||
TEMPFILE=TempFileMunge,
|
||||
|
||||
@@ -14,243 +14,15 @@
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import atexit
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from tempfile import mkstemp
|
||||
|
||||
import click
|
||||
|
||||
from platformio import fs, util
|
||||
from platformio.compat import get_filesystem_encoding, get_locale_encoding, glob_escape
|
||||
from platformio.package.manager.core import get_core_package_dir
|
||||
from platformio.proc import exec_command
|
||||
|
||||
|
||||
class InoToCPPConverter(object):
|
||||
|
||||
PROTOTYPE_RE = re.compile(
|
||||
r"""^(
|
||||
(?:template\<.*\>\s*)? # template
|
||||
([a-z_\d\&]+\*?\s+){1,2} # return type
|
||||
([a-z_\d]+\s*) # name of prototype
|
||||
\([a-z_,\.\*\&\[\]\s\d]*\) # arguments
|
||||
)\s*(\{|;) # must end with `{` or `;`
|
||||
""",
|
||||
re.X | re.M | re.I,
|
||||
)
|
||||
DETECTMAIN_RE = re.compile(r"void\s+(setup|loop)\s*\(", re.M | re.I)
|
||||
PROTOPTRS_TPLRE = r"\([^&\(]*&(%s)[^\)]*\)"
|
||||
|
||||
def __init__(self, env):
|
||||
self.env = env
|
||||
self._main_ino = None
|
||||
self._safe_encoding = None
|
||||
|
||||
def read_safe_contents(self, path):
|
||||
error_reported = False
|
||||
for encoding in (
|
||||
"utf-8",
|
||||
None,
|
||||
get_filesystem_encoding(),
|
||||
get_locale_encoding(),
|
||||
"latin-1",
|
||||
):
|
||||
try:
|
||||
with io.open(path, encoding=encoding) as fp:
|
||||
contents = fp.read()
|
||||
self._safe_encoding = encoding
|
||||
return contents
|
||||
except UnicodeDecodeError:
|
||||
if not error_reported:
|
||||
error_reported = True
|
||||
click.secho(
|
||||
"Unicode decode error has occurred, please remove invalid "
|
||||
"(non-ASCII or non-UTF8) characters from %s file or convert it to UTF-8"
|
||||
% path,
|
||||
fg="yellow",
|
||||
err=True,
|
||||
)
|
||||
return ""
|
||||
|
||||
def write_safe_contents(self, path, contents):
|
||||
with io.open(
|
||||
path, "w", encoding=self._safe_encoding, errors="backslashreplace"
|
||||
) as fp:
|
||||
return fp.write(contents)
|
||||
|
||||
def is_main_node(self, contents):
|
||||
return self.DETECTMAIN_RE.search(contents)
|
||||
|
||||
def convert(self, nodes):
|
||||
contents = self.merge(nodes)
|
||||
if not contents:
|
||||
return None
|
||||
return self.process(contents)
|
||||
|
||||
def merge(self, nodes):
|
||||
assert nodes
|
||||
lines = []
|
||||
for node in nodes:
|
||||
contents = self.read_safe_contents(node.get_path())
|
||||
_lines = ['# 1 "%s"' % node.get_path().replace("\\", "/"), contents]
|
||||
if self.is_main_node(contents):
|
||||
lines = _lines + lines
|
||||
self._main_ino = node.get_path()
|
||||
else:
|
||||
lines.extend(_lines)
|
||||
|
||||
if not self._main_ino:
|
||||
self._main_ino = nodes[0].get_path()
|
||||
|
||||
return "\n".join(["#include <Arduino.h>"] + lines) if lines else None
|
||||
|
||||
def process(self, contents):
|
||||
out_file = self._main_ino + ".cpp"
|
||||
assert self._gcc_preprocess(contents, out_file)
|
||||
contents = self.read_safe_contents(out_file)
|
||||
contents = self._join_multiline_strings(contents)
|
||||
self.write_safe_contents(out_file, self.append_prototypes(contents))
|
||||
return out_file
|
||||
|
||||
def _gcc_preprocess(self, contents, out_file):
|
||||
tmp_path = mkstemp()[1]
|
||||
self.write_safe_contents(tmp_path, contents)
|
||||
self.env.Execute(
|
||||
self.env.VerboseAction(
|
||||
'$CXX -o "{0}" -x c++ -fpreprocessed -dD -E "{1}"'.format(
|
||||
out_file, tmp_path
|
||||
),
|
||||
"Converting " + os.path.basename(out_file[:-4]),
|
||||
)
|
||||
)
|
||||
atexit.register(_delete_file, tmp_path)
|
||||
return os.path.isfile(out_file)
|
||||
|
||||
def _join_multiline_strings(self, contents):
|
||||
if "\\\n" not in contents:
|
||||
return contents
|
||||
newlines = []
|
||||
linenum = 0
|
||||
stropen = False
|
||||
for line in contents.split("\n"):
|
||||
_linenum = self._parse_preproc_line_num(line)
|
||||
if _linenum is not None:
|
||||
linenum = _linenum
|
||||
else:
|
||||
linenum += 1
|
||||
|
||||
if line.endswith("\\"):
|
||||
if line.startswith('"'):
|
||||
stropen = True
|
||||
newlines.append(line[:-1])
|
||||
continue
|
||||
if stropen:
|
||||
newlines[len(newlines) - 1] += line[:-1]
|
||||
continue
|
||||
elif stropen and line.endswith(('",', '";')):
|
||||
newlines[len(newlines) - 1] += line
|
||||
stropen = False
|
||||
newlines.append(
|
||||
'#line %d "%s"' % (linenum, self._main_ino.replace("\\", "/"))
|
||||
)
|
||||
continue
|
||||
|
||||
newlines.append(line)
|
||||
|
||||
return "\n".join(newlines)
|
||||
|
||||
@staticmethod
|
||||
def _parse_preproc_line_num(line):
|
||||
if not line.startswith("#"):
|
||||
return None
|
||||
tokens = line.split(" ", 3)
|
||||
if len(tokens) > 2 and tokens[1].isdigit():
|
||||
return int(tokens[1])
|
||||
return None
|
||||
|
||||
def _parse_prototypes(self, contents):
|
||||
prototypes = []
|
||||
reserved_keywords = set(["if", "else", "while"])
|
||||
for match in self.PROTOTYPE_RE.finditer(contents):
|
||||
if (
|
||||
set([match.group(2).strip(), match.group(3).strip()])
|
||||
& reserved_keywords
|
||||
):
|
||||
continue
|
||||
prototypes.append(match)
|
||||
return prototypes
|
||||
|
||||
def _get_total_lines(self, contents):
|
||||
total = 0
|
||||
if contents.endswith("\n"):
|
||||
contents = contents[:-1]
|
||||
for line in contents.split("\n")[::-1]:
|
||||
linenum = self._parse_preproc_line_num(line)
|
||||
if linenum is not None:
|
||||
return total + linenum
|
||||
total += 1
|
||||
return total
|
||||
|
||||
def append_prototypes(self, contents):
|
||||
prototypes = self._parse_prototypes(contents) or []
|
||||
|
||||
# skip already declared prototypes
|
||||
declared = set(m.group(1).strip() for m in prototypes if m.group(4) == ";")
|
||||
prototypes = [m for m in prototypes if m.group(1).strip() not in declared]
|
||||
|
||||
if not prototypes:
|
||||
return contents
|
||||
|
||||
prototype_names = set(m.group(3).strip() for m in prototypes)
|
||||
split_pos = prototypes[0].start()
|
||||
match_ptrs = re.search(
|
||||
self.PROTOPTRS_TPLRE % ("|".join(prototype_names)),
|
||||
contents[:split_pos],
|
||||
re.M,
|
||||
)
|
||||
if match_ptrs:
|
||||
split_pos = contents.rfind("\n", 0, match_ptrs.start()) + 1
|
||||
|
||||
result = []
|
||||
result.append(contents[:split_pos].strip())
|
||||
result.append("%s;" % ";\n".join([m.group(1) for m in prototypes]))
|
||||
result.append(
|
||||
'#line %d "%s"'
|
||||
% (
|
||||
self._get_total_lines(contents[:split_pos]),
|
||||
self._main_ino.replace("\\", "/"),
|
||||
)
|
||||
)
|
||||
result.append(contents[split_pos:].strip())
|
||||
return "\n".join(result)
|
||||
|
||||
|
||||
def ConvertInoToCpp(env):
|
||||
src_dir = glob_escape(env.subst("$PROJECT_SRC_DIR"))
|
||||
ino_nodes = env.Glob(os.path.join(src_dir, "*.ino")) + env.Glob(
|
||||
os.path.join(src_dir, "*.pde")
|
||||
)
|
||||
if not ino_nodes:
|
||||
return
|
||||
c = InoToCPPConverter(env)
|
||||
out_file = c.convert(ino_nodes)
|
||||
|
||||
atexit.register(_delete_file, out_file)
|
||||
|
||||
|
||||
def _delete_file(path):
|
||||
try:
|
||||
if os.path.isfile(path):
|
||||
os.remove(path)
|
||||
except: # pylint: disable=bare-except
|
||||
pass
|
||||
|
||||
|
||||
@util.memoized()
|
||||
def _get_compiler_type(env):
|
||||
def GetCompilerType(env):
|
||||
if env.subst("$CC").endswith("-gcc"):
|
||||
return "gcc"
|
||||
try:
|
||||
@@ -269,10 +41,6 @@ def _get_compiler_type(env):
|
||||
return None
|
||||
|
||||
|
||||
def GetCompilerType(env):
|
||||
return _get_compiler_type(env)
|
||||
|
||||
|
||||
def GetActualLDScript(env):
|
||||
def _lookup_in_ldpath(script):
|
||||
for d in env.get("LIBPATH", []):
|
||||
@@ -318,7 +86,7 @@ def GetActualLDScript(env):
|
||||
env.Exit(1)
|
||||
|
||||
|
||||
def ConfigureDebugFlags(env):
|
||||
def ConfigureDebugTarget(env):
|
||||
def _cleanup_debug_flags(scope):
|
||||
if scope not in env:
|
||||
return
|
||||
@@ -333,30 +101,28 @@ def ConfigureDebugFlags(env):
|
||||
for scope in ("ASFLAGS", "CCFLAGS", "LINKFLAGS"):
|
||||
_cleanup_debug_flags(scope)
|
||||
|
||||
debug_flags = env.ParseFlags(env.GetProjectOption("debug_build_flags"))
|
||||
debug_flags = env.ParseFlags(
|
||||
env.get("PIODEBUGFLAGS")
|
||||
if env.get("PIODEBUGFLAGS")
|
||||
and not env.GetProjectOptions(as_dict=True).get("debug_build_flags")
|
||||
else env.GetProjectOption("debug_build_flags")
|
||||
)
|
||||
|
||||
env.MergeFlags(debug_flags)
|
||||
optimization_flags = [
|
||||
f for f in debug_flags.get("CCFLAGS", []) if f.startswith(("-O", "-g"))
|
||||
]
|
||||
|
||||
if optimization_flags:
|
||||
env.AppendUnique(ASFLAGS=optimization_flags, LINKFLAGS=optimization_flags)
|
||||
|
||||
|
||||
def ConfigureTestTarget(env):
|
||||
env.Append(
|
||||
CPPDEFINES=["UNIT_TEST", "UNITY_INCLUDE_CONFIG_H"],
|
||||
CPPPATH=[os.path.join("$BUILD_DIR", "UnityTestLib")],
|
||||
)
|
||||
unitylib = env.BuildLibrary(
|
||||
os.path.join("$BUILD_DIR", "UnityTestLib"), get_core_package_dir("tool-unity")
|
||||
)
|
||||
env.Prepend(LIBS=[unitylib])
|
||||
|
||||
src_filter = ["+<*.cpp>", "+<*.c>"]
|
||||
if "PIOTEST_RUNNING_NAME" in env:
|
||||
src_filter.append("+<%s%s>" % (env["PIOTEST_RUNNING_NAME"], os.path.sep))
|
||||
env.Replace(PIOTEST_SRC_FILTER=src_filter)
|
||||
env.AppendUnique(
|
||||
ASFLAGS=[
|
||||
# skip -O flags for assembler
|
||||
f
|
||||
for f in optimization_flags
|
||||
if f.startswith("-g")
|
||||
],
|
||||
LINKFLAGS=optimization_flags,
|
||||
)
|
||||
|
||||
|
||||
def GetExtraScripts(env, scope):
|
||||
@@ -369,18 +135,17 @@ def GetExtraScripts(env, scope):
|
||||
if not items:
|
||||
return items
|
||||
with fs.cd(env.subst("$PROJECT_DIR")):
|
||||
return [os.path.realpath(item) for item in items]
|
||||
return [os.path.abspath(env.subst(item)) for item in items]
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.AddMethod(GetCompilerType)
|
||||
env.AddMethod(GetActualLDScript)
|
||||
env.AddMethod(ConfigureDebugTarget)
|
||||
env.AddMethod(GetExtraScripts)
|
||||
# bakward-compatibility with Zephyr build script
|
||||
env.AddMethod(ConfigureDebugTarget, "ConfigureDebugFlags")
|
||||
|
||||
|
||||
def exists(_):
|
||||
return True
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.AddMethod(ConvertInoToCpp)
|
||||
env.AddMethod(GetCompilerType)
|
||||
env.AddMethod(GetActualLDScript)
|
||||
env.AddMethod(ConfigureDebugFlags)
|
||||
env.AddMethod(ConfigureTestTarget)
|
||||
env.AddMethod(GetExtraScripts)
|
||||
return env
|
||||
|
||||
@@ -19,9 +19,10 @@ import sys
|
||||
|
||||
from SCons.Script import ARGUMENTS # pylint: disable=import-error
|
||||
from SCons.Script import COMMAND_LINE_TARGETS # pylint: disable=import-error
|
||||
from SCons.Script import DefaultEnvironment # pylint: disable=import-error
|
||||
|
||||
from platformio import fs, util
|
||||
from platformio.compat import WINDOWS
|
||||
from platformio.compat import IS_MACOS, IS_WINDOWS
|
||||
from platformio.package.meta import PackageItem
|
||||
from platformio.package.version import get_original_version
|
||||
from platformio.platform.exception import UnknownBoard
|
||||
@@ -32,16 +33,17 @@ from platformio.project.config import ProjectOptions
|
||||
|
||||
|
||||
@util.memoized()
|
||||
def PioPlatform(env):
|
||||
variables = env.GetProjectOptions(as_dict=True)
|
||||
if "framework" in variables:
|
||||
# support PIO Core 3.0 dev/platforms
|
||||
variables["pioframework"] = variables["framework"]
|
||||
def _PioPlatform():
|
||||
env = DefaultEnvironment()
|
||||
p = PlatformFactory.new(os.path.dirname(env["PLATFORM_MANIFEST"]))
|
||||
p.configure_default_packages(variables, COMMAND_LINE_TARGETS)
|
||||
p.configure_project_packages(env["PIOENV"], COMMAND_LINE_TARGETS)
|
||||
return p
|
||||
|
||||
|
||||
def PioPlatform(_):
|
||||
return _PioPlatform()
|
||||
|
||||
|
||||
def BoardConfig(env, board=None):
|
||||
with fs.cd(env.subst("$PROJECT_DIR")):
|
||||
try:
|
||||
@@ -49,9 +51,10 @@ def BoardConfig(env, board=None):
|
||||
board = board or env.get("BOARD")
|
||||
assert board, "BoardConfig: Board is not defined"
|
||||
return p.board_config(board)
|
||||
except (AssertionError, UnknownBoard) as e:
|
||||
sys.stderr.write("Error: %s\n" % str(e))
|
||||
except (AssertionError, UnknownBoard) as exc:
|
||||
sys.stderr.write("Error: %s\n" % str(exc))
|
||||
env.Exit(1)
|
||||
return None
|
||||
|
||||
|
||||
def GetFrameworkScript(env, framework):
|
||||
@@ -70,7 +73,6 @@ def LoadPioPlatform(env):
|
||||
env["PIOPLATFORM"] = p.name
|
||||
|
||||
# Add toolchains and uploaders to $PATH and $*_LIBRARY_PATH
|
||||
systype = util.get_systype()
|
||||
for pkg in p.get_installed_packages():
|
||||
type_ = p.get_package_type(pkg.metadata.name)
|
||||
if type_ not in ("toolchain", "uploader", "debugger"):
|
||||
@@ -82,12 +84,12 @@ def LoadPioPlatform(env):
|
||||
else pkg.path,
|
||||
)
|
||||
if (
|
||||
not WINDOWS
|
||||
not IS_WINDOWS
|
||||
and os.path.isdir(os.path.join(pkg.path, "lib"))
|
||||
and type_ != "toolchain"
|
||||
):
|
||||
env.PrependENVPath(
|
||||
"DYLD_LIBRARY_PATH" if "darwin" in systype else "LD_LIBRARY_PATH",
|
||||
"DYLD_LIBRARY_PATH" if IS_MACOS else "LD_LIBRARY_PATH",
|
||||
os.path.join(pkg.path, "lib"),
|
||||
)
|
||||
|
||||
@@ -160,7 +162,7 @@ def PrintConfiguration(env): # pylint: disable=too-many-statements
|
||||
and pkg_metadata
|
||||
and pkg_metadata.spec.external
|
||||
):
|
||||
data.append("(%s)" % pkg_metadata.spec.url)
|
||||
data.append("(%s)" % pkg_metadata.spec.uri)
|
||||
if board_config:
|
||||
data.extend([">", board_config.get("name")])
|
||||
return data
|
||||
@@ -213,7 +215,7 @@ def PrintConfiguration(env): # pylint: disable=too-many-statements
|
||||
data = []
|
||||
for item in platform.dump_used_packages():
|
||||
original_version = get_original_version(item["version"])
|
||||
info = "%s %s" % (item["name"], item["version"])
|
||||
info = "%s @ %s" % (item["name"], item["version"])
|
||||
extra = []
|
||||
if original_version:
|
||||
extra.append(original_version)
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from platformio.project.config import MISSING, ProjectConfig, ProjectOptions
|
||||
from platformio.compat import MISSING
|
||||
from platformio.project.config import ProjectConfig
|
||||
|
||||
|
||||
def GetProjectConfig(env):
|
||||
@@ -30,15 +31,17 @@ def GetProjectOption(env, option, default=MISSING):
|
||||
|
||||
|
||||
def LoadProjectOptions(env):
|
||||
for option, value in env.GetProjectOptions():
|
||||
option_meta = ProjectOptions.get("env." + option)
|
||||
config = env.GetProjectConfig()
|
||||
section = "env:" + env["PIOENV"]
|
||||
for option in config.options(section):
|
||||
option_meta = config.find_option_meta(section, option)
|
||||
if (
|
||||
not option_meta
|
||||
or not option_meta.buildenvvar
|
||||
or option_meta.buildenvvar in env
|
||||
):
|
||||
continue
|
||||
env[option_meta.buildenvvar] = value
|
||||
env[option_meta.buildenvvar] = config.get(section, option)
|
||||
|
||||
|
||||
def exists(_):
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import json
|
||||
import sys
|
||||
from os import environ, makedirs, remove
|
||||
from os.path import isdir, join, splitdrive
|
||||
@@ -23,9 +24,8 @@ from os.path import isdir, join, splitdrive
|
||||
from elftools.elf.descriptions import describe_sh_flags
|
||||
from elftools.elf.elffile import ELFFile
|
||||
|
||||
from platformio.compat import dump_json_to_unicode
|
||||
from platformio.compat import IS_WINDOWS
|
||||
from platformio.proc import exec_command
|
||||
from platformio.util import get_systype
|
||||
|
||||
|
||||
def _run_tool(cmd, env, tool_args):
|
||||
@@ -37,7 +37,7 @@ def _run_tool(cmd, env, tool_args):
|
||||
makedirs(build_dir)
|
||||
tmp_file = join(build_dir, "size-data-longcmd.txt")
|
||||
|
||||
with open(tmp_file, "w") as fp:
|
||||
with open(tmp_file, mode="w", encoding="utf8") as fp:
|
||||
fp.write("\n".join(tool_args))
|
||||
|
||||
cmd.append("@" + tmp_file)
|
||||
@@ -164,7 +164,7 @@ def _collect_symbols_info(env, elffile, elf_path, sections):
|
||||
location = symbol_locations.get(hex(symbol["addr"]))
|
||||
if not location or "?" in location:
|
||||
continue
|
||||
if "windows" in get_systype():
|
||||
if IS_WINDOWS:
|
||||
drive, tail = splitdrive(location)
|
||||
location = join(drive.upper(), tail)
|
||||
symbol["file"] = location
|
||||
@@ -220,7 +220,7 @@ def DumpSizeData(_, target, source, env): # pylint: disable=unused-argument
|
||||
"sections": sections,
|
||||
}
|
||||
|
||||
files = dict()
|
||||
files = {}
|
||||
for symbol in _collect_symbols_info(env, elffile, elf_path, sections):
|
||||
file_path = symbol.get("file") or "unknown"
|
||||
if not files.get(file_path, {}):
|
||||
@@ -235,14 +235,16 @@ def DumpSizeData(_, target, source, env): # pylint: disable=unused-argument
|
||||
|
||||
files[file_path]["symbols"].append(symbol)
|
||||
|
||||
data["memory"]["files"] = list()
|
||||
data["memory"]["files"] = []
|
||||
for k, v in files.items():
|
||||
file_data = {"path": k}
|
||||
file_data.update(v)
|
||||
data["memory"]["files"].append(file_data)
|
||||
|
||||
with open(join(env.subst("$BUILD_DIR"), "sizedata.json"), "w") as fp:
|
||||
fp.write(dump_json_to_unicode(data))
|
||||
with open(
|
||||
join(env.subst("$BUILD_DIR"), "sizedata.json"), mode="w", encoding="utf8"
|
||||
) as fp:
|
||||
fp.write(json.dumps(data))
|
||||
|
||||
|
||||
def exists(_):
|
||||
|
||||
@@ -29,9 +29,9 @@ def VerboseAction(_, act, actstr):
|
||||
return Action(act, actstr)
|
||||
|
||||
|
||||
def PioClean(env, clean_dir):
|
||||
def PioClean(env, clean_all=False):
|
||||
def _relpath(path):
|
||||
if compat.WINDOWS:
|
||||
if compat.IS_WINDOWS:
|
||||
prefix = os.getcwd()[:2].lower()
|
||||
if (
|
||||
":" not in prefix
|
||||
@@ -41,21 +41,22 @@ def PioClean(env, clean_dir):
|
||||
return path
|
||||
return os.path.relpath(path)
|
||||
|
||||
if not os.path.isdir(clean_dir):
|
||||
def _clean_dir(path):
|
||||
clean_rel_path = _relpath(path)
|
||||
print(f"Removing {clean_rel_path}")
|
||||
fs.rmtree(path)
|
||||
|
||||
build_dir = env.subst("$BUILD_DIR")
|
||||
libdeps_dir = env.subst("$PROJECT_LIBDEPS_DIR")
|
||||
if os.path.isdir(build_dir):
|
||||
_clean_dir(build_dir)
|
||||
else:
|
||||
print("Build environment is clean")
|
||||
env.Exit(0)
|
||||
clean_rel_path = _relpath(clean_dir)
|
||||
for root, _, files in os.walk(clean_dir):
|
||||
for f in files:
|
||||
dst = os.path.join(root, f)
|
||||
os.remove(dst)
|
||||
print(
|
||||
"Removed %s"
|
||||
% (dst if not clean_rel_path.startswith(".") else _relpath(dst))
|
||||
)
|
||||
|
||||
if clean_all and os.path.isdir(libdeps_dir):
|
||||
_clean_dir(libdeps_dir)
|
||||
|
||||
print("Done cleaning")
|
||||
fs.rmtree(clean_dir)
|
||||
env.Exit(0)
|
||||
|
||||
|
||||
def AddTarget( # pylint: disable=too-many-arguments
|
||||
@@ -65,7 +66,7 @@ def AddTarget( # pylint: disable=too-many-arguments
|
||||
actions,
|
||||
title=None,
|
||||
description=None,
|
||||
group="Generic",
|
||||
group="General",
|
||||
always_build=True,
|
||||
):
|
||||
if "__PIO_TARGETS" not in env:
|
||||
@@ -95,13 +96,6 @@ def DumpTargets(env):
|
||||
t["group"] == "Platform" for t in targets.values()
|
||||
):
|
||||
targets["upload"] = dict(name="upload", group="Platform", title="Upload")
|
||||
targets["compiledb"] = dict(
|
||||
name="compiledb",
|
||||
title="Compilation Database",
|
||||
description="Generate compilation database `compile_commands.json`",
|
||||
group="Advanced",
|
||||
)
|
||||
targets["clean"] = dict(name="clean", title="Clean", group="Generic")
|
||||
return list(targets.values())
|
||||
|
||||
|
||||
|
||||
63
platformio/builder/tools/piotest.py
Normal file
63
platformio/builder/tools/piotest.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
|
||||
from platformio.builder.tools import platformio as piotool
|
||||
from platformio.test.result import TestSuite
|
||||
from platformio.test.runners.factory import TestRunnerFactory
|
||||
|
||||
|
||||
def ConfigureTestTarget(env):
|
||||
env.Append(
|
||||
CPPDEFINES=["UNIT_TEST", "PIO_UNIT_TESTING"],
|
||||
PIOTEST_SRC_FILTER=[f"+<*.{ext}>" for ext in piotool.SRC_BUILD_EXT],
|
||||
)
|
||||
env.Prepend(CPPPATH=["$PROJECT_TEST_DIR"])
|
||||
|
||||
if "PIOTEST_RUNNING_NAME" in env:
|
||||
test_name = env["PIOTEST_RUNNING_NAME"]
|
||||
while True:
|
||||
test_name = os.path.dirname(test_name) # parent dir
|
||||
# skip nested tests (user's side issue?)
|
||||
if not test_name or os.path.basename(test_name).startswith("test_"):
|
||||
break
|
||||
env.Prepend(
|
||||
PIOTEST_SRC_FILTER=[
|
||||
f"+<{test_name}{os.path.sep}*.{ext}>"
|
||||
for ext in piotool.SRC_BUILD_EXT
|
||||
],
|
||||
CPPPATH=[os.path.join("$PROJECT_TEST_DIR", test_name)],
|
||||
)
|
||||
|
||||
env.Prepend(
|
||||
PIOTEST_SRC_FILTER=[f"+<$PIOTEST_RUNNING_NAME{os.path.sep}>"],
|
||||
CPPPATH=[os.path.join("$PROJECT_TEST_DIR", "$PIOTEST_RUNNING_NAME")],
|
||||
)
|
||||
|
||||
test_runner = TestRunnerFactory.new(
|
||||
TestSuite(env["PIOENV"], env.get("PIOTEST_RUNNING_NAME", "*")),
|
||||
env.GetProjectConfig(),
|
||||
)
|
||||
test_runner.configure_build_env(env)
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.AddMethod(ConfigureTestTarget)
|
||||
|
||||
|
||||
def exists(_):
|
||||
return True
|
||||
@@ -12,25 +12,24 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from fnmatch import fnmatch
|
||||
from os import environ
|
||||
from os.path import isfile, join
|
||||
from shutil import copyfile
|
||||
from time import sleep
|
||||
|
||||
from SCons.Script import ARGUMENTS # pylint: disable=import-error
|
||||
from serial import Serial, SerialException
|
||||
|
||||
from platformio import exception, fs, util
|
||||
from platformio.compat import WINDOWS
|
||||
from platformio import exception, fs
|
||||
from platformio.device.finder import find_mbed_disk, find_serial_port, is_pattern_port
|
||||
from platformio.device.list.util import list_serial_ports
|
||||
from platformio.proc import exec_command
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
|
||||
def FlushSerialBuffer(env, port):
|
||||
s = Serial(env.subst(port))
|
||||
@@ -62,7 +61,7 @@ def WaitForNewSerialPort(env, before):
|
||||
elapsed = 0
|
||||
before = [p["port"] for p in before]
|
||||
while elapsed < 5 and new_port is None:
|
||||
now = [p["port"] for p in util.get_serial_ports()]
|
||||
now = [p["port"] for p in list_serial_ports()]
|
||||
for p in now:
|
||||
if p not in before:
|
||||
new_port = p
|
||||
@@ -97,67 +96,30 @@ def WaitForNewSerialPort(env, before):
|
||||
|
||||
def AutodetectUploadPort(*args, **kwargs):
|
||||
env = args[0]
|
||||
|
||||
def _get_pattern():
|
||||
if "UPLOAD_PORT" not in env:
|
||||
return None
|
||||
if set(["*", "?", "[", "]"]) & set(env["UPLOAD_PORT"]):
|
||||
return env["UPLOAD_PORT"]
|
||||
return None
|
||||
|
||||
def _is_match_pattern(port):
|
||||
pattern = _get_pattern()
|
||||
if not pattern:
|
||||
return True
|
||||
return fnmatch(port, pattern)
|
||||
|
||||
def _look_for_mbed_disk():
|
||||
msdlabels = ("mbed", "nucleo", "frdm", "microbit")
|
||||
for item in util.get_logical_devices():
|
||||
if item["path"].startswith("/net") or not _is_match_pattern(item["path"]):
|
||||
continue
|
||||
mbed_pages = [join(item["path"], n) for n in ("mbed.htm", "mbed.html")]
|
||||
if any(isfile(p) for p in mbed_pages):
|
||||
return item["path"]
|
||||
if item["name"] and any(l in item["name"].lower() for l in msdlabels):
|
||||
return item["path"]
|
||||
return None
|
||||
|
||||
def _look_for_serial_port():
|
||||
port = None
|
||||
board_hwids = []
|
||||
upload_protocol = env.subst("$UPLOAD_PROTOCOL")
|
||||
if "BOARD" in env and "build.hwids" in env.BoardConfig():
|
||||
board_hwids = env.BoardConfig().get("build.hwids")
|
||||
for item in util.get_serial_ports(filter_hwid=True):
|
||||
if not _is_match_pattern(item["port"]):
|
||||
continue
|
||||
port = item["port"]
|
||||
if upload_protocol.startswith("blackmagic"):
|
||||
if WINDOWS and port.startswith("COM") and len(port) > 4:
|
||||
port = "\\\\.\\%s" % port
|
||||
if "GDB" in item["description"]:
|
||||
return port
|
||||
for hwid in board_hwids:
|
||||
hwid_str = ("%s:%s" % (hwid[0], hwid[1])).replace("0x", "")
|
||||
if hwid_str in item["hwid"]:
|
||||
return port
|
||||
return port
|
||||
|
||||
if "UPLOAD_PORT" in env and not _get_pattern():
|
||||
print(env.subst("Use manually specified: $UPLOAD_PORT"))
|
||||
initial_port = env.subst("$UPLOAD_PORT")
|
||||
upload_protocol = env.subst("$UPLOAD_PROTOCOL")
|
||||
if initial_port and not is_pattern_port(initial_port):
|
||||
print(env.subst("Using manually specified: $UPLOAD_PORT"))
|
||||
return
|
||||
|
||||
if env.subst("$UPLOAD_PROTOCOL") == "mbed" or (
|
||||
"mbed" in env.subst("$PIOFRAMEWORK") and not env.subst("$UPLOAD_PROTOCOL")
|
||||
if upload_protocol == "mbed" or (
|
||||
"mbed" in env.subst("$PIOFRAMEWORK") and not upload_protocol
|
||||
):
|
||||
env.Replace(UPLOAD_PORT=_look_for_mbed_disk())
|
||||
env.Replace(UPLOAD_PORT=find_mbed_disk(initial_port))
|
||||
else:
|
||||
try:
|
||||
fs.ensure_udev_rules()
|
||||
except exception.InvalidUdevRules as e:
|
||||
sys.stderr.write("\n%s\n\n" % e)
|
||||
env.Replace(UPLOAD_PORT=_look_for_serial_port())
|
||||
except exception.InvalidUdevRules as exc:
|
||||
sys.stderr.write("\n%s\n\n" % exc)
|
||||
env.Replace(
|
||||
UPLOAD_PORT=find_serial_port(
|
||||
initial_port=initial_port,
|
||||
board_config=env.BoardConfig() if "BOARD" in env else None,
|
||||
upload_protocol=upload_protocol,
|
||||
prefer_gdb_port="blackmagic" in upload_protocol,
|
||||
verbose=int(ARGUMENTS.get("PIOVERBOSE", 0)),
|
||||
)
|
||||
)
|
||||
|
||||
if env.subst("$UPLOAD_PORT"):
|
||||
print(env.subst("Auto-detected: $UPLOAD_PORT"))
|
||||
@@ -175,10 +137,12 @@ def UploadToDisk(_, target, source, env):
|
||||
assert "UPLOAD_PORT" in env
|
||||
progname = env.subst("$PROGNAME")
|
||||
for ext in ("bin", "hex"):
|
||||
fpath = join(env.subst("$BUILD_DIR"), "%s.%s" % (progname, ext))
|
||||
if not isfile(fpath):
|
||||
fpath = os.path.join(env.subst("$BUILD_DIR"), "%s.%s" % (progname, ext))
|
||||
if not os.path.isfile(fpath):
|
||||
continue
|
||||
copyfile(fpath, join(env.subst("$UPLOAD_PORT"), "%s.%s" % (progname, ext)))
|
||||
copyfile(
|
||||
fpath, os.path.join(env.subst("$UPLOAD_PORT"), "%s.%s" % (progname, ext))
|
||||
)
|
||||
print(
|
||||
"Firmware has been successfully uploaded.\n"
|
||||
"(Some boards may require manual hard reset)"
|
||||
@@ -211,7 +175,7 @@ def CheckUploadSize(_, target, source, env):
|
||||
if not isinstance(cmd, list):
|
||||
cmd = cmd.split()
|
||||
cmd = [arg.replace("$SOURCES", str(source[0])) for arg in cmd if arg]
|
||||
sysenv = environ.copy()
|
||||
sysenv = os.environ.copy()
|
||||
sysenv["PATH"] = str(env["ENV"]["PATH"])
|
||||
result = exec_command(env.subst(cmd), env=sysenv)
|
||||
if result["returncode"] != 0:
|
||||
@@ -236,9 +200,9 @@ def CheckUploadSize(_, target, source, env):
|
||||
def _format_availale_bytes(value, total):
|
||||
percent_raw = float(value) / float(total)
|
||||
blocks_per_progress = 10
|
||||
used_blocks = int(round(blocks_per_progress * percent_raw))
|
||||
if used_blocks > blocks_per_progress:
|
||||
used_blocks = blocks_per_progress
|
||||
used_blocks = min(
|
||||
int(round(blocks_per_progress * percent_raw)), blocks_per_progress
|
||||
)
|
||||
return "[{:{}}] {: 6.1%} (used {:d} bytes from {:d} bytes)".format(
|
||||
"=" * used_blocks, blocks_per_progress, percent_raw, value, total
|
||||
)
|
||||
|
||||
@@ -23,11 +23,10 @@ from SCons.Node import FS # pylint: disable=import-error
|
||||
from SCons.Script import COMMAND_LINE_TARGETS # pylint: disable=import-error
|
||||
from SCons.Script import AlwaysBuild # pylint: disable=import-error
|
||||
from SCons.Script import DefaultEnvironment # pylint: disable=import-error
|
||||
from SCons.Script import Export # pylint: disable=import-error
|
||||
from SCons.Script import SConscript # pylint: disable=import-error
|
||||
|
||||
from platformio import __version__, fs
|
||||
from platformio.compat import MACOS, string_types
|
||||
from platformio.compat import IS_MACOS, string_types
|
||||
from platformio.package.version import pepver_to_semver
|
||||
|
||||
SRC_HEADER_EXT = ["h", "hpp"]
|
||||
@@ -47,14 +46,16 @@ def scons_patched_match_splitext(path, suffixes=None):
|
||||
|
||||
|
||||
def GetBuildType(env):
|
||||
return (
|
||||
"debug"
|
||||
if (
|
||||
set(["debug", "sizedata"]) & set(COMMAND_LINE_TARGETS)
|
||||
or env.GetProjectOption("build_type") == "debug"
|
||||
)
|
||||
else "release"
|
||||
)
|
||||
modes = []
|
||||
if (
|
||||
set(["__debug", "sizedata"]) # sizedata = for memory inspection
|
||||
& set(COMMAND_LINE_TARGETS)
|
||||
or env.GetProjectOption("build_type") == "debug"
|
||||
):
|
||||
modes.append("debug")
|
||||
if "__test" in COMMAND_LINE_TARGETS or env.GetProjectOption("build_type") == "test":
|
||||
modes.append("test")
|
||||
return "+".join(modes or ["release"])
|
||||
|
||||
|
||||
def BuildProgram(env):
|
||||
@@ -69,14 +70,12 @@ def BuildProgram(env):
|
||||
if (
|
||||
env.get("LIBS")
|
||||
and env.GetCompilerType() == "gcc"
|
||||
and (env.PioPlatform().is_embedded() or not MACOS)
|
||||
and (env.PioPlatform().is_embedded() or not IS_MACOS)
|
||||
):
|
||||
env.Prepend(_LIBFLAGS="-Wl,--start-group ")
|
||||
env.Append(_LIBFLAGS=" -Wl,--end-group")
|
||||
|
||||
program = env.Program(
|
||||
os.path.join("$BUILD_DIR", env.subst("$PROGNAME")), env["PIOBUILDFILES"]
|
||||
)
|
||||
program = env.Program(env.subst("$PROGPATH"), env["PIOBUILDFILES"])
|
||||
env.Replace(PIOMAINPROG=program)
|
||||
|
||||
AlwaysBuild(
|
||||
@@ -112,10 +111,6 @@ def ProcessProgramDeps(env):
|
||||
|
||||
env.PrintConfiguration()
|
||||
|
||||
# fix ASM handling under non case-sensitive OS
|
||||
if not Util.case_sensitive_suffixes(".s", ".S"):
|
||||
env.Replace(AS="$CC", ASCOM="$ASPPCOM")
|
||||
|
||||
# process extra flags from board
|
||||
if "BOARD" in env and "build.extra_flags" in env.BoardConfig():
|
||||
env.ProcessFlags(env.BoardConfig().get("build.extra_flags"))
|
||||
@@ -126,56 +121,58 @@ def ProcessProgramDeps(env):
|
||||
# process framework scripts
|
||||
env.BuildFrameworks(env.get("PIOFRAMEWORK"))
|
||||
|
||||
if env.GetBuildType() == "debug":
|
||||
env.ConfigureDebugFlags()
|
||||
if "debug" in env.GetBuildType():
|
||||
env.ConfigureDebugTarget()
|
||||
|
||||
# remove specified flags
|
||||
env.ProcessUnFlags(env.get("BUILD_UNFLAGS"))
|
||||
|
||||
if "__test" in COMMAND_LINE_TARGETS:
|
||||
env.ConfigureTestTarget()
|
||||
if "compiledb" in COMMAND_LINE_TARGETS and env.get(
|
||||
"COMPILATIONDB_INCLUDE_TOOLCHAIN"
|
||||
):
|
||||
for scope, includes in env.DumpIntegrationIncludes().items():
|
||||
if scope in ("toolchain",):
|
||||
env.Append(CPPPATH=includes)
|
||||
|
||||
|
||||
def ProcessProjectDeps(env):
|
||||
project_lib_builder = env.ConfigureProjectLibBuilder()
|
||||
plb = env.ConfigureProjectLibBuilder()
|
||||
|
||||
# prepend project libs to the beginning of list
|
||||
env.Prepend(LIBS=project_lib_builder.build())
|
||||
env.Prepend(LIBS=plb.build())
|
||||
# prepend extra linker related options from libs
|
||||
env.PrependUnique(
|
||||
**{
|
||||
key: project_lib_builder.env.get(key)
|
||||
key: plb.env.get(key)
|
||||
for key in ("LIBS", "LIBPATH", "LINKFLAGS")
|
||||
if project_lib_builder.env.get(key)
|
||||
if plb.env.get(key)
|
||||
}
|
||||
)
|
||||
|
||||
projenv = env.Clone()
|
||||
|
||||
# CPPPATH from dependencies
|
||||
projenv.PrependUnique(CPPPATH=project_lib_builder.env.get("CPPPATH"))
|
||||
# extra build flags from `platformio.ini`
|
||||
projenv.ProcessFlags(env.get("SRC_BUILD_FLAGS"))
|
||||
|
||||
is_test = "__test" in COMMAND_LINE_TARGETS
|
||||
if is_test:
|
||||
projenv.BuildSources(
|
||||
if "test" in env.GetBuildType():
|
||||
build_files_before_nums = len(env.get("PIOBUILDFILES", []))
|
||||
plb.env.BuildSources(
|
||||
"$BUILD_TEST_DIR", "$PROJECT_TEST_DIR", "$PIOTEST_SRC_FILTER"
|
||||
)
|
||||
if not is_test or env.GetProjectOption("test_build_project_src"):
|
||||
projenv.BuildSources(
|
||||
if len(env.get("PIOBUILDFILES", [])) - build_files_before_nums < 1:
|
||||
sys.stderr.write(
|
||||
"Error: Nothing to build. Please put your test suites "
|
||||
"to the '%s' folder\n" % env.subst("$PROJECT_TEST_DIR")
|
||||
)
|
||||
env.Exit(1)
|
||||
|
||||
if "test" not in env.GetBuildType() or env.GetProjectOption("test_build_src"):
|
||||
plb.env.BuildSources(
|
||||
"$BUILD_SRC_DIR", "$PROJECT_SRC_DIR", env.get("SRC_FILTER")
|
||||
)
|
||||
|
||||
if not env.get("PIOBUILDFILES") and not COMMAND_LINE_TARGETS:
|
||||
sys.stderr.write(
|
||||
"Error: Nothing to build. Please put your source code files "
|
||||
"to '%s' folder\n" % env.subst("$PROJECT_SRC_DIR")
|
||||
"to the '%s' folder\n" % env.subst("$PROJECT_SRC_DIR")
|
||||
)
|
||||
env.Exit(1)
|
||||
|
||||
Export("projenv")
|
||||
|
||||
|
||||
def ParseFlagsExtended(env, flags): # pylint: disable=too-many-branches
|
||||
if not isinstance(flags, list):
|
||||
@@ -206,12 +203,12 @@ def ParseFlagsExtended(env, flags): # pylint: disable=too-many-branches
|
||||
for k in ("CPPPATH", "LIBPATH"):
|
||||
for i, p in enumerate(result.get(k, [])):
|
||||
if os.path.isdir(p):
|
||||
result[k][i] = os.path.realpath(p)
|
||||
result[k][i] = os.path.abspath(p)
|
||||
|
||||
# fix relative path for "-include"
|
||||
for i, f in enumerate(result.get("CCFLAGS", [])):
|
||||
if isinstance(f, tuple) and f[0] == "-include":
|
||||
result["CCFLAGS"][i] = (f[0], env.File(os.path.realpath(f[1].get_path())))
|
||||
result["CCFLAGS"][i] = (f[0], env.File(os.path.abspath(f[1].get_path())))
|
||||
|
||||
return result
|
||||
|
||||
@@ -240,33 +237,30 @@ def ProcessUnFlags(env, flags):
|
||||
if not flags:
|
||||
return
|
||||
parsed = env.ParseFlagsExtended(flags)
|
||||
|
||||
# get all flags and copy them to each "*FLAGS" variable
|
||||
all_flags = []
|
||||
for key, unflags in parsed.items():
|
||||
if key.endswith("FLAGS"):
|
||||
all_flags.extend(unflags)
|
||||
for key, unflags in parsed.items():
|
||||
if key.endswith("FLAGS"):
|
||||
parsed[key].extend(all_flags)
|
||||
|
||||
for key, unflags in parsed.items():
|
||||
for unflag in unflags:
|
||||
for current in env.get(key, []):
|
||||
conditions = [
|
||||
unflag == current,
|
||||
isinstance(current, (tuple, list)) and unflag[0] == current[0],
|
||||
]
|
||||
if any(conditions):
|
||||
env[key].remove(current)
|
||||
unflag_scopes = tuple(set(["ASPPFLAGS"] + list(parsed.keys())))
|
||||
for scope in unflag_scopes:
|
||||
for unflags in parsed.values():
|
||||
for unflag in unflags:
|
||||
for current in env.get(scope, []):
|
||||
conditions = [
|
||||
unflag == current,
|
||||
not isinstance(unflag, (tuple, list))
|
||||
and isinstance(current, (tuple, list))
|
||||
and unflag == current[0],
|
||||
]
|
||||
if any(conditions):
|
||||
env[scope].remove(current)
|
||||
|
||||
|
||||
def MatchSourceFiles(env, src_dir, src_filter=None):
|
||||
def StringifyMacro(env, value): # pylint: disable=unused-argument
|
||||
return '\\"%s\\"' % value.replace('"', '\\\\\\"')
|
||||
|
||||
|
||||
def MatchSourceFiles(env, src_dir, src_filter=None, src_exts=None):
|
||||
src_filter = env.subst(src_filter) if src_filter else None
|
||||
src_filter = src_filter or SRC_FILTER_DEFAULT
|
||||
return fs.match_src_files(
|
||||
env.subst(src_dir), src_filter, SRC_BUILD_EXT + SRC_HEADER_EXT
|
||||
)
|
||||
src_exts = src_exts or (SRC_BUILD_EXT + SRC_HEADER_EXT)
|
||||
return fs.match_src_files(env.subst(src_dir), src_filter, src_exts)
|
||||
|
||||
|
||||
def CollectBuildFiles(
|
||||
@@ -279,7 +273,7 @@ def CollectBuildFiles(
|
||||
if src_dir.endswith(os.sep):
|
||||
src_dir = src_dir[:-1]
|
||||
|
||||
for item in env.MatchSourceFiles(src_dir, src_filter):
|
||||
for item in env.MatchSourceFiles(src_dir, src_filter, SRC_BUILD_EXT):
|
||||
_reldir = os.path.dirname(item)
|
||||
_src_dir = os.path.join(src_dir, _reldir) if _reldir else src_dir
|
||||
_var_dir = os.path.join(variant_dir, _reldir) if _reldir else variant_dir
|
||||
@@ -288,8 +282,7 @@ def CollectBuildFiles(
|
||||
variants.append(_var_dir)
|
||||
env.VariantDir(_var_dir, _src_dir, duplicate)
|
||||
|
||||
if fs.path_endswith_ext(item, SRC_BUILD_EXT):
|
||||
sources.append(env.File(os.path.join(_var_dir, os.path.basename(item))))
|
||||
sources.append(env.File(os.path.join(_var_dir, os.path.basename(item))))
|
||||
|
||||
middlewares = env.get("__PIO_BUILD_MIDDLEWARES")
|
||||
if not middlewares:
|
||||
@@ -323,33 +316,25 @@ def BuildFrameworks(env, frameworks):
|
||||
)
|
||||
env.Exit(1)
|
||||
|
||||
board_frameworks = env.BoardConfig().get("frameworks", [])
|
||||
if frameworks == ["platformio"]:
|
||||
if board_frameworks:
|
||||
frameworks.insert(0, board_frameworks[0])
|
||||
else:
|
||||
sys.stderr.write("Error: Please specify `board` in `platformio.ini`\n")
|
||||
env.Exit(1)
|
||||
|
||||
for f in frameworks:
|
||||
if f == "arduino":
|
||||
# Arduino IDE appends .o the end of filename
|
||||
supported_frameworks = env.BoardConfig().get("frameworks", [])
|
||||
for name in frameworks:
|
||||
if name == "arduino":
|
||||
# Arduino IDE appends .o to the end of filename
|
||||
Builder.match_splitext = scons_patched_match_splitext
|
||||
if "nobuild" not in COMMAND_LINE_TARGETS:
|
||||
env.ConvertInoToCpp()
|
||||
|
||||
if f in board_frameworks:
|
||||
SConscript(env.GetFrameworkScript(f), exports="env")
|
||||
if name in supported_frameworks:
|
||||
SConscript(env.GetFrameworkScript(name), exports="env")
|
||||
else:
|
||||
sys.stderr.write("Error: This board doesn't support %s framework!\n" % f)
|
||||
sys.stderr.write("Error: This board doesn't support %s framework!\n" % name)
|
||||
env.Exit(1)
|
||||
|
||||
|
||||
def BuildLibrary(env, variant_dir, src_dir, src_filter=None):
|
||||
def BuildLibrary(env, variant_dir, src_dir, src_filter=None, nodes=None):
|
||||
env.ProcessUnFlags(env.get("BUILD_UNFLAGS"))
|
||||
return env.StaticLibrary(
|
||||
env.subst(variant_dir), env.CollectBuildFiles(variant_dir, src_dir, src_filter)
|
||||
)
|
||||
nodes = nodes or env.CollectBuildFiles(variant_dir, src_dir, src_filter)
|
||||
return env.StaticLibrary(env.subst(variant_dir), nodes)
|
||||
|
||||
|
||||
def BuildSources(env, variant_dir, src_dir, src_filter=None):
|
||||
@@ -373,6 +358,7 @@ def generate(env):
|
||||
env.AddMethod(ParseFlagsExtended)
|
||||
env.AddMethod(ProcessFlags)
|
||||
env.AddMethod(ProcessUnFlags)
|
||||
env.AddMethod(StringifyMacro)
|
||||
env.AddMethod(MatchSourceFiles)
|
||||
env.AddMethod(CollectBuildFiles)
|
||||
env.AddMethod(AddBuildMiddleware)
|
||||
|
||||
@@ -23,7 +23,7 @@ from platformio.package.lockfile import LockFile
|
||||
from platformio.project.helpers import get_project_cache_dir
|
||||
|
||||
|
||||
class ContentCache(object):
|
||||
class ContentCache:
|
||||
def __init__(self, namespace=None):
|
||||
self.cache_dir = os.path.join(get_project_cache_dir(), namespace or "content")
|
||||
self._db_path = os.path.join(self.cache_dir, "db.data")
|
||||
@@ -78,9 +78,9 @@ class ContentCache(object):
|
||||
if not os.path.isdir(os.path.dirname(cache_path)):
|
||||
os.makedirs(os.path.dirname(cache_path))
|
||||
try:
|
||||
with codecs.open(cache_path, "wb", encoding="utf8") as fp:
|
||||
with codecs.open(cache_path, mode="wb", encoding="utf8") as fp:
|
||||
fp.write(data)
|
||||
with open(self._db_path, "a") as fp:
|
||||
with open(self._db_path, mode="a", encoding="utf8") as fp:
|
||||
fp.write("%s=%s\n" % (str(expire_time), os.path.basename(cache_path)))
|
||||
except UnicodeError:
|
||||
if os.path.isfile(cache_path):
|
||||
@@ -92,7 +92,7 @@ class ContentCache(object):
|
||||
return self._unlock_dbindex()
|
||||
|
||||
def delete(self, keys=None):
|
||||
""" Keys=None, delete expired items """
|
||||
"""Keys=None, delete expired items"""
|
||||
if not os.path.isfile(self._db_path):
|
||||
return None
|
||||
if not keys:
|
||||
@@ -102,7 +102,7 @@ class ContentCache(object):
|
||||
paths_for_delete = [self.get_cache_path(k) for k in keys]
|
||||
found = False
|
||||
newlines = []
|
||||
with open(self._db_path) as fp:
|
||||
with open(self._db_path, encoding="utf8") as fp:
|
||||
for line in fp.readlines():
|
||||
line = line.strip()
|
||||
if "=" not in line:
|
||||
@@ -129,7 +129,7 @@ class ContentCache(object):
|
||||
pass
|
||||
|
||||
if found and self._lock_dbindex():
|
||||
with open(self._db_path, "w") as fp:
|
||||
with open(self._db_path, mode="w", encoding="utf8") as fp:
|
||||
fp.write("\n".join(newlines) + "\n")
|
||||
self._unlock_dbindex()
|
||||
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
# pylint: disable=too-many-arguments,too-many-locals,too-many-branches
|
||||
# pylint: disable=redefined-builtin,too-many-statements
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from collections import Counter
|
||||
from os.path import dirname, isfile
|
||||
from time import time
|
||||
@@ -24,14 +26,13 @@ import click
|
||||
from tabulate import tabulate
|
||||
|
||||
from platformio import app, exception, fs, util
|
||||
from platformio.commands.check.defect import DefectItem
|
||||
from platformio.commands.check.tools import CheckToolFactory
|
||||
from platformio.compat import dump_json_to_unicode
|
||||
from platformio.check.defect import DefectItem
|
||||
from platformio.check.tools import CheckToolFactory
|
||||
from platformio.project.config import ProjectConfig
|
||||
from platformio.project.helpers import find_project_dir_above, get_project_dir
|
||||
|
||||
|
||||
@click.command("check", short_help="Static code analysis")
|
||||
@click.command("check", short_help="Static Code Analysis")
|
||||
@click.option("-e", "--environment", multiple=True)
|
||||
@click.option(
|
||||
"-d",
|
||||
@@ -105,8 +106,8 @@ def cli(
|
||||
)
|
||||
|
||||
default_patterns = [
|
||||
config.get_optional_dir("src"),
|
||||
config.get_optional_dir("include"),
|
||||
config.get("platformio", "src_dir"),
|
||||
config.get("platformio", "include_dir"),
|
||||
]
|
||||
tool_options = dict(
|
||||
verbose=verbose,
|
||||
@@ -117,6 +118,7 @@ def cli(
|
||||
if silent
|
||||
else severity or config.get("env:" + envname, "check_severity"),
|
||||
skip_packages=skip_packages or env_options.get("check_skip_packages"),
|
||||
platform_packages=env_options.get("platform_packages"),
|
||||
)
|
||||
|
||||
for tool in config.get("env:" + envname, "check_tool"):
|
||||
@@ -163,9 +165,12 @@ def cli(
|
||||
print_processing_footer(result)
|
||||
|
||||
if json_output:
|
||||
click.echo(dump_json_to_unicode(results_to_json(results)))
|
||||
click.echo(json.dumps(results_to_json(results)))
|
||||
elif not silent:
|
||||
print_check_summary(results)
|
||||
print_check_summary(results, verbose=verbose)
|
||||
|
||||
# Reset custom project config
|
||||
app.set_session_var("custom_project_conf", None)
|
||||
|
||||
command_failed = any(r.get("succeeded") is False for r in results)
|
||||
if command_failed:
|
||||
@@ -193,7 +198,7 @@ def print_processing_header(tool, envname, envdump):
|
||||
"Checking %s > %s (%s)"
|
||||
% (click.style(envname, fg="cyan", bold=True), tool, "; ".join(envdump))
|
||||
)
|
||||
terminal_width, _ = click.get_terminal_size()
|
||||
terminal_width, _ = shutil.get_terminal_size()
|
||||
click.secho("-" * terminal_width, bold=True)
|
||||
|
||||
|
||||
@@ -214,7 +219,7 @@ def print_processing_footer(result):
|
||||
|
||||
|
||||
def collect_component_stats(result):
|
||||
components = dict()
|
||||
components = {}
|
||||
|
||||
def _append_defect(component, defect):
|
||||
if not components.get(component):
|
||||
@@ -249,7 +254,7 @@ def print_defects_stats(results):
|
||||
|
||||
severity_labels = list(DefectItem.SEVERITY_LABELS.values())
|
||||
severity_labels.reverse()
|
||||
tabular_data = list()
|
||||
tabular_data = []
|
||||
for k, v in component_stats.items():
|
||||
tool_defect = [v.get(s, 0) for s in severity_labels]
|
||||
tabular_data.append([k] + tool_defect)
|
||||
@@ -266,7 +271,7 @@ def print_defects_stats(results):
|
||||
click.echo()
|
||||
|
||||
|
||||
def print_check_summary(results):
|
||||
def print_check_summary(results, verbose=False):
|
||||
click.echo()
|
||||
|
||||
tabular_data = []
|
||||
@@ -283,6 +288,8 @@ def print_check_summary(results):
|
||||
status_str = click.style("FAILED", fg="red")
|
||||
elif result.get("succeeded") is None:
|
||||
status_str = "IGNORED"
|
||||
if not verbose:
|
||||
continue
|
||||
else:
|
||||
succeeded_nums += 1
|
||||
status_str = click.style("PASSED", fg="green")
|
||||
@@ -22,7 +22,7 @@ from platformio.project.helpers import get_project_dir
|
||||
# pylint: disable=too-many-arguments
|
||||
|
||||
|
||||
class DefectItem(object):
|
||||
class DefectItem:
|
||||
|
||||
SEVERITY_HIGH = 1
|
||||
SEVERITY_MEDIUM = 2
|
||||
@@ -34,7 +34,7 @@ class DefectItem(object):
|
||||
severity,
|
||||
category,
|
||||
message,
|
||||
file="unknown",
|
||||
file=None,
|
||||
line=0,
|
||||
column=0,
|
||||
id=None,
|
||||
@@ -50,7 +50,7 @@ class DefectItem(object):
|
||||
self.callstack = callstack
|
||||
self.cwe = cwe
|
||||
self.id = id
|
||||
self.file = file
|
||||
self.file = file or "unknown"
|
||||
if file.lower().startswith(get_project_dir().lower()):
|
||||
self.file = os.path.relpath(file, get_project_dir())
|
||||
|
||||
@@ -86,7 +86,7 @@ class DefectItem(object):
|
||||
"severity": self.SEVERITY_LABELS[self.severity],
|
||||
"category": self.category,
|
||||
"message": self.message,
|
||||
"file": os.path.realpath(self.file),
|
||||
"file": os.path.abspath(self.file),
|
||||
"line": self.line,
|
||||
"column": self.column,
|
||||
"callstack": self.callstack,
|
||||
@@ -13,12 +13,12 @@
|
||||
# limitations under the License.
|
||||
|
||||
from platformio import exception
|
||||
from platformio.commands.check.tools.clangtidy import ClangtidyCheckTool
|
||||
from platformio.commands.check.tools.cppcheck import CppcheckCheckTool
|
||||
from platformio.commands.check.tools.pvsstudio import PvsStudioCheckTool
|
||||
from platformio.check.tools.clangtidy import ClangtidyCheckTool
|
||||
from platformio.check.tools.cppcheck import CppcheckCheckTool
|
||||
from platformio.check.tools.pvsstudio import PvsStudioCheckTool
|
||||
|
||||
|
||||
class CheckToolFactory(object):
|
||||
class CheckToolFactory:
|
||||
@staticmethod
|
||||
def new(tool, project_dir, config, envname, options):
|
||||
cls = None
|
||||
@@ -12,17 +12,20 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import glob
|
||||
import os
|
||||
from tempfile import NamedTemporaryFile
|
||||
import tempfile
|
||||
|
||||
import click
|
||||
|
||||
from platformio import compat, fs, proc
|
||||
from platformio.commands.check.defect import DefectItem
|
||||
from platformio.project.helpers import load_project_ide_data
|
||||
from platformio import fs, proc
|
||||
from platformio.check.defect import DefectItem
|
||||
from platformio.package.manager.core import get_core_package_dir
|
||||
from platformio.package.meta import PackageSpec
|
||||
from platformio.project.helpers import load_build_metadata
|
||||
|
||||
|
||||
class CheckToolBase(object): # pylint: disable=too-many-instance-attributes
|
||||
class CheckToolBase: # pylint: disable=too-many-instance-attributes
|
||||
def __init__(self, project_dir, config, envname, options):
|
||||
self.config = config
|
||||
self.envname = envname
|
||||
@@ -54,7 +57,7 @@ class CheckToolBase(object): # pylint: disable=too-many-instance-attributes
|
||||
]
|
||||
|
||||
def _load_cpp_data(self, project_dir):
|
||||
data = load_project_ide_data(project_dir, self.envname)
|
||||
data = load_build_metadata(project_dir, self.envname)
|
||||
if not data:
|
||||
return
|
||||
self.cc_flags = click.parser.split_arg_string(data.get("cc_flags", ""))
|
||||
@@ -65,6 +68,13 @@ class CheckToolBase(object): # pylint: disable=too-many-instance-attributes
|
||||
self.cxx_path = data.get("cxx_path")
|
||||
self.toolchain_defines = self._get_toolchain_defines()
|
||||
|
||||
def get_tool_dir(self, pkg_name):
|
||||
for spec in self.options["platform_packages"] or []:
|
||||
spec = PackageSpec(spec)
|
||||
if spec.name == pkg_name:
|
||||
return get_core_package_dir(pkg_name, spec=spec)
|
||||
return get_core_package_dir(pkg_name)
|
||||
|
||||
def get_flags(self, tool):
|
||||
result = []
|
||||
flags = self.options.get("flags") or []
|
||||
@@ -104,7 +114,7 @@ class CheckToolBase(object): # pylint: disable=too-many-instance-attributes
|
||||
return {lang: _extract_defines(lang, incflags_file) for lang in ("c", "c++")}
|
||||
|
||||
def _create_tmp_file(self, data):
|
||||
with NamedTemporaryFile("w", delete=False) as fp:
|
||||
with tempfile.NamedTemporaryFile("w", delete=False) as fp:
|
||||
fp.write(data)
|
||||
self._tmp_files.append(fp.name)
|
||||
return fp.name
|
||||
@@ -200,14 +210,14 @@ class CheckToolBase(object): # pylint: disable=too-many-instance-attributes
|
||||
|
||||
def _add_file(path):
|
||||
if path.endswith(header_extensions):
|
||||
result["headers"].append(os.path.realpath(path))
|
||||
result["headers"].append(os.path.abspath(path))
|
||||
elif path.endswith(c_extension):
|
||||
result["c"].append(os.path.realpath(path))
|
||||
result["c"].append(os.path.abspath(path))
|
||||
elif path.endswith(cpp_extensions):
|
||||
result["c++"].append(os.path.realpath(path))
|
||||
result["c++"].append(os.path.abspath(path))
|
||||
|
||||
for pattern in patterns:
|
||||
for item in compat.glob_recursive(pattern):
|
||||
for item in glob.glob(pattern, recursive=True):
|
||||
if not os.path.isdir(item):
|
||||
_add_file(item)
|
||||
for root, _, files in os.walk(item, followlinks=True):
|
||||
@@ -15,13 +15,12 @@
|
||||
import re
|
||||
from os.path import join
|
||||
|
||||
from platformio.commands.check.defect import DefectItem
|
||||
from platformio.commands.check.tools.base import CheckToolBase
|
||||
from platformio.package.manager.core import get_core_package_dir
|
||||
from platformio.check.defect import DefectItem
|
||||
from platformio.check.tools.base import CheckToolBase
|
||||
|
||||
|
||||
class ClangtidyCheckTool(CheckToolBase):
|
||||
def tool_output_filter(self, line):
|
||||
def tool_output_filter(self, line): # pylint: disable=arguments-differ
|
||||
if not self.options.get("verbose") and "[clang-diagnostic-error]" in line:
|
||||
return ""
|
||||
|
||||
@@ -34,7 +33,7 @@ class ClangtidyCheckTool(CheckToolBase):
|
||||
|
||||
return ""
|
||||
|
||||
def parse_defect(self, raw_line):
|
||||
def parse_defect(self, raw_line): # pylint: disable=arguments-differ
|
||||
match = re.match(r"^(.*):(\d+):(\d+):\s+([^:]+):\s(.+)\[([^]]+)\]$", raw_line)
|
||||
if not match:
|
||||
return raw_line
|
||||
@@ -56,18 +55,20 @@ class ClangtidyCheckTool(CheckToolBase):
|
||||
return cmd_result["returncode"] < 2
|
||||
|
||||
def configure_command(self):
|
||||
tool_path = join(get_core_package_dir("tool-clangtidy"), "clang-tidy")
|
||||
tool_path = join(self.get_tool_dir("tool-clangtidy"), "clang-tidy")
|
||||
|
||||
cmd = [tool_path, "--quiet"]
|
||||
flags = self.get_flags("clangtidy")
|
||||
if not self.is_flag_set("--checks", flags):
|
||||
if not (
|
||||
self.is_flag_set("--checks", flags) or self.is_flag_set("--config", flags)
|
||||
):
|
||||
cmd.append("--checks=*")
|
||||
|
||||
project_files = self.get_project_target_files(self.options["patterns"])
|
||||
|
||||
src_files = []
|
||||
for scope in project_files:
|
||||
src_files.extend(project_files[scope])
|
||||
for items in project_files.values():
|
||||
src_files.extend(items)
|
||||
|
||||
cmd.extend(flags + src_files + ["--"])
|
||||
cmd.extend(
|
||||
@@ -77,7 +78,7 @@ class ClangtidyCheckTool(CheckToolBase):
|
||||
includes = []
|
||||
for inc in self.cpp_includes:
|
||||
if self.options.get("skip_packages") and inc.lower().startswith(
|
||||
self.config.get_optional_dir("packages").lower()
|
||||
self.config.get("platformio", "packages_dir").lower()
|
||||
):
|
||||
continue
|
||||
includes.append(inc)
|
||||
@@ -17,13 +17,13 @@ import os
|
||||
import click
|
||||
|
||||
from platformio import proc
|
||||
from platformio.commands.check.defect import DefectItem
|
||||
from platformio.commands.check.tools.base import CheckToolBase
|
||||
from platformio.package.manager.core import get_core_package_dir
|
||||
from platformio.check.defect import DefectItem
|
||||
from platformio.check.tools.base import CheckToolBase
|
||||
|
||||
|
||||
class CppcheckCheckTool(CheckToolBase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._field_delimiter = "<&PIO&>"
|
||||
self._buffer = ""
|
||||
self.defect_fields = [
|
||||
@@ -36,9 +36,8 @@ class CppcheckCheckTool(CheckToolBase):
|
||||
"cwe",
|
||||
"id",
|
||||
]
|
||||
super(CppcheckCheckTool, self).__init__(*args, **kwargs)
|
||||
|
||||
def tool_output_filter(self, line):
|
||||
def tool_output_filter(self, line): # pylint: disable=arguments-differ
|
||||
if (
|
||||
not self.options.get("verbose")
|
||||
and "--suppress=unmatchedSuppression:" in line
|
||||
@@ -50,13 +49,14 @@ class CppcheckCheckTool(CheckToolBase):
|
||||
for msg in (
|
||||
"No C or C++ source files found",
|
||||
"unrecognized command line option",
|
||||
"there was an internal error",
|
||||
)
|
||||
):
|
||||
self._bad_input = True
|
||||
|
||||
return line
|
||||
|
||||
def parse_defect(self, raw_line):
|
||||
def parse_defect(self, raw_line): # pylint: disable=arguments-differ
|
||||
if self._field_delimiter not in raw_line:
|
||||
return None
|
||||
|
||||
@@ -64,7 +64,7 @@ class CppcheckCheckTool(CheckToolBase):
|
||||
if any(f not in self._buffer for f in self.defect_fields):
|
||||
return None
|
||||
|
||||
args = dict()
|
||||
args = {}
|
||||
for field in self._buffer.split(self._field_delimiter):
|
||||
field = field.strip().replace('"', "")
|
||||
name, value = field.split("=", 1)
|
||||
@@ -84,7 +84,7 @@ class CppcheckCheckTool(CheckToolBase):
|
||||
if (
|
||||
args.get("file", "")
|
||||
.lower()
|
||||
.startswith(self.config.get_optional_dir("packages").lower())
|
||||
.startswith(self.config.get("platformio", "packages_dir").lower())
|
||||
):
|
||||
if args["id"] in breaking_defect_ids:
|
||||
if self.options.get("verbose"):
|
||||
@@ -96,15 +96,14 @@ class CppcheckCheckTool(CheckToolBase):
|
||||
)
|
||||
click.echo()
|
||||
self._bad_input = True
|
||||
self._buffer = ""
|
||||
return None
|
||||
|
||||
self._buffer = ""
|
||||
return DefectItem(**args)
|
||||
|
||||
def configure_command(
|
||||
self, language, src_files
|
||||
): # pylint: disable=arguments-differ
|
||||
tool_path = os.path.join(get_core_package_dir("tool-cppcheck"), "cppcheck")
|
||||
def configure_command(self, language, src_file): # pylint: disable=arguments-differ
|
||||
tool_path = os.path.join(self.get_tool_dir("tool-cppcheck"), "cppcheck")
|
||||
|
||||
cmd = [
|
||||
tool_path,
|
||||
@@ -142,10 +141,11 @@ class CppcheckCheckTool(CheckToolBase):
|
||||
|
||||
build_flags = self.cxx_flags if language == "c++" else self.cc_flags
|
||||
|
||||
for flag in build_flags:
|
||||
if "-std" in flag:
|
||||
# Standards with GNU extensions are not allowed
|
||||
cmd.append("-" + flag.replace("gnu", "c"))
|
||||
if not self.is_flag_set("--std", flags):
|
||||
# Try to guess the standard version from the build flags
|
||||
for flag in build_flags:
|
||||
if "-std" in flag:
|
||||
cmd.append("-" + self.convert_language_standard(flag))
|
||||
|
||||
cmd.extend(
|
||||
["-D%s" % d for d in self.cpp_defines + self.toolchain_defines[language]]
|
||||
@@ -157,8 +157,8 @@ class CppcheckCheckTool(CheckToolBase):
|
||||
"--include=" + inc
|
||||
for inc in self.get_forced_includes(build_flags, self.cpp_includes)
|
||||
)
|
||||
cmd.append("--file-list=%s" % self._generate_src_file(src_files))
|
||||
cmd.append("--includes-file=%s" % self._generate_inc_file())
|
||||
cmd.append('"%s"' % src_file)
|
||||
|
||||
return cmd
|
||||
|
||||
@@ -201,14 +201,14 @@ class CppcheckCheckTool(CheckToolBase):
|
||||
result = []
|
||||
for inc in self.cpp_includes:
|
||||
if self.options.get("skip_packages") and inc.lower().startswith(
|
||||
self.config.get_optional_dir("packages").lower()
|
||||
self.config.get("platformio", "packages_dir").lower()
|
||||
):
|
||||
continue
|
||||
result.append(inc)
|
||||
return self._create_tmp_file("\n".join(result))
|
||||
|
||||
def clean_up(self):
|
||||
super(CppcheckCheckTool, self).clean_up()
|
||||
super().clean_up()
|
||||
|
||||
# delete temporary dump files generated by addons
|
||||
if not self.is_flag_set("--addon", self.get_flags("cppcheck")):
|
||||
@@ -225,25 +225,42 @@ class CppcheckCheckTool(CheckToolBase):
|
||||
# Cppcheck is configured to return '3' if a defect is found
|
||||
return cmd_result["returncode"] in (0, 3)
|
||||
|
||||
@staticmethod
|
||||
def convert_language_standard(flag):
|
||||
cpp_standards_map = {
|
||||
"0x": "11",
|
||||
"1y": "14",
|
||||
"1z": "17",
|
||||
"2a": "20",
|
||||
}
|
||||
|
||||
standard = flag[-2:]
|
||||
# Note: GNU extensions are not supported and converted to regular standards
|
||||
return flag.replace("gnu", "c").replace(
|
||||
standard, cpp_standards_map.get(standard, standard)
|
||||
)
|
||||
|
||||
def check(self, on_defect_callback=None):
|
||||
self._on_defect_callback = on_defect_callback
|
||||
project_files = self.get_project_target_files(self.options["patterns"])
|
||||
|
||||
languages = ("c", "c++")
|
||||
if not any([project_files[t] for t in languages]):
|
||||
project_files = self.get_project_target_files(self.options["patterns"])
|
||||
src_files_scope = ("c", "c++")
|
||||
if not any(project_files[t] for t in src_files_scope):
|
||||
click.echo("Error: Nothing to check.")
|
||||
return True
|
||||
for language in languages:
|
||||
if not project_files[language]:
|
||||
continue
|
||||
cmd = self.configure_command(language, project_files[language])
|
||||
if not cmd:
|
||||
self._bad_input = True
|
||||
continue
|
||||
if self.options.get("verbose"):
|
||||
click.echo(" ".join(cmd))
|
||||
|
||||
self.execute_check_cmd(cmd)
|
||||
for scope, files in project_files.items():
|
||||
if scope not in src_files_scope:
|
||||
continue
|
||||
for src_file in files:
|
||||
cmd = self.configure_command(scope, src_file)
|
||||
if not cmd:
|
||||
self._bad_input = True
|
||||
continue
|
||||
if self.options.get("verbose"):
|
||||
click.echo(" ".join(cmd))
|
||||
|
||||
self.execute_check_cmd(cmd)
|
||||
|
||||
self.clean_up()
|
||||
|
||||
@@ -19,41 +19,47 @@ from xml.etree.ElementTree import fromstring
|
||||
|
||||
import click
|
||||
|
||||
from platformio import proc, util
|
||||
from platformio.commands.check.defect import DefectItem
|
||||
from platformio.commands.check.tools.base import CheckToolBase
|
||||
from platformio.package.manager.core import get_core_package_dir
|
||||
from platformio import proc
|
||||
from platformio.check.defect import DefectItem
|
||||
from platformio.check.tools.base import CheckToolBase
|
||||
from platformio.compat import IS_WINDOWS
|
||||
|
||||
|
||||
class PvsStudioCheckTool(CheckToolBase): # pylint: disable=too-many-instance-attributes
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._tmp_dir = tempfile.mkdtemp(prefix="piocheck")
|
||||
self._tmp_preprocessed_file = self._generate_tmp_file_path() + ".i"
|
||||
self._tmp_output_file = self._generate_tmp_file_path() + ".pvs"
|
||||
self._tmp_cfg_file = self._generate_tmp_file_path() + ".cfg"
|
||||
self._tmp_cmd_file = self._generate_tmp_file_path() + ".cmd"
|
||||
self.tool_path = os.path.join(
|
||||
get_core_package_dir("tool-pvs-studio"),
|
||||
"x64" if "windows" in util.get_systype() else "bin",
|
||||
self.get_tool_dir("tool-pvs-studio"),
|
||||
"x64" if IS_WINDOWS else "bin",
|
||||
"pvs-studio",
|
||||
)
|
||||
super(PvsStudioCheckTool, self).__init__(*args, **kwargs)
|
||||
|
||||
with open(self._tmp_cfg_file, "w") as fp:
|
||||
with open(self._tmp_cfg_file, mode="w", encoding="utf8") as fp:
|
||||
fp.write(
|
||||
"exclude-path = "
|
||||
+ self.config.get_optional_dir("packages").replace("\\", "/")
|
||||
+ self.config.get("platformio", "packages_dir").replace("\\", "/")
|
||||
)
|
||||
|
||||
with open(self._tmp_cmd_file, "w") as fp:
|
||||
with open(self._tmp_cmd_file, mode="w", encoding="utf8") as fp:
|
||||
fp.write(
|
||||
" ".join(
|
||||
['-I"%s"' % inc.replace("\\", "/") for inc in self.cpp_includes]
|
||||
)
|
||||
)
|
||||
|
||||
def tool_output_filter(self, line):
|
||||
if "license was not entered" in line.lower():
|
||||
def tool_output_filter(self, line): # pylint: disable=arguments-differ
|
||||
if any(
|
||||
err_msg in line.lower()
|
||||
for err_msg in (
|
||||
"license was not entered",
|
||||
"license information is incorrect",
|
||||
)
|
||||
):
|
||||
self._bad_input = True
|
||||
return line
|
||||
|
||||
@@ -69,10 +75,8 @@ class PvsStudioCheckTool(CheckToolBase): # pylint: disable=too-many-instance-at
|
||||
|
||||
def _demangle_report(self, output_file):
|
||||
converter_tool = os.path.join(
|
||||
get_core_package_dir("tool-pvs-studio"),
|
||||
"HtmlGenerator"
|
||||
if "windows" in util.get_systype()
|
||||
else os.path.join("bin", "plog-converter"),
|
||||
self.get_tool_dir("tool-pvs-studio"),
|
||||
"HtmlGenerator" if IS_WINDOWS else os.path.join("bin", "plog-converter"),
|
||||
)
|
||||
|
||||
cmd = (
|
||||
@@ -187,9 +191,15 @@ class PvsStudioCheckTool(CheckToolBase): # pylint: disable=too-many-instance-at
|
||||
flags = self.cc_flags
|
||||
compiler = self.cc_path
|
||||
|
||||
cmd = [compiler, src_file, "-E", "-o", self._tmp_preprocessed_file]
|
||||
cmd = [
|
||||
compiler,
|
||||
'"%s"' % src_file,
|
||||
"-E",
|
||||
"-o",
|
||||
'"%s"' % self._tmp_preprocessed_file,
|
||||
]
|
||||
cmd.extend([f for f in flags if f])
|
||||
cmd.extend(["-D%s" % d for d in self.cpp_defines])
|
||||
cmd.extend(['"-D%s"' % d.replace('"', '\\"') for d in self.cpp_defines])
|
||||
cmd.append('@"%s"' % self._tmp_cmd_file)
|
||||
|
||||
# Explicitly specify C++ as the language used in .ino files
|
||||
@@ -204,7 +214,7 @@ class PvsStudioCheckTool(CheckToolBase): # pylint: disable=too-many-instance-at
|
||||
self._bad_input = True
|
||||
|
||||
def clean_up(self):
|
||||
super(PvsStudioCheckTool, self).clean_up()
|
||||
super().clean_up()
|
||||
if os.path.isdir(self._tmp_dir):
|
||||
shutil.rmtree(self._tmp_dir)
|
||||
|
||||
96
platformio/cli.py
Normal file
96
platformio/cli.py
Normal file
@@ -0,0 +1,96 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
|
||||
|
||||
class PlatformioCLI(click.MultiCommand):
|
||||
|
||||
leftover_args = []
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._pio_root_path = Path(__file__).parent
|
||||
self._pio_cmd_aliases = dict(package="pkg")
|
||||
|
||||
def _find_pio_commands(self):
|
||||
def _to_module_path(p):
|
||||
return (
|
||||
"platformio." + ".".join(p.relative_to(self._pio_root_path).parts)[:-3]
|
||||
)
|
||||
|
||||
result = {}
|
||||
for p in self._pio_root_path.rglob("cli.py"):
|
||||
# skip this module
|
||||
if p.parent == self._pio_root_path:
|
||||
continue
|
||||
cmd_name = p.parent.name
|
||||
result[self._pio_cmd_aliases.get(cmd_name, cmd_name)] = _to_module_path(p)
|
||||
|
||||
# find legacy commands
|
||||
for p in (self._pio_root_path / "commands").iterdir():
|
||||
if p.name.startswith("_"):
|
||||
continue
|
||||
if (p / "command.py").is_file():
|
||||
result[p.name] = _to_module_path(p / "command.py")
|
||||
elif p.name.endswith(".py"):
|
||||
result[p.name[:-3]] = _to_module_path(p)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def in_silence():
|
||||
args = PlatformioCLI.leftover_args
|
||||
return args and any(
|
||||
[
|
||||
args[0] == "debug" and "--interpreter" in " ".join(args),
|
||||
args[0] == "upgrade",
|
||||
"--json-output" in args,
|
||||
"--version" in args,
|
||||
]
|
||||
)
|
||||
|
||||
def invoke(self, ctx):
|
||||
PlatformioCLI.leftover_args = ctx.args
|
||||
if hasattr(ctx, "protected_args"):
|
||||
PlatformioCLI.leftover_args = ctx.protected_args + ctx.args
|
||||
return super().invoke(ctx)
|
||||
|
||||
def list_commands(self, ctx):
|
||||
return sorted(list(self._find_pio_commands()))
|
||||
|
||||
def get_command(self, ctx, cmd_name):
|
||||
commands = self._find_pio_commands()
|
||||
if cmd_name not in commands:
|
||||
return self._handle_obsolate_command(ctx, cmd_name)
|
||||
module = importlib.import_module(commands[cmd_name])
|
||||
return getattr(module, "cli")
|
||||
|
||||
@staticmethod
|
||||
def _handle_obsolate_command(ctx, cmd_name):
|
||||
# pylint: disable=import-outside-toplevel
|
||||
if cmd_name == "init":
|
||||
from platformio.project.commands.init import project_init_cmd
|
||||
|
||||
return project_init_cmd
|
||||
|
||||
if cmd_name == "package":
|
||||
from platformio.package.cli import cli
|
||||
|
||||
return cli
|
||||
|
||||
raise click.UsageError('No such command "%s"' % cmd_name, ctx)
|
||||
@@ -11,70 +11,3 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
|
||||
import click
|
||||
|
||||
|
||||
class PlatformioCLI(click.MultiCommand):
|
||||
|
||||
leftover_args = []
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PlatformioCLI, self).__init__(*args, **kwargs)
|
||||
self._pio_cmds_dir = os.path.dirname(__file__)
|
||||
|
||||
@staticmethod
|
||||
def in_silence():
|
||||
args = PlatformioCLI.leftover_args
|
||||
return args and any(
|
||||
[
|
||||
args[0] == "debug" and "--interpreter" in " ".join(args),
|
||||
args[0] == "upgrade",
|
||||
"--json-output" in args,
|
||||
"--version" in args,
|
||||
]
|
||||
)
|
||||
|
||||
def invoke(self, ctx):
|
||||
PlatformioCLI.leftover_args = ctx.args
|
||||
if hasattr(ctx, "protected_args"):
|
||||
PlatformioCLI.leftover_args = ctx.protected_args + ctx.args
|
||||
return super(PlatformioCLI, self).invoke(ctx)
|
||||
|
||||
def list_commands(self, ctx):
|
||||
cmds = []
|
||||
for cmd_name in os.listdir(self._pio_cmds_dir):
|
||||
if cmd_name.startswith("__init__"):
|
||||
continue
|
||||
if os.path.isfile(os.path.join(self._pio_cmds_dir, cmd_name, "command.py")):
|
||||
cmds.append(cmd_name)
|
||||
elif cmd_name.endswith(".py"):
|
||||
cmds.append(cmd_name[:-3])
|
||||
cmds.sort()
|
||||
return cmds
|
||||
|
||||
def get_command(self, ctx, cmd_name):
|
||||
mod = None
|
||||
try:
|
||||
mod_path = "platformio.commands." + cmd_name
|
||||
if os.path.isfile(os.path.join(self._pio_cmds_dir, cmd_name, "command.py")):
|
||||
mod_path = "platformio.commands.%s.command" % cmd_name
|
||||
mod = __import__(mod_path, None, None, ["cli"])
|
||||
except ImportError:
|
||||
try:
|
||||
return self._handle_obsolate_command(cmd_name)
|
||||
except AttributeError:
|
||||
pass
|
||||
raise click.UsageError('No such command "%s"' % cmd_name, ctx)
|
||||
return mod.cli
|
||||
|
||||
@staticmethod
|
||||
def _handle_obsolate_command(name):
|
||||
# pylint: disable=import-outside-toplevel
|
||||
if name == "init":
|
||||
from platformio.commands.project import project_init
|
||||
|
||||
return project_init
|
||||
raise AttributeError()
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
import click
|
||||
from tabulate import tabulate
|
||||
|
||||
from platformio.clients.registry import RegistryClient
|
||||
from platformio.commands.account import validate_username
|
||||
from platformio.commands.team import validate_orgname_teamname
|
||||
|
||||
|
||||
def validate_client(value):
|
||||
if ":" in value:
|
||||
validate_orgname_teamname(value)
|
||||
else:
|
||||
validate_username(value)
|
||||
return value
|
||||
|
||||
|
||||
@click.group("access", short_help="Manage resource access")
|
||||
def cli():
|
||||
pass
|
||||
|
||||
|
||||
def validate_urn(value):
|
||||
value = str(value).strip()
|
||||
if not re.match(r"^prn:reg:pkg:(\d+):(\w+)$", value, flags=re.I):
|
||||
raise click.BadParameter("Invalid URN format.")
|
||||
return value
|
||||
|
||||
|
||||
@cli.command("public", short_help="Make resource public")
|
||||
@click.argument(
|
||||
"urn",
|
||||
callback=lambda _, __, value: validate_urn(value),
|
||||
)
|
||||
@click.option("--urn-type", type=click.Choice(["prn:reg:pkg"]), default="prn:reg:pkg")
|
||||
def access_public(urn, urn_type):
|
||||
client = RegistryClient()
|
||||
client.update_resource(urn=urn, private=0)
|
||||
return click.secho(
|
||||
"The resource %s has been successfully updated." % urn,
|
||||
fg="green",
|
||||
)
|
||||
|
||||
|
||||
@cli.command("private", short_help="Make resource private")
|
||||
@click.argument(
|
||||
"urn",
|
||||
callback=lambda _, __, value: validate_urn(value),
|
||||
)
|
||||
@click.option("--urn-type", type=click.Choice(["prn:reg:pkg"]), default="prn:reg:pkg")
|
||||
def access_private(urn, urn_type):
|
||||
client = RegistryClient()
|
||||
client.update_resource(urn=urn, private=1)
|
||||
return click.secho(
|
||||
"The resource %s has been successfully updated." % urn,
|
||||
fg="green",
|
||||
)
|
||||
|
||||
|
||||
@cli.command("grant", short_help="Grant access")
|
||||
@click.argument("level", type=click.Choice(["admin", "maintainer", "guest"]))
|
||||
@click.argument(
|
||||
"client",
|
||||
metavar="[<ORGNAME:TEAMNAME>|<USERNAME>]",
|
||||
callback=lambda _, __, value: validate_client(value),
|
||||
)
|
||||
@click.argument(
|
||||
"urn",
|
||||
callback=lambda _, __, value: validate_urn(value),
|
||||
)
|
||||
@click.option("--urn-type", type=click.Choice(["prn:reg:pkg"]), default="prn:reg:pkg")
|
||||
def access_grant(level, client, urn, urn_type):
|
||||
reg_client = RegistryClient()
|
||||
reg_client.grant_access_for_resource(urn=urn, client=client, level=level)
|
||||
return click.secho(
|
||||
"Access for resource %s has been granted for %s" % (urn, client),
|
||||
fg="green",
|
||||
)
|
||||
|
||||
|
||||
@cli.command("revoke", short_help="Revoke access")
|
||||
@click.argument(
|
||||
"client",
|
||||
metavar="[ORGNAME:TEAMNAME|USERNAME]",
|
||||
callback=lambda _, __, value: validate_client(value),
|
||||
)
|
||||
@click.argument(
|
||||
"urn",
|
||||
callback=lambda _, __, value: validate_urn(value),
|
||||
)
|
||||
@click.option("--urn-type", type=click.Choice(["prn:reg:pkg"]), default="prn:reg:pkg")
|
||||
def access_revoke(client, urn, urn_type):
|
||||
reg_client = RegistryClient()
|
||||
reg_client.revoke_access_from_resource(urn=urn, client=client)
|
||||
return click.secho(
|
||||
"Access for resource %s has been revoked for %s" % (urn, client),
|
||||
fg="green",
|
||||
)
|
||||
|
||||
|
||||
@cli.command("list", short_help="List published resources")
|
||||
@click.argument("owner", required=False)
|
||||
@click.option("--urn-type", type=click.Choice(["prn:reg:pkg"]), default="prn:reg:pkg")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def access_list(owner, urn_type, json_output):
|
||||
reg_client = RegistryClient()
|
||||
resources = reg_client.list_resources(owner=owner)
|
||||
if json_output:
|
||||
return click.echo(json.dumps(resources))
|
||||
if not resources:
|
||||
return click.secho("You do not have any resources.", fg="yellow")
|
||||
for resource in resources:
|
||||
click.echo()
|
||||
click.secho(resource.get("name"), fg="cyan")
|
||||
click.echo("-" * len(resource.get("name")))
|
||||
table_data = []
|
||||
table_data.append(("URN:", resource.get("urn")))
|
||||
table_data.append(("Owner:", resource.get("owner")))
|
||||
table_data.append(
|
||||
(
|
||||
"Access level(s):",
|
||||
", ".join(
|
||||
(level.capitalize() for level in resource.get("access_levels"))
|
||||
),
|
||||
)
|
||||
)
|
||||
click.echo(tabulate(table_data, tablefmt="plain"))
|
||||
return click.echo()
|
||||
@@ -1,299 +0,0 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import re
|
||||
|
||||
import click
|
||||
from tabulate import tabulate
|
||||
|
||||
from platformio.clients.account import AccountClient, AccountNotAuthorized
|
||||
|
||||
|
||||
@click.group("account", short_help="Manage PlatformIO account")
|
||||
def cli():
|
||||
pass
|
||||
|
||||
|
||||
def validate_username(value, field="username"):
|
||||
value = str(value).strip()
|
||||
if not re.match(r"^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,37}$", value, flags=re.I):
|
||||
raise click.BadParameter(
|
||||
"Invalid %s format. "
|
||||
"%s must contain only alphanumeric characters "
|
||||
"or single hyphens, cannot begin or end with a hyphen, "
|
||||
"and must not be longer than 38 characters."
|
||||
% (field.lower(), field.capitalize())
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
def validate_email(value):
|
||||
value = str(value).strip()
|
||||
if not re.match(r"^[a-z\d_.+-]+@[a-z\d\-]+\.[a-z\d\-.]+$", value, flags=re.I):
|
||||
raise click.BadParameter("Invalid email address")
|
||||
return value
|
||||
|
||||
|
||||
def validate_password(value):
|
||||
value = str(value).strip()
|
||||
if not re.match(r"^(?=.*[a-z])(?=.*\d).{8,}$", value):
|
||||
raise click.BadParameter(
|
||||
"Invalid password format. "
|
||||
"Password must contain at least 8 characters"
|
||||
" including a number and a lowercase letter"
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
@cli.command("register", short_help="Create new PlatformIO Account")
|
||||
@click.option(
|
||||
"-u",
|
||||
"--username",
|
||||
prompt=True,
|
||||
callback=lambda _, __, value: validate_username(value),
|
||||
)
|
||||
@click.option(
|
||||
"-e", "--email", prompt=True, callback=lambda _, __, value: validate_email(value)
|
||||
)
|
||||
@click.option(
|
||||
"-p",
|
||||
"--password",
|
||||
prompt=True,
|
||||
hide_input=True,
|
||||
confirmation_prompt=True,
|
||||
callback=lambda _, __, value: validate_password(value),
|
||||
)
|
||||
@click.option("--firstname", prompt=True)
|
||||
@click.option("--lastname", prompt=True)
|
||||
def account_register(username, email, password, firstname, lastname):
|
||||
client = AccountClient()
|
||||
client.registration(username, email, password, firstname, lastname)
|
||||
return click.secho(
|
||||
"An account has been successfully created. "
|
||||
"Please check your mail to activate your account and verify your email address.",
|
||||
fg="green",
|
||||
)
|
||||
|
||||
|
||||
@cli.command("login", short_help="Log in to PlatformIO Account")
|
||||
@click.option("-u", "--username", prompt="Username or email")
|
||||
@click.option("-p", "--password", prompt=True, hide_input=True)
|
||||
def account_login(username, password):
|
||||
client = AccountClient()
|
||||
client.login(username, password)
|
||||
return click.secho("Successfully logged in!", fg="green")
|
||||
|
||||
|
||||
@cli.command("logout", short_help="Log out of PlatformIO Account")
|
||||
def account_logout():
|
||||
client = AccountClient()
|
||||
client.logout()
|
||||
return click.secho("Successfully logged out!", fg="green")
|
||||
|
||||
|
||||
@cli.command("password", short_help="Change password")
|
||||
@click.option("--old-password", prompt=True, hide_input=True)
|
||||
@click.option("--new-password", prompt=True, hide_input=True, confirmation_prompt=True)
|
||||
def account_password(old_password, new_password):
|
||||
client = AccountClient()
|
||||
client.change_password(old_password, new_password)
|
||||
return click.secho("Password successfully changed!", fg="green")
|
||||
|
||||
|
||||
@cli.command("token", short_help="Get or regenerate Authentication Token")
|
||||
@click.option("-p", "--password", prompt=True, hide_input=True)
|
||||
@click.option("--regenerate", is_flag=True)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def account_token(password, regenerate, json_output):
|
||||
client = AccountClient()
|
||||
auth_token = client.auth_token(password, regenerate)
|
||||
if json_output:
|
||||
return click.echo(json.dumps({"status": "success", "result": auth_token}))
|
||||
return click.secho("Personal Authentication Token: %s" % auth_token, fg="green")
|
||||
|
||||
|
||||
@cli.command("forgot", short_help="Forgot password")
|
||||
@click.option("--username", prompt="Username or email")
|
||||
def account_forgot(username):
|
||||
client = AccountClient()
|
||||
client.forgot_password(username)
|
||||
return click.secho(
|
||||
"If this account is registered, we will send the "
|
||||
"further instructions to your email.",
|
||||
fg="green",
|
||||
)
|
||||
|
||||
|
||||
@cli.command("update", short_help="Update profile information")
|
||||
@click.option("--current-password", prompt=True, hide_input=True)
|
||||
@click.option("--username")
|
||||
@click.option("--email")
|
||||
@click.option("--firstname")
|
||||
@click.option("--lastname")
|
||||
def account_update(current_password, **kwargs):
|
||||
client = AccountClient()
|
||||
profile = client.get_profile()
|
||||
new_profile = profile.copy()
|
||||
if not any(kwargs.values()):
|
||||
for field in profile:
|
||||
new_profile[field] = click.prompt(
|
||||
field.replace("_", " ").capitalize(), default=profile[field]
|
||||
)
|
||||
if field == "email":
|
||||
validate_email(new_profile[field])
|
||||
if field == "username":
|
||||
validate_username(new_profile[field])
|
||||
else:
|
||||
new_profile.update({key: value for key, value in kwargs.items() if value})
|
||||
client.update_profile(new_profile, current_password)
|
||||
click.secho("Profile successfully updated!", fg="green")
|
||||
username_changed = new_profile["username"] != profile["username"]
|
||||
email_changed = new_profile["email"] != profile["email"]
|
||||
if not username_changed and not email_changed:
|
||||
return None
|
||||
try:
|
||||
client.logout()
|
||||
except AccountNotAuthorized:
|
||||
pass
|
||||
if email_changed:
|
||||
return click.secho(
|
||||
"Please check your mail to verify your new email address and re-login. ",
|
||||
fg="yellow",
|
||||
)
|
||||
return click.secho("Please re-login.", fg="yellow")
|
||||
|
||||
|
||||
@cli.command("destroy", short_help="Destroy account")
|
||||
def account_destroy():
|
||||
client = AccountClient()
|
||||
click.confirm(
|
||||
"Are you sure you want to delete the %s user account?\n"
|
||||
"Warning! All linked data will be permanently removed and can not be restored."
|
||||
% client.get_account_info().get("profile").get("username"),
|
||||
abort=True,
|
||||
)
|
||||
client.destroy_account()
|
||||
try:
|
||||
client.logout()
|
||||
except AccountNotAuthorized:
|
||||
pass
|
||||
return click.secho(
|
||||
"User account has been destroyed.",
|
||||
fg="green",
|
||||
)
|
||||
|
||||
|
||||
@cli.command("show", short_help="PlatformIO Account information")
|
||||
@click.option("--offline", is_flag=True)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def account_show(offline, json_output):
|
||||
client = AccountClient()
|
||||
info = client.get_account_info(offline)
|
||||
if json_output:
|
||||
return click.echo(json.dumps(info))
|
||||
click.echo()
|
||||
if info.get("profile"):
|
||||
print_profile(info["profile"])
|
||||
if info.get("packages"):
|
||||
print_packages(info["packages"])
|
||||
if info.get("subscriptions"):
|
||||
print_subscriptions(info["subscriptions"])
|
||||
return click.echo()
|
||||
|
||||
|
||||
def print_profile(profile):
|
||||
click.secho("Profile", fg="cyan", bold=True)
|
||||
click.echo("=" * len("Profile"))
|
||||
data = []
|
||||
if profile.get("username"):
|
||||
data.append(("Username:", profile["username"]))
|
||||
if profile.get("email"):
|
||||
data.append(("Email:", profile["email"]))
|
||||
if profile.get("firstname"):
|
||||
data.append(("First name:", profile["firstname"]))
|
||||
if profile.get("lastname"):
|
||||
data.append(("Last name:", profile["lastname"]))
|
||||
click.echo(tabulate(data, tablefmt="plain"))
|
||||
|
||||
|
||||
def print_packages(packages):
|
||||
click.echo()
|
||||
click.secho("Packages", fg="cyan")
|
||||
click.echo("=" * len("Packages"))
|
||||
for package in packages:
|
||||
click.echo()
|
||||
click.secho(package.get("name"), bold=True)
|
||||
click.echo("-" * len(package.get("name")))
|
||||
if package.get("description"):
|
||||
click.echo(package.get("description"))
|
||||
data = []
|
||||
expire = "-"
|
||||
if "subscription" in package:
|
||||
expire = datetime.datetime.strptime(
|
||||
(
|
||||
package["subscription"].get("end_at")
|
||||
or package["subscription"].get("next_bill_at")
|
||||
),
|
||||
"%Y-%m-%dT%H:%M:%SZ",
|
||||
).strftime("%Y-%m-%d")
|
||||
data.append(("Expire:", expire))
|
||||
services = []
|
||||
for key in package:
|
||||
if not key.startswith("service."):
|
||||
continue
|
||||
if isinstance(package[key], dict):
|
||||
services.append(package[key].get("title"))
|
||||
else:
|
||||
services.append(package[key])
|
||||
if services:
|
||||
data.append(("Services:", ", ".join(services)))
|
||||
click.echo(tabulate(data, tablefmt="plain"))
|
||||
|
||||
|
||||
def print_subscriptions(subscriptions):
|
||||
click.echo()
|
||||
click.secho("Subscriptions", fg="cyan")
|
||||
click.echo("=" * len("Subscriptions"))
|
||||
for subscription in subscriptions:
|
||||
click.echo()
|
||||
click.secho(subscription.get("product_name"), bold=True)
|
||||
click.echo("-" * len(subscription.get("product_name")))
|
||||
data = [("State:", subscription.get("status"))]
|
||||
begin_at = datetime.datetime.strptime(
|
||||
subscription.get("begin_at"), "%Y-%m-%dT%H:%M:%SZ"
|
||||
).strftime("%Y-%m-%d %H:%M:%S")
|
||||
data.append(("Start date:", begin_at or "-"))
|
||||
end_at = subscription.get("end_at")
|
||||
if end_at:
|
||||
end_at = datetime.datetime.strptime(
|
||||
subscription.get("end_at"), "%Y-%m-%dT%H:%M:%SZ"
|
||||
).strftime("%Y-%m-%d %H:%M:%S")
|
||||
data.append(("End date:", end_at or "-"))
|
||||
next_bill_at = subscription.get("next_bill_at")
|
||||
if next_bill_at:
|
||||
next_bill_at = datetime.datetime.strptime(
|
||||
subscription.get("next_bill_at"), "%Y-%m-%dT%H:%M:%SZ"
|
||||
).strftime("%Y-%m-%d %H:%M:%S")
|
||||
data.append(("Next payment:", next_bill_at or "-"))
|
||||
data.append(
|
||||
("Edit:", click.style(subscription.get("update_url"), fg="blue") or "-")
|
||||
)
|
||||
data.append(
|
||||
("Cancel:", click.style(subscription.get("cancel_url"), fg="blue") or "-")
|
||||
)
|
||||
click.echo(tabulate(data, tablefmt="plain"))
|
||||
@@ -13,16 +13,16 @@
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import shutil
|
||||
|
||||
import click
|
||||
from tabulate import tabulate
|
||||
|
||||
from platformio import fs
|
||||
from platformio.compat import dump_json_to_unicode
|
||||
from platformio.package.manager.platform import PlatformPackageManager
|
||||
|
||||
|
||||
@click.command("boards", short_help="Embedded board explorer")
|
||||
@click.command("boards", short_help="Board Explorer")
|
||||
@click.argument("query", required=False)
|
||||
@click.option("--installed", is_flag=True)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@@ -41,7 +41,7 @@ def cli(query, installed, json_output): # pylint: disable=R0912
|
||||
grpboards[board["platform"]] = []
|
||||
grpboards[board["platform"]].append(board)
|
||||
|
||||
terminal_width, _ = click.get_terminal_size()
|
||||
terminal_width, _ = shutil.get_terminal_size()
|
||||
for (platform, boards) in sorted(grpboards.items()):
|
||||
click.echo("")
|
||||
click.echo("Platform: ", nl=False)
|
||||
@@ -83,4 +83,4 @@ def _print_boards_json(query, installed=False):
|
||||
if query.lower() not in search_data.lower():
|
||||
continue
|
||||
result.append(board)
|
||||
click.echo(dump_json_to_unicode(result))
|
||||
click.echo(json.dumps(result))
|
||||
|
||||
@@ -12,19 +12,18 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from os import getenv, makedirs, remove
|
||||
from os.path import basename, isdir, isfile, join, realpath
|
||||
from shutil import copyfile, copytree
|
||||
from tempfile import mkdtemp
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
import click
|
||||
|
||||
from platformio import app, compat, fs
|
||||
from platformio.commands.project import project_init as cmd_project_init
|
||||
from platformio.commands.project import validate_boards
|
||||
from platformio.commands.run.command import cli as cmd_run
|
||||
from platformio import app, fs
|
||||
from platformio.exception import CIBuildEnvsEmpty
|
||||
from platformio.project.commands.init import project_init_cmd, validate_boards
|
||||
from platformio.project.config import ProjectConfig
|
||||
from platformio.run.cli import cli as cmd_run
|
||||
|
||||
|
||||
def validate_path(ctx, param, value): # pylint: disable=unused-argument
|
||||
@@ -33,25 +32,25 @@ def validate_path(ctx, param, value): # pylint: disable=unused-argument
|
||||
for i, p in enumerate(value):
|
||||
if p.startswith("~"):
|
||||
value[i] = fs.expanduser(p)
|
||||
value[i] = realpath(value[i])
|
||||
if not compat.glob_recursive(value[i]):
|
||||
value[i] = os.path.abspath(value[i])
|
||||
if not glob.glob(value[i], recursive=True):
|
||||
invalid_path = p
|
||||
break
|
||||
try:
|
||||
assert invalid_path is None
|
||||
return value
|
||||
except AssertionError:
|
||||
raise click.BadParameter("Found invalid path: %s" % invalid_path)
|
||||
except AssertionError as exc:
|
||||
raise click.BadParameter("Found invalid path: %s" % invalid_path) from exc
|
||||
|
||||
|
||||
@click.command("ci", short_help="Continuous integration")
|
||||
@click.command("ci", short_help="Continuous Integration")
|
||||
@click.argument("src", nargs=-1, callback=validate_path)
|
||||
@click.option("-l", "--lib", multiple=True, callback=validate_path, metavar="DIRECTORY")
|
||||
@click.option("--exclude", multiple=True)
|
||||
@click.option("-b", "--board", multiple=True, metavar="ID", callback=validate_boards)
|
||||
@click.option(
|
||||
"--build-dir",
|
||||
default=mkdtemp,
|
||||
default=tempfile.mkdtemp,
|
||||
type=click.Path(file_okay=False, dir_okay=True, writable=True, resolve_path=True),
|
||||
)
|
||||
@click.option("--keep-build-dir", is_flag=True)
|
||||
@@ -63,6 +62,7 @@ def validate_path(ctx, param, value): # pylint: disable=unused-argument
|
||||
),
|
||||
)
|
||||
@click.option("-O", "--project-option", multiple=True)
|
||||
@click.option("-e", "--environment", "environments", multiple=True)
|
||||
@click.option("-v", "--verbose", is_flag=True)
|
||||
@click.pass_context
|
||||
def cli( # pylint: disable=too-many-arguments, too-many-branches
|
||||
@@ -75,31 +75,31 @@ def cli( # pylint: disable=too-many-arguments, too-many-branches
|
||||
keep_build_dir,
|
||||
project_conf,
|
||||
project_option,
|
||||
environments,
|
||||
verbose,
|
||||
):
|
||||
|
||||
if not src and getenv("PLATFORMIO_CI_SRC"):
|
||||
src = validate_path(ctx, None, getenv("PLATFORMIO_CI_SRC").split(":"))
|
||||
if not src and os.getenv("PLATFORMIO_CI_SRC"):
|
||||
src = validate_path(ctx, None, os.getenv("PLATFORMIO_CI_SRC").split(":"))
|
||||
if not src:
|
||||
raise click.BadParameter("Missing argument 'src'")
|
||||
|
||||
try:
|
||||
app.set_session_var("force_option", True)
|
||||
|
||||
if not keep_build_dir and isdir(build_dir):
|
||||
if not keep_build_dir and os.path.isdir(build_dir):
|
||||
fs.rmtree(build_dir)
|
||||
if not isdir(build_dir):
|
||||
makedirs(build_dir)
|
||||
if not os.path.isdir(build_dir):
|
||||
os.makedirs(build_dir)
|
||||
|
||||
for dir_name, patterns in dict(lib=lib, src=src).items():
|
||||
if not patterns:
|
||||
continue
|
||||
contents = []
|
||||
for p in patterns:
|
||||
contents += compat.glob_recursive(p)
|
||||
_copy_contents(join(build_dir, dir_name), contents)
|
||||
contents += glob.glob(p, recursive=True)
|
||||
_copy_contents(os.path.join(build_dir, dir_name), contents)
|
||||
|
||||
if project_conf and isfile(project_conf):
|
||||
if project_conf and os.path.isfile(project_conf):
|
||||
_copy_project_conf(build_dir, project_conf)
|
||||
elif not board:
|
||||
raise CIBuildEnvsEmpty()
|
||||
@@ -109,65 +109,70 @@ def cli( # pylint: disable=too-many-arguments, too-many-branches
|
||||
|
||||
# initialise project
|
||||
ctx.invoke(
|
||||
cmd_project_init,
|
||||
project_init_cmd,
|
||||
project_dir=build_dir,
|
||||
board=board,
|
||||
project_option=project_option,
|
||||
)
|
||||
|
||||
# process project
|
||||
ctx.invoke(cmd_run, project_dir=build_dir, verbose=verbose)
|
||||
ctx.invoke(
|
||||
cmd_run, project_dir=build_dir, environment=environments, verbose=verbose
|
||||
)
|
||||
finally:
|
||||
if not keep_build_dir:
|
||||
fs.rmtree(build_dir)
|
||||
|
||||
|
||||
def _copy_contents(dst_dir, contents):
|
||||
def _copy_contents(dst_dir, contents): # pylint: disable=too-many-branches
|
||||
items = {"dirs": set(), "files": set()}
|
||||
|
||||
for path in contents:
|
||||
if isdir(path):
|
||||
if os.path.isdir(path):
|
||||
items["dirs"].add(path)
|
||||
elif isfile(path):
|
||||
elif os.path.isfile(path):
|
||||
items["files"].add(path)
|
||||
|
||||
dst_dir_name = basename(dst_dir)
|
||||
dst_dir_name = os.path.basename(dst_dir)
|
||||
|
||||
if dst_dir_name == "src" and len(items["dirs"]) == 1:
|
||||
copytree(list(items["dirs"]).pop(), dst_dir, symlinks=True)
|
||||
if not os.path.isdir(dst_dir):
|
||||
shutil.copytree(list(items["dirs"]).pop(), dst_dir, symlinks=True)
|
||||
else:
|
||||
if not isdir(dst_dir):
|
||||
makedirs(dst_dir)
|
||||
if not os.path.isdir(dst_dir):
|
||||
os.makedirs(dst_dir)
|
||||
for d in items["dirs"]:
|
||||
copytree(d, join(dst_dir, basename(d)), symlinks=True)
|
||||
src_dst_dir = os.path.join(dst_dir, os.path.basename(d))
|
||||
if not os.path.isdir(src_dst_dir):
|
||||
shutil.copytree(d, src_dst_dir, symlinks=True)
|
||||
|
||||
if not items["files"]:
|
||||
return
|
||||
|
||||
if dst_dir_name == "lib":
|
||||
dst_dir = join(dst_dir, mkdtemp(dir=dst_dir))
|
||||
dst_dir = os.path.join(dst_dir, tempfile.mkdtemp(dir=dst_dir))
|
||||
|
||||
for f in items["files"]:
|
||||
dst_file = join(dst_dir, basename(f))
|
||||
dst_file = os.path.join(dst_dir, os.path.basename(f))
|
||||
if f == dst_file:
|
||||
continue
|
||||
copyfile(f, dst_file)
|
||||
shutil.copyfile(f, dst_file)
|
||||
|
||||
|
||||
def _exclude_contents(dst_dir, patterns):
|
||||
contents = []
|
||||
for p in patterns:
|
||||
contents += compat.glob_recursive(join(compat.glob_escape(dst_dir), p))
|
||||
contents += glob.glob(os.path.join(glob.escape(dst_dir), p), recursive=True)
|
||||
for path in contents:
|
||||
path = realpath(path)
|
||||
if isdir(path):
|
||||
path = os.path.abspath(path)
|
||||
if os.path.isdir(path):
|
||||
fs.rmtree(path)
|
||||
elif isfile(path):
|
||||
remove(path)
|
||||
elif os.path.isfile(path):
|
||||
os.remove(path)
|
||||
|
||||
|
||||
def _copy_project_conf(build_dir, project_conf):
|
||||
config = ProjectConfig(project_conf, parse_extra=False)
|
||||
if config.has_section("platformio"):
|
||||
config.remove_section("platformio")
|
||||
config.save(join(build_dir, "platformio.ini"))
|
||||
config.save(os.path.join(build_dir, "platformio.ini"))
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# pylint: disable=too-many-arguments, too-many-statements
|
||||
# pylint: disable=too-many-locals, too-many-branches
|
||||
|
||||
import os
|
||||
import signal
|
||||
from os.path import isfile
|
||||
|
||||
import click
|
||||
|
||||
from platformio import app, exception, fs, proc
|
||||
from platformio.commands.debug import helpers
|
||||
from platformio.commands.debug.exception import DebugInvalidOptionsError
|
||||
from platformio.commands.platform import platform_install as cmd_platform_install
|
||||
from platformio.package.manager.core import inject_contrib_pysite
|
||||
from platformio.platform.exception import UnknownPlatform
|
||||
from platformio.platform.factory import PlatformFactory
|
||||
from platformio.project.config import ProjectConfig
|
||||
from platformio.project.exception import ProjectEnvsNotAvailableError
|
||||
from platformio.project.helpers import is_platformio_project, load_project_ide_data
|
||||
|
||||
|
||||
@click.command(
|
||||
"debug",
|
||||
context_settings=dict(ignore_unknown_options=True),
|
||||
short_help="Unified debugger",
|
||||
)
|
||||
@click.option(
|
||||
"-d",
|
||||
"--project-dir",
|
||||
default=os.getcwd,
|
||||
type=click.Path(
|
||||
exists=True, file_okay=False, dir_okay=True, writable=True, resolve_path=True
|
||||
),
|
||||
)
|
||||
@click.option(
|
||||
"-c",
|
||||
"--project-conf",
|
||||
type=click.Path(
|
||||
exists=True, file_okay=True, dir_okay=False, readable=True, resolve_path=True
|
||||
),
|
||||
)
|
||||
@click.option("--environment", "-e", metavar="<environment>")
|
||||
@click.option("--verbose", "-v", is_flag=True)
|
||||
@click.option("--interface", type=click.Choice(["gdb"]))
|
||||
@click.argument("__unprocessed", nargs=-1, type=click.UNPROCESSED)
|
||||
@click.pass_context
|
||||
def cli(ctx, project_dir, project_conf, environment, verbose, interface, __unprocessed):
|
||||
app.set_session_var("custom_project_conf", project_conf)
|
||||
|
||||
# use env variables from Eclipse or CLion
|
||||
for sysenv in ("CWD", "PWD", "PLATFORMIO_PROJECT_DIR"):
|
||||
if is_platformio_project(project_dir):
|
||||
break
|
||||
if os.getenv(sysenv):
|
||||
project_dir = os.getenv(sysenv)
|
||||
|
||||
with fs.cd(project_dir):
|
||||
config = ProjectConfig.get_instance(project_conf)
|
||||
config.validate(envs=[environment] if environment else None)
|
||||
|
||||
env_name = environment or helpers.get_default_debug_env(config)
|
||||
env_options = config.items(env=env_name, as_dict=True)
|
||||
if not set(env_options.keys()) >= set(["platform", "board"]):
|
||||
raise ProjectEnvsNotAvailableError()
|
||||
|
||||
try:
|
||||
platform = PlatformFactory.new(env_options["platform"])
|
||||
except UnknownPlatform:
|
||||
ctx.invoke(
|
||||
cmd_platform_install,
|
||||
platforms=[env_options["platform"]],
|
||||
skip_default_package=True,
|
||||
)
|
||||
platform = PlatformFactory.new(env_options["platform"])
|
||||
|
||||
debug_options = helpers.configure_initial_debug_options(platform, env_options)
|
||||
assert debug_options
|
||||
|
||||
if not interface:
|
||||
return helpers.predebug_project(ctx, project_dir, env_name, False, verbose)
|
||||
|
||||
ide_data = load_project_ide_data(project_dir, env_name)
|
||||
if not ide_data:
|
||||
raise DebugInvalidOptionsError("Could not load a build configuration")
|
||||
|
||||
if "--version" in __unprocessed:
|
||||
result = proc.exec_command([ide_data["gdb_path"], "--version"])
|
||||
if result["returncode"] == 0:
|
||||
return click.echo(result["out"])
|
||||
raise exception.PlatformioException("\n".join([result["out"], result["err"]]))
|
||||
|
||||
try:
|
||||
fs.ensure_udev_rules()
|
||||
except exception.InvalidUdevRules as e:
|
||||
click.echo(
|
||||
helpers.escape_gdbmi_stream("~", str(e) + "\n")
|
||||
if helpers.is_gdbmi_mode()
|
||||
else str(e) + "\n",
|
||||
nl=False,
|
||||
)
|
||||
|
||||
try:
|
||||
debug_options = platform.configure_debug_options(debug_options, ide_data)
|
||||
except NotImplementedError:
|
||||
# legacy for ESP32 dev-platform <=2.0.0
|
||||
debug_options["load_cmds"] = helpers.configure_esp32_load_cmds(
|
||||
debug_options, ide_data
|
||||
)
|
||||
|
||||
rebuild_prog = False
|
||||
preload = debug_options["load_cmds"] == ["preload"]
|
||||
load_mode = debug_options["load_mode"]
|
||||
if load_mode == "always":
|
||||
rebuild_prog = preload or not helpers.has_debug_symbols(ide_data["prog_path"])
|
||||
elif load_mode == "modified":
|
||||
rebuild_prog = helpers.is_prog_obsolete(
|
||||
ide_data["prog_path"]
|
||||
) or not helpers.has_debug_symbols(ide_data["prog_path"])
|
||||
else:
|
||||
rebuild_prog = not isfile(ide_data["prog_path"])
|
||||
|
||||
if preload or (not rebuild_prog and load_mode != "always"):
|
||||
# don't load firmware through debug server
|
||||
debug_options["load_cmds"] = []
|
||||
|
||||
if rebuild_prog:
|
||||
if helpers.is_gdbmi_mode():
|
||||
click.echo(
|
||||
helpers.escape_gdbmi_stream(
|
||||
"~", "Preparing firmware for debugging...\n"
|
||||
),
|
||||
nl=False,
|
||||
)
|
||||
stream = helpers.GDBMIConsoleStream()
|
||||
with proc.capture_std_streams(stream):
|
||||
helpers.predebug_project(ctx, project_dir, env_name, preload, verbose)
|
||||
stream.close()
|
||||
else:
|
||||
click.echo("Preparing firmware for debugging...")
|
||||
helpers.predebug_project(ctx, project_dir, env_name, preload, verbose)
|
||||
|
||||
# save SHA sum of newly created prog
|
||||
if load_mode == "modified":
|
||||
helpers.is_prog_obsolete(ide_data["prog_path"])
|
||||
|
||||
if not isfile(ide_data["prog_path"]):
|
||||
raise DebugInvalidOptionsError("Program/firmware is missed")
|
||||
|
||||
# run debugging client
|
||||
inject_contrib_pysite()
|
||||
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from platformio.commands.debug.process.client import GDBClient, reactor
|
||||
|
||||
client = GDBClient(project_dir, __unprocessed, debug_options, env_options)
|
||||
client.spawn(ide_data["gdb_path"], ide_data["prog_path"])
|
||||
|
||||
signal.signal(signal.SIGINT, lambda *args, **kwargs: None)
|
||||
reactor.run()
|
||||
|
||||
return True
|
||||
@@ -1,303 +0,0 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from fnmatch import fnmatch
|
||||
from hashlib import sha1
|
||||
from io import BytesIO
|
||||
from os.path import isfile
|
||||
|
||||
from platformio import fs, util
|
||||
from platformio.commands import PlatformioCLI
|
||||
from platformio.commands.debug.exception import DebugInvalidOptionsError
|
||||
from platformio.commands.run.command import cli as cmd_run
|
||||
from platformio.compat import is_bytes
|
||||
from platformio.project.config import ProjectConfig
|
||||
from platformio.project.options import ProjectOptions
|
||||
|
||||
|
||||
class GDBMIConsoleStream(BytesIO): # pylint: disable=too-few-public-methods
|
||||
|
||||
STDOUT = sys.stdout
|
||||
|
||||
def write(self, text):
|
||||
self.STDOUT.write(escape_gdbmi_stream("~", text))
|
||||
self.STDOUT.flush()
|
||||
|
||||
|
||||
def is_gdbmi_mode():
|
||||
return "--interpreter" in " ".join(PlatformioCLI.leftover_args)
|
||||
|
||||
|
||||
def escape_gdbmi_stream(prefix, stream):
|
||||
bytes_stream = False
|
||||
if is_bytes(stream):
|
||||
bytes_stream = True
|
||||
stream = stream.decode()
|
||||
|
||||
if not stream:
|
||||
return b"" if bytes_stream else ""
|
||||
|
||||
ends_nl = stream.endswith("\n")
|
||||
stream = re.sub(r"\\+", "\\\\\\\\", stream)
|
||||
stream = stream.replace('"', '\\"')
|
||||
stream = stream.replace("\n", "\\n")
|
||||
stream = '%s"%s"' % (prefix, stream)
|
||||
if ends_nl:
|
||||
stream += "\n"
|
||||
|
||||
return stream.encode() if bytes_stream else stream
|
||||
|
||||
|
||||
def get_default_debug_env(config):
|
||||
default_envs = config.default_envs()
|
||||
all_envs = config.envs()
|
||||
for env in default_envs:
|
||||
if config.get("env:" + env, "build_type") == "debug":
|
||||
return env
|
||||
for env in all_envs:
|
||||
if config.get("env:" + env, "build_type") == "debug":
|
||||
return env
|
||||
return default_envs[0] if default_envs else all_envs[0]
|
||||
|
||||
|
||||
def predebug_project(ctx, project_dir, env_name, preload, verbose):
|
||||
ctx.invoke(
|
||||
cmd_run,
|
||||
project_dir=project_dir,
|
||||
environment=[env_name],
|
||||
target=["debug"] + (["upload"] if preload else []),
|
||||
verbose=verbose,
|
||||
)
|
||||
if preload:
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
def configure_initial_debug_options(platform, env_options):
|
||||
def _cleanup_cmds(items):
|
||||
items = ProjectConfig.parse_multi_values(items)
|
||||
return ["$LOAD_CMDS" if item == "$LOAD_CMD" else item for item in items]
|
||||
|
||||
board_config = platform.board_config(env_options["board"])
|
||||
tool_name = board_config.get_debug_tool_name(env_options.get("debug_tool"))
|
||||
tool_settings = board_config.get("debug", {}).get("tools", {}).get(tool_name, {})
|
||||
server_options = None
|
||||
|
||||
# specific server per a system
|
||||
if isinstance(tool_settings.get("server", {}), list):
|
||||
for item in tool_settings["server"][:]:
|
||||
tool_settings["server"] = item
|
||||
if util.get_systype() in item.get("system", []):
|
||||
break
|
||||
|
||||
# user overwrites debug server
|
||||
if env_options.get("debug_server"):
|
||||
server_options = {
|
||||
"cwd": None,
|
||||
"executable": None,
|
||||
"arguments": env_options.get("debug_server"),
|
||||
}
|
||||
server_options["executable"] = server_options["arguments"][0]
|
||||
server_options["arguments"] = server_options["arguments"][1:]
|
||||
elif "server" in tool_settings:
|
||||
server_options = tool_settings["server"]
|
||||
server_package = server_options.get("package")
|
||||
server_package_dir = (
|
||||
platform.get_package_dir(server_package) if server_package else None
|
||||
)
|
||||
if server_package and not server_package_dir:
|
||||
platform.install_packages(
|
||||
with_packages=[server_package], skip_default_package=True, silent=True
|
||||
)
|
||||
server_package_dir = platform.get_package_dir(server_package)
|
||||
server_options.update(
|
||||
dict(
|
||||
cwd=server_package_dir if server_package else None,
|
||||
executable=server_options.get("executable"),
|
||||
arguments=[
|
||||
a.replace("$PACKAGE_DIR", server_package_dir)
|
||||
if server_package_dir
|
||||
else a
|
||||
for a in server_options.get("arguments", [])
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
extra_cmds = _cleanup_cmds(env_options.get("debug_extra_cmds"))
|
||||
extra_cmds.extend(_cleanup_cmds(tool_settings.get("extra_cmds")))
|
||||
result = dict(
|
||||
tool=tool_name,
|
||||
upload_protocol=env_options.get(
|
||||
"upload_protocol", board_config.get("upload", {}).get("protocol")
|
||||
),
|
||||
load_cmds=_cleanup_cmds(
|
||||
env_options.get(
|
||||
"debug_load_cmds",
|
||||
tool_settings.get(
|
||||
"load_cmds",
|
||||
tool_settings.get(
|
||||
"load_cmd", ProjectOptions["env.debug_load_cmds"].default
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
load_mode=env_options.get(
|
||||
"debug_load_mode",
|
||||
tool_settings.get(
|
||||
"load_mode", ProjectOptions["env.debug_load_mode"].default
|
||||
),
|
||||
),
|
||||
init_break=env_options.get(
|
||||
"debug_init_break",
|
||||
tool_settings.get(
|
||||
"init_break", ProjectOptions["env.debug_init_break"].default
|
||||
),
|
||||
),
|
||||
init_cmds=_cleanup_cmds(
|
||||
env_options.get("debug_init_cmds", tool_settings.get("init_cmds"))
|
||||
),
|
||||
extra_cmds=extra_cmds,
|
||||
require_debug_port=tool_settings.get("require_debug_port", False),
|
||||
port=reveal_debug_port(
|
||||
env_options.get("debug_port", tool_settings.get("port")),
|
||||
tool_name,
|
||||
tool_settings,
|
||||
),
|
||||
speed=env_options.get("debug_speed", tool_settings.get("speed")),
|
||||
server=server_options,
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def configure_esp32_load_cmds(debug_options, configuration):
|
||||
"""
|
||||
DEPRECATED: Moved to ESP32 dev-platform
|
||||
See platform.py::configure_debug_options
|
||||
"""
|
||||
flash_images = configuration.get("extra", {}).get("flash_images")
|
||||
ignore_conds = [
|
||||
debug_options["load_cmds"] != ["load"],
|
||||
"xtensa-esp32" not in configuration.get("cc_path", ""),
|
||||
not flash_images,
|
||||
not all([isfile(item["path"]) for item in flash_images]),
|
||||
]
|
||||
if any(ignore_conds):
|
||||
return debug_options["load_cmds"]
|
||||
|
||||
mon_cmds = [
|
||||
'monitor program_esp32 "{{{path}}}" {offset} verify'.format(
|
||||
path=fs.to_unix_path(item["path"]), offset=item["offset"]
|
||||
)
|
||||
for item in flash_images
|
||||
]
|
||||
mon_cmds.append(
|
||||
'monitor program_esp32 "{%s.bin}" 0x10000 verify'
|
||||
% fs.to_unix_path(configuration["prog_path"][:-4])
|
||||
)
|
||||
return mon_cmds
|
||||
|
||||
|
||||
def has_debug_symbols(prog_path):
|
||||
if not isfile(prog_path):
|
||||
return False
|
||||
matched = {
|
||||
b".debug_info": False,
|
||||
b".debug_abbrev": False,
|
||||
b" -Og": False,
|
||||
b" -g": False,
|
||||
b"__PLATFORMIO_BUILD_DEBUG__": False,
|
||||
}
|
||||
with open(prog_path, "rb") as fp:
|
||||
last_data = b""
|
||||
while True:
|
||||
data = fp.read(1024)
|
||||
if not data:
|
||||
break
|
||||
for pattern, found in matched.items():
|
||||
if found:
|
||||
continue
|
||||
if pattern in last_data + data:
|
||||
matched[pattern] = True
|
||||
last_data = data
|
||||
return all(matched.values())
|
||||
|
||||
|
||||
def is_prog_obsolete(prog_path):
|
||||
prog_hash_path = prog_path + ".sha1"
|
||||
if not isfile(prog_path):
|
||||
return True
|
||||
shasum = sha1()
|
||||
with open(prog_path, "rb") as fp:
|
||||
while True:
|
||||
data = fp.read(1024)
|
||||
if not data:
|
||||
break
|
||||
shasum.update(data)
|
||||
new_digest = shasum.hexdigest()
|
||||
old_digest = None
|
||||
if isfile(prog_hash_path):
|
||||
with open(prog_hash_path) as fp:
|
||||
old_digest = fp.read()
|
||||
if new_digest == old_digest:
|
||||
return False
|
||||
with open(prog_hash_path, "w") as fp:
|
||||
fp.write(new_digest)
|
||||
return True
|
||||
|
||||
|
||||
def reveal_debug_port(env_debug_port, tool_name, tool_settings):
|
||||
def _get_pattern():
|
||||
if not env_debug_port:
|
||||
return None
|
||||
if set(["*", "?", "[", "]"]) & set(env_debug_port):
|
||||
return env_debug_port
|
||||
return None
|
||||
|
||||
def _is_match_pattern(port):
|
||||
pattern = _get_pattern()
|
||||
if not pattern:
|
||||
return True
|
||||
return fnmatch(port, pattern)
|
||||
|
||||
def _look_for_serial_port(hwids):
|
||||
for item in util.get_serialports(filter_hwid=True):
|
||||
if not _is_match_pattern(item["port"]):
|
||||
continue
|
||||
port = item["port"]
|
||||
if tool_name.startswith("blackmagic"):
|
||||
if (
|
||||
"windows" in util.get_systype()
|
||||
and port.startswith("COM")
|
||||
and len(port) > 4
|
||||
):
|
||||
port = "\\\\.\\%s" % port
|
||||
if "GDB" in item["description"]:
|
||||
return port
|
||||
for hwid in hwids:
|
||||
hwid_str = ("%s:%s" % (hwid[0], hwid[1])).replace("0x", "")
|
||||
if hwid_str in item["hwid"]:
|
||||
return port
|
||||
return None
|
||||
|
||||
if env_debug_port and not _get_pattern():
|
||||
return env_debug_port
|
||||
if not tool_settings.get("require_debug_port"):
|
||||
return None
|
||||
|
||||
debug_port = _look_for_serial_port(tool_settings.get("hwids", []))
|
||||
if not debug_port:
|
||||
raise DebugInvalidOptionsError("Please specify `debug_port` for environment")
|
||||
return debug_port
|
||||
@@ -1,161 +0,0 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
GDB_DEFAULT_INIT_CONFIG = """
|
||||
define pio_reset_halt_target
|
||||
monitor reset halt
|
||||
end
|
||||
|
||||
define pio_reset_run_target
|
||||
monitor reset
|
||||
end
|
||||
|
||||
target extended-remote $DEBUG_PORT
|
||||
monitor init
|
||||
$LOAD_CMDS
|
||||
pio_reset_halt_target
|
||||
$INIT_BREAK
|
||||
"""
|
||||
|
||||
GDB_STUTIL_INIT_CONFIG = """
|
||||
define pio_reset_halt_target
|
||||
monitor reset
|
||||
monitor halt
|
||||
end
|
||||
|
||||
define pio_reset_run_target
|
||||
monitor reset
|
||||
end
|
||||
|
||||
target extended-remote $DEBUG_PORT
|
||||
$LOAD_CMDS
|
||||
pio_reset_halt_target
|
||||
$INIT_BREAK
|
||||
"""
|
||||
|
||||
GDB_JLINK_INIT_CONFIG = """
|
||||
define pio_reset_halt_target
|
||||
monitor reset
|
||||
monitor halt
|
||||
end
|
||||
|
||||
define pio_reset_run_target
|
||||
monitor clrbp
|
||||
monitor reset
|
||||
monitor go
|
||||
end
|
||||
|
||||
target extended-remote $DEBUG_PORT
|
||||
monitor clrbp
|
||||
monitor speed auto
|
||||
pio_reset_halt_target
|
||||
$LOAD_CMDS
|
||||
$INIT_BREAK
|
||||
"""
|
||||
|
||||
GDB_BLACKMAGIC_INIT_CONFIG = """
|
||||
define pio_reset_halt_target
|
||||
set language c
|
||||
set *0xE000ED0C = 0x05FA0004
|
||||
set $busy = (*0xE000ED0C & 0x4)
|
||||
while ($busy)
|
||||
set $busy = (*0xE000ED0C & 0x4)
|
||||
end
|
||||
set language auto
|
||||
end
|
||||
|
||||
define pio_reset_run_target
|
||||
pio_reset_halt_target
|
||||
end
|
||||
|
||||
target extended-remote $DEBUG_PORT
|
||||
monitor swdp_scan
|
||||
attach 1
|
||||
set mem inaccessible-by-default off
|
||||
$LOAD_CMDS
|
||||
$INIT_BREAK
|
||||
|
||||
set language c
|
||||
set *0xE000ED0C = 0x05FA0004
|
||||
set $busy = (*0xE000ED0C & 0x4)
|
||||
while ($busy)
|
||||
set $busy = (*0xE000ED0C & 0x4)
|
||||
end
|
||||
set language auto
|
||||
"""
|
||||
|
||||
GDB_MSPDEBUG_INIT_CONFIG = """
|
||||
define pio_reset_halt_target
|
||||
end
|
||||
|
||||
define pio_reset_run_target
|
||||
end
|
||||
|
||||
target extended-remote $DEBUG_PORT
|
||||
monitor erase
|
||||
$LOAD_CMDS
|
||||
pio_reset_halt_target
|
||||
$INIT_BREAK
|
||||
"""
|
||||
|
||||
GDB_QEMU_INIT_CONFIG = """
|
||||
define pio_reset_halt_target
|
||||
monitor system_reset
|
||||
end
|
||||
|
||||
define pio_reset_run_target
|
||||
monitor system_reset
|
||||
end
|
||||
|
||||
target extended-remote $DEBUG_PORT
|
||||
$LOAD_CMDS
|
||||
pio_reset_halt_target
|
||||
$INIT_BREAK
|
||||
"""
|
||||
|
||||
GDB_RENODE_INIT_CONFIG = """
|
||||
define pio_reset_halt_target
|
||||
monitor machine Reset
|
||||
$LOAD_CMDS
|
||||
monitor start
|
||||
end
|
||||
|
||||
define pio_reset_run_target
|
||||
pio_reset_halt_target
|
||||
end
|
||||
|
||||
target extended-remote $DEBUG_PORT
|
||||
$LOAD_CMDS
|
||||
$INIT_BREAK
|
||||
monitor start
|
||||
"""
|
||||
|
||||
|
||||
TOOL_TO_CONFIG = {
|
||||
"jlink": GDB_JLINK_INIT_CONFIG,
|
||||
"mspdebug": GDB_MSPDEBUG_INIT_CONFIG,
|
||||
"qemu": GDB_QEMU_INIT_CONFIG,
|
||||
"blackmagic": GDB_BLACKMAGIC_INIT_CONFIG,
|
||||
"renode": GDB_RENODE_INIT_CONFIG,
|
||||
}
|
||||
|
||||
|
||||
def get_gdb_init_config(debug_options):
|
||||
tool = debug_options.get("tool")
|
||||
if tool and tool in TOOL_TO_CONFIG:
|
||||
return TOOL_TO_CONFIG[tool]
|
||||
server_exe = (debug_options.get("server") or {}).get("executable", "").lower()
|
||||
if "st-util" in server_exe:
|
||||
return GDB_STUTIL_INIT_CONFIG
|
||||
return GDB_DEFAULT_INIT_CONFIG
|
||||
@@ -1,93 +0,0 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import signal
|
||||
import time
|
||||
|
||||
import click
|
||||
from twisted.internet import protocol # pylint: disable=import-error
|
||||
|
||||
from platformio import fs
|
||||
from platformio.compat import string_types
|
||||
from platformio.proc import get_pythonexe_path
|
||||
from platformio.project.helpers import get_project_core_dir
|
||||
|
||||
|
||||
class BaseProcess(protocol.ProcessProtocol, object):
|
||||
|
||||
STDOUT_CHUNK_SIZE = 2048
|
||||
LOG_FILE = None
|
||||
|
||||
COMMON_PATTERNS = {
|
||||
"PLATFORMIO_HOME_DIR": get_project_core_dir(),
|
||||
"PLATFORMIO_CORE_DIR": get_project_core_dir(),
|
||||
"PYTHONEXE": get_pythonexe_path(),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._last_activity = 0
|
||||
|
||||
def apply_patterns(self, source, patterns=None):
|
||||
_patterns = self.COMMON_PATTERNS.copy()
|
||||
_patterns.update(patterns or {})
|
||||
|
||||
for key, value in _patterns.items():
|
||||
if key.endswith(("_DIR", "_PATH")):
|
||||
_patterns[key] = fs.to_unix_path(value)
|
||||
|
||||
def _replace(text):
|
||||
for key, value in _patterns.items():
|
||||
pattern = "$%s" % key
|
||||
text = text.replace(pattern, value or "")
|
||||
return text
|
||||
|
||||
if isinstance(source, string_types):
|
||||
source = _replace(source)
|
||||
elif isinstance(source, (list, dict)):
|
||||
items = enumerate(source) if isinstance(source, list) else source.items()
|
||||
for key, value in items:
|
||||
if isinstance(value, string_types):
|
||||
source[key] = _replace(value)
|
||||
elif isinstance(value, (list, dict)):
|
||||
source[key] = self.apply_patterns(value, patterns)
|
||||
|
||||
return source
|
||||
|
||||
def onStdInData(self, data):
|
||||
self._last_activity = time.time()
|
||||
if self.LOG_FILE:
|
||||
with open(self.LOG_FILE, "ab") as fp:
|
||||
fp.write(data)
|
||||
|
||||
def outReceived(self, data):
|
||||
self._last_activity = time.time()
|
||||
if self.LOG_FILE:
|
||||
with open(self.LOG_FILE, "ab") as fp:
|
||||
fp.write(data)
|
||||
while data:
|
||||
chunk = data[: self.STDOUT_CHUNK_SIZE]
|
||||
click.echo(chunk, nl=False)
|
||||
data = data[self.STDOUT_CHUNK_SIZE :]
|
||||
|
||||
def errReceived(self, data):
|
||||
self._last_activity = time.time()
|
||||
if self.LOG_FILE:
|
||||
with open(self.LOG_FILE, "ab") as fp:
|
||||
fp.write(data)
|
||||
click.echo(data, nl=False, err=True)
|
||||
|
||||
def processEnded(self, _):
|
||||
self._last_activity = time.time()
|
||||
# Allow terminating via SIGINT/CTRL+C
|
||||
signal.signal(signal.SIGINT, signal.default_int_handler)
|
||||
@@ -1,280 +0,0 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import time
|
||||
from hashlib import sha1
|
||||
from os.path import basename, dirname, isdir, join, realpath, splitext
|
||||
from tempfile import mkdtemp
|
||||
|
||||
from twisted.internet import defer # pylint: disable=import-error
|
||||
from twisted.internet import protocol # pylint: disable=import-error
|
||||
from twisted.internet import reactor # pylint: disable=import-error
|
||||
from twisted.internet import stdio # pylint: disable=import-error
|
||||
from twisted.internet import task # pylint: disable=import-error
|
||||
|
||||
from platformio import fs, proc, telemetry, util
|
||||
from platformio.cache import ContentCache
|
||||
from platformio.commands.debug import helpers
|
||||
from platformio.commands.debug.exception import DebugInvalidOptionsError
|
||||
from platformio.commands.debug.initcfgs import get_gdb_init_config
|
||||
from platformio.commands.debug.process.base import BaseProcess
|
||||
from platformio.commands.debug.process.server import DebugServer
|
||||
from platformio.compat import hashlib_encode_data, is_bytes
|
||||
from platformio.project.helpers import get_project_cache_dir
|
||||
|
||||
|
||||
class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes
|
||||
|
||||
PIO_SRC_NAME = ".pioinit"
|
||||
INIT_COMPLETED_BANNER = "PlatformIO: Initialization completed"
|
||||
|
||||
def __init__(self, project_dir, args, debug_options, env_options):
|
||||
super(GDBClient, self).__init__()
|
||||
self.project_dir = project_dir
|
||||
self.args = list(args)
|
||||
self.debug_options = debug_options
|
||||
self.env_options = env_options
|
||||
|
||||
self._debug_server = DebugServer(debug_options, env_options)
|
||||
self._session_id = None
|
||||
|
||||
if not isdir(get_project_cache_dir()):
|
||||
os.makedirs(get_project_cache_dir())
|
||||
self._gdbsrc_dir = mkdtemp(dir=get_project_cache_dir(), prefix=".piodebug-")
|
||||
|
||||
self._target_is_run = False
|
||||
self._auto_continue_timer = None
|
||||
self._errors_buffer = b""
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def spawn(self, gdb_path, prog_path):
|
||||
session_hash = gdb_path + prog_path
|
||||
self._session_id = sha1(hashlib_encode_data(session_hash)).hexdigest()
|
||||
self._kill_previous_session()
|
||||
|
||||
patterns = {
|
||||
"PROJECT_DIR": self.project_dir,
|
||||
"PROG_PATH": prog_path,
|
||||
"PROG_DIR": dirname(prog_path),
|
||||
"PROG_NAME": basename(splitext(prog_path)[0]),
|
||||
"DEBUG_PORT": self.debug_options["port"],
|
||||
"UPLOAD_PROTOCOL": self.debug_options["upload_protocol"],
|
||||
"INIT_BREAK": self.debug_options["init_break"] or "",
|
||||
"LOAD_CMDS": "\n".join(self.debug_options["load_cmds"] or []),
|
||||
}
|
||||
|
||||
yield self._debug_server.spawn(patterns)
|
||||
if not patterns["DEBUG_PORT"]:
|
||||
patterns["DEBUG_PORT"] = self._debug_server.get_debug_port()
|
||||
|
||||
self.generate_pioinit(self._gdbsrc_dir, patterns)
|
||||
|
||||
# start GDB client
|
||||
args = [
|
||||
"piogdb",
|
||||
"-q",
|
||||
"--directory",
|
||||
self._gdbsrc_dir,
|
||||
"--directory",
|
||||
self.project_dir,
|
||||
"-l",
|
||||
"10",
|
||||
]
|
||||
args.extend(self.args)
|
||||
if not gdb_path:
|
||||
raise DebugInvalidOptionsError("GDB client is not configured")
|
||||
gdb_data_dir = self._get_data_dir(gdb_path)
|
||||
if gdb_data_dir:
|
||||
args.extend(["--data-directory", gdb_data_dir])
|
||||
args.append(patterns["PROG_PATH"])
|
||||
|
||||
transport = reactor.spawnProcess(
|
||||
self, gdb_path, args, path=self.project_dir, env=os.environ
|
||||
)
|
||||
defer.returnValue(transport)
|
||||
|
||||
@staticmethod
|
||||
def _get_data_dir(gdb_path):
|
||||
if "msp430" in gdb_path:
|
||||
return None
|
||||
gdb_data_dir = realpath(join(dirname(gdb_path), "..", "share", "gdb"))
|
||||
return gdb_data_dir if isdir(gdb_data_dir) else None
|
||||
|
||||
def generate_pioinit(self, dst_dir, patterns):
|
||||
# default GDB init commands depending on debug tool
|
||||
commands = get_gdb_init_config(self.debug_options).split("\n")
|
||||
|
||||
if self.debug_options["init_cmds"]:
|
||||
commands = self.debug_options["init_cmds"]
|
||||
commands.extend(self.debug_options["extra_cmds"])
|
||||
|
||||
if not any("define pio_reset_run_target" in cmd for cmd in commands):
|
||||
commands = [
|
||||
"define pio_reset_run_target",
|
||||
" echo Warning! Undefined pio_reset_run_target command\\n",
|
||||
" monitor reset",
|
||||
"end",
|
||||
] + commands
|
||||
if not any("define pio_reset_halt_target" in cmd for cmd in commands):
|
||||
commands = [
|
||||
"define pio_reset_halt_target",
|
||||
" echo Warning! Undefined pio_reset_halt_target command\\n",
|
||||
" monitor reset halt",
|
||||
"end",
|
||||
] + commands
|
||||
if not any("define pio_restart_target" in cmd for cmd in commands):
|
||||
commands += [
|
||||
"define pio_restart_target",
|
||||
" pio_reset_halt_target",
|
||||
" $INIT_BREAK",
|
||||
" %s" % ("continue" if patterns["INIT_BREAK"] else "next"),
|
||||
"end",
|
||||
]
|
||||
|
||||
banner = [
|
||||
"echo PlatformIO Unified Debugger -> http://bit.ly/pio-debug\\n",
|
||||
"echo PlatformIO: debug_tool = %s\\n" % self.debug_options["tool"],
|
||||
"echo PlatformIO: Initializing remote target...\\n",
|
||||
]
|
||||
footer = ["echo %s\\n" % self.INIT_COMPLETED_BANNER]
|
||||
commands = banner + commands + footer
|
||||
|
||||
with open(join(dst_dir, self.PIO_SRC_NAME), "w") as fp:
|
||||
fp.write("\n".join(self.apply_patterns(commands, patterns)))
|
||||
|
||||
def connectionMade(self):
|
||||
self._lock_session(self.transport.pid)
|
||||
|
||||
p = protocol.Protocol()
|
||||
p.dataReceived = self.onStdInData
|
||||
stdio.StandardIO(p)
|
||||
|
||||
def onStdInData(self, data):
|
||||
super(GDBClient, self).onStdInData(data)
|
||||
if b"-exec-run" in data:
|
||||
if self._target_is_run:
|
||||
token, _ = data.split(b"-", 1)
|
||||
self.outReceived(token + b"^running\n")
|
||||
return
|
||||
data = data.replace(b"-exec-run", b"-exec-continue")
|
||||
|
||||
if b"-exec-continue" in data:
|
||||
self._target_is_run = True
|
||||
if b"-gdb-exit" in data or data.strip() in (b"q", b"quit"):
|
||||
# Allow terminating via SIGINT/CTRL+C
|
||||
signal.signal(signal.SIGINT, signal.default_int_handler)
|
||||
self.transport.write(b"pio_reset_run_target\n")
|
||||
self.transport.write(data)
|
||||
|
||||
def processEnded(self, reason): # pylint: disable=unused-argument
|
||||
self._unlock_session()
|
||||
if self._gdbsrc_dir and isdir(self._gdbsrc_dir):
|
||||
fs.rmtree(self._gdbsrc_dir)
|
||||
if self._debug_server:
|
||||
self._debug_server.terminate()
|
||||
|
||||
reactor.stop()
|
||||
|
||||
def outReceived(self, data):
|
||||
super(GDBClient, self).outReceived(data)
|
||||
self._handle_error(data)
|
||||
# go to init break automatically
|
||||
if self.INIT_COMPLETED_BANNER.encode() in data:
|
||||
telemetry.send_event(
|
||||
"Debug", "Started", telemetry.dump_run_environment(self.env_options)
|
||||
)
|
||||
self._auto_continue_timer = task.LoopingCall(self._auto_exec_continue)
|
||||
self._auto_continue_timer.start(0.1)
|
||||
|
||||
def errReceived(self, data):
|
||||
super(GDBClient, self).errReceived(data)
|
||||
self._handle_error(data)
|
||||
|
||||
def console_log(self, msg):
|
||||
if helpers.is_gdbmi_mode():
|
||||
msg = helpers.escape_gdbmi_stream("~", msg)
|
||||
self.outReceived(msg if is_bytes(msg) else msg.encode())
|
||||
|
||||
def _auto_exec_continue(self):
|
||||
auto_exec_delay = 0.5 # in seconds
|
||||
if self._last_activity > (time.time() - auto_exec_delay):
|
||||
return
|
||||
if self._auto_continue_timer:
|
||||
self._auto_continue_timer.stop()
|
||||
self._auto_continue_timer = None
|
||||
|
||||
if not self.debug_options["init_break"] or self._target_is_run:
|
||||
return
|
||||
self.console_log(
|
||||
"PlatformIO: Resume the execution to `debug_init_break = %s`\n"
|
||||
% self.debug_options["init_break"]
|
||||
)
|
||||
self.console_log(
|
||||
"PlatformIO: More configuration options -> http://bit.ly/pio-debug\n"
|
||||
)
|
||||
self.transport.write(
|
||||
b"0-exec-continue\n" if helpers.is_gdbmi_mode() else b"continue\n"
|
||||
)
|
||||
self._target_is_run = True
|
||||
|
||||
def _handle_error(self, data):
|
||||
self._errors_buffer = (self._errors_buffer + data)[-8192:] # keep last 8 KBytes
|
||||
if not (
|
||||
self.PIO_SRC_NAME.encode() in self._errors_buffer
|
||||
and b"Error in sourced" in self._errors_buffer
|
||||
):
|
||||
return
|
||||
|
||||
last_erros = self._errors_buffer.decode()
|
||||
last_erros = " ".join(reversed(last_erros.split("\n")))
|
||||
last_erros = re.sub(r'((~|&)"|\\n\"|\\t)', " ", last_erros, flags=re.M)
|
||||
|
||||
err = "%s -> %s" % (
|
||||
telemetry.dump_run_environment(self.env_options),
|
||||
last_erros,
|
||||
)
|
||||
telemetry.send_exception("DebugInitError: %s" % err)
|
||||
self.transport.loseConnection()
|
||||
|
||||
def _kill_previous_session(self):
|
||||
assert self._session_id
|
||||
pid = None
|
||||
with ContentCache() as cc:
|
||||
pid = cc.get(self._session_id)
|
||||
cc.delete(self._session_id)
|
||||
if not pid:
|
||||
return
|
||||
if "windows" in util.get_systype():
|
||||
kill = ["Taskkill", "/PID", pid, "/F"]
|
||||
else:
|
||||
kill = ["kill", pid]
|
||||
try:
|
||||
proc.exec_command(kill)
|
||||
except: # pylint: disable=bare-except
|
||||
pass
|
||||
|
||||
def _lock_session(self, pid):
|
||||
if not self._session_id:
|
||||
return
|
||||
with ContentCache() as cc:
|
||||
cc.set(self._session_id, str(pid), "1h")
|
||||
|
||||
def _unlock_session(self):
|
||||
if not self._session_id:
|
||||
return
|
||||
with ContentCache() as cc:
|
||||
cc.delete(self._session_id)
|
||||
@@ -1,175 +0,0 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import time
|
||||
from os.path import isdir, isfile, join
|
||||
|
||||
from twisted.internet import defer # pylint: disable=import-error
|
||||
from twisted.internet import reactor # pylint: disable=import-error
|
||||
|
||||
from platformio import fs, util
|
||||
from platformio.commands.debug.exception import DebugInvalidOptionsError
|
||||
from platformio.commands.debug.helpers import escape_gdbmi_stream, is_gdbmi_mode
|
||||
from platformio.commands.debug.process.base import BaseProcess
|
||||
from platformio.proc import where_is_program
|
||||
|
||||
|
||||
class DebugServer(BaseProcess):
|
||||
def __init__(self, debug_options, env_options):
|
||||
super(DebugServer, self).__init__()
|
||||
self.debug_options = debug_options
|
||||
self.env_options = env_options
|
||||
|
||||
self._debug_port = ":3333"
|
||||
self._transport = None
|
||||
self._process_ended = False
|
||||
self._ready = False
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def spawn(self, patterns): # pylint: disable=too-many-branches
|
||||
systype = util.get_systype()
|
||||
server = self.debug_options.get("server")
|
||||
if not server:
|
||||
defer.returnValue(None)
|
||||
server = self.apply_patterns(server, patterns)
|
||||
server_executable = server["executable"]
|
||||
if not server_executable:
|
||||
defer.returnValue(None)
|
||||
if server["cwd"]:
|
||||
server_executable = join(server["cwd"], server_executable)
|
||||
if (
|
||||
"windows" in systype
|
||||
and not server_executable.endswith(".exe")
|
||||
and isfile(server_executable + ".exe")
|
||||
):
|
||||
server_executable = server_executable + ".exe"
|
||||
|
||||
if not isfile(server_executable):
|
||||
server_executable = where_is_program(server_executable)
|
||||
if not isfile(server_executable):
|
||||
raise DebugInvalidOptionsError(
|
||||
"\nCould not launch Debug Server '%s'. Please check that it "
|
||||
"is installed and is included in a system PATH\n\n"
|
||||
"See documentation or contact contact@platformio.org:\n"
|
||||
"https://docs.platformio.org/page/plus/debugging.html\n"
|
||||
% server_executable
|
||||
)
|
||||
|
||||
openocd_pipe_allowed = all(
|
||||
[not self.debug_options["port"], "openocd" in server_executable]
|
||||
)
|
||||
if openocd_pipe_allowed:
|
||||
args = []
|
||||
if server["cwd"]:
|
||||
args.extend(["-s", server["cwd"]])
|
||||
args.extend(
|
||||
["-c", "gdb_port pipe; tcl_port disabled; telnet_port disabled"]
|
||||
)
|
||||
args.extend(server["arguments"])
|
||||
str_args = " ".join(
|
||||
[arg if arg.startswith("-") else '"%s"' % arg for arg in args]
|
||||
)
|
||||
self._debug_port = '| "%s" %s' % (server_executable, str_args)
|
||||
self._debug_port = fs.to_unix_path(self._debug_port)
|
||||
defer.returnValue(self._debug_port)
|
||||
|
||||
env = os.environ.copy()
|
||||
# prepend server "lib" folder to LD path
|
||||
if (
|
||||
"windows" not in systype
|
||||
and server["cwd"]
|
||||
and isdir(join(server["cwd"], "lib"))
|
||||
):
|
||||
ld_key = "DYLD_LIBRARY_PATH" if "darwin" in systype else "LD_LIBRARY_PATH"
|
||||
env[ld_key] = join(server["cwd"], "lib")
|
||||
if os.environ.get(ld_key):
|
||||
env[ld_key] = "%s:%s" % (env[ld_key], os.environ.get(ld_key))
|
||||
# prepend BIN to PATH
|
||||
if server["cwd"] and isdir(join(server["cwd"], "bin")):
|
||||
env["PATH"] = "%s%s%s" % (
|
||||
join(server["cwd"], "bin"),
|
||||
os.pathsep,
|
||||
os.environ.get("PATH", os.environ.get("Path", "")),
|
||||
)
|
||||
|
||||
self._transport = reactor.spawnProcess(
|
||||
self,
|
||||
server_executable,
|
||||
[server_executable] + server["arguments"],
|
||||
path=server["cwd"],
|
||||
env=env,
|
||||
)
|
||||
if "mspdebug" in server_executable.lower():
|
||||
self._debug_port = ":2000"
|
||||
elif "jlink" in server_executable.lower():
|
||||
self._debug_port = ":2331"
|
||||
elif "qemu" in server_executable.lower():
|
||||
self._debug_port = ":1234"
|
||||
|
||||
yield self._wait_until_ready()
|
||||
|
||||
defer.returnValue(self._debug_port)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def _wait_until_ready(self):
|
||||
ready_pattern = self.debug_options.get("server", {}).get("ready_pattern")
|
||||
timeout = 60 if ready_pattern else 10
|
||||
elapsed = 0
|
||||
delay = 0.5
|
||||
auto_ready_delay = 0.5
|
||||
while not self._ready and not self._process_ended and elapsed < timeout:
|
||||
yield self.async_sleep(delay)
|
||||
if not ready_pattern:
|
||||
self._ready = self._last_activity < (time.time() - auto_ready_delay)
|
||||
elapsed += delay
|
||||
|
||||
def _check_ready_by_pattern(self, data):
|
||||
if self._ready:
|
||||
return self._ready
|
||||
ready_pattern = self.debug_options.get("server", {}).get("ready_pattern")
|
||||
if ready_pattern:
|
||||
self._ready = ready_pattern.encode() in data
|
||||
return self._ready
|
||||
|
||||
@staticmethod
|
||||
def async_sleep(secs):
|
||||
d = defer.Deferred()
|
||||
reactor.callLater(secs, d.callback, None)
|
||||
return d
|
||||
|
||||
def get_debug_port(self):
|
||||
return self._debug_port
|
||||
|
||||
def outReceived(self, data):
|
||||
super(DebugServer, self).outReceived(
|
||||
escape_gdbmi_stream("@", data) if is_gdbmi_mode() else data
|
||||
)
|
||||
self._check_ready_by_pattern(data)
|
||||
|
||||
def errReceived(self, data):
|
||||
super(DebugServer, self).errReceived(data)
|
||||
self._check_ready_by_pattern(data)
|
||||
|
||||
def processEnded(self, reason):
|
||||
self._process_ended = True
|
||||
super(DebugServer, self).processEnded(reason)
|
||||
|
||||
def terminate(self):
|
||||
if self._process_ended or not self._transport:
|
||||
return
|
||||
try:
|
||||
self._transport.signalProcess("KILL")
|
||||
except: # pylint: disable=bare-except
|
||||
pass
|
||||
@@ -12,4 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from platformio.commands.device.filters.base import DeviceMonitorFilter
|
||||
# pylint: disable=unused-import
|
||||
from platformio.device.monitor.filters.base import (
|
||||
DeviceMonitorFilterBase as DeviceMonitorFilter,
|
||||
)
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import sys
|
||||
from fnmatch import fnmatch
|
||||
|
||||
import click
|
||||
from serial.tools import miniterm
|
||||
|
||||
from platformio import exception, fs, util
|
||||
from platformio.commands.device import helpers as device_helpers
|
||||
from platformio.compat import dump_json_to_unicode
|
||||
from platformio.platform.factory import PlatformFactory
|
||||
from platformio.project.exception import NotPlatformIOProjectError
|
||||
|
||||
|
||||
@click.group(short_help="Device manager & serial/socket monitor")
|
||||
def cli():
|
||||
pass
|
||||
|
||||
|
||||
@cli.command("list", short_help="List devices")
|
||||
@click.option("--serial", is_flag=True, help="List serial ports, default")
|
||||
@click.option("--logical", is_flag=True, help="List logical devices")
|
||||
@click.option("--mdns", is_flag=True, help="List multicast DNS services")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def device_list( # pylint: disable=too-many-branches
|
||||
serial, logical, mdns, json_output
|
||||
):
|
||||
if not logical and not mdns:
|
||||
serial = True
|
||||
data = {}
|
||||
if serial:
|
||||
data["serial"] = util.get_serial_ports()
|
||||
if logical:
|
||||
data["logical"] = util.get_logical_devices()
|
||||
if mdns:
|
||||
data["mdns"] = util.get_mdns_services()
|
||||
|
||||
single_key = list(data)[0] if len(list(data)) == 1 else None
|
||||
|
||||
if json_output:
|
||||
return click.echo(
|
||||
dump_json_to_unicode(data[single_key] if single_key else data)
|
||||
)
|
||||
|
||||
titles = {
|
||||
"serial": "Serial Ports",
|
||||
"logical": "Logical Devices",
|
||||
"mdns": "Multicast DNS Services",
|
||||
}
|
||||
|
||||
for key, value in data.items():
|
||||
if not single_key:
|
||||
click.secho(titles[key], bold=True)
|
||||
click.echo("=" * len(titles[key]))
|
||||
|
||||
if key == "serial":
|
||||
for item in value:
|
||||
click.secho(item["port"], fg="cyan")
|
||||
click.echo("-" * len(item["port"]))
|
||||
click.echo("Hardware ID: %s" % item["hwid"])
|
||||
click.echo("Description: %s" % item["description"])
|
||||
click.echo("")
|
||||
|
||||
if key == "logical":
|
||||
for item in value:
|
||||
click.secho(item["path"], fg="cyan")
|
||||
click.echo("-" * len(item["path"]))
|
||||
click.echo("Name: %s" % item["name"])
|
||||
click.echo("")
|
||||
|
||||
if key == "mdns":
|
||||
for item in value:
|
||||
click.secho(item["name"], fg="cyan")
|
||||
click.echo("-" * len(item["name"]))
|
||||
click.echo("Type: %s" % item["type"])
|
||||
click.echo("IP: %s" % item["ip"])
|
||||
click.echo("Port: %s" % item["port"])
|
||||
if item["properties"]:
|
||||
click.echo(
|
||||
"Properties: %s"
|
||||
% (
|
||||
"; ".join(
|
||||
[
|
||||
"%s=%s" % (k, v)
|
||||
for k, v in item["properties"].items()
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
click.echo("")
|
||||
|
||||
if single_key:
|
||||
click.echo("")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@cli.command("monitor", short_help="Monitor device (Serial)")
|
||||
@click.option("--port", "-p", help="Port, a number or a device name")
|
||||
@click.option("--baud", "-b", type=int, help="Set baud rate, default=9600")
|
||||
@click.option(
|
||||
"--parity",
|
||||
default="N",
|
||||
type=click.Choice(["N", "E", "O", "S", "M"]),
|
||||
help="Set parity, default=N",
|
||||
)
|
||||
@click.option("--rtscts", is_flag=True, help="Enable RTS/CTS flow control, default=Off")
|
||||
@click.option(
|
||||
"--xonxoff", is_flag=True, help="Enable software flow control, default=Off"
|
||||
)
|
||||
@click.option(
|
||||
"--rts", default=None, type=click.IntRange(0, 1), help="Set initial RTS line state"
|
||||
)
|
||||
@click.option(
|
||||
"--dtr", default=None, type=click.IntRange(0, 1), help="Set initial DTR line state"
|
||||
)
|
||||
@click.option("--echo", is_flag=True, help="Enable local echo, default=Off")
|
||||
@click.option(
|
||||
"--encoding",
|
||||
default="UTF-8",
|
||||
help="Set the encoding for the serial port (e.g. hexlify, "
|
||||
"Latin1, UTF-8), default: UTF-8",
|
||||
)
|
||||
@click.option("--filter", "-f", multiple=True, help="Add filters/text transformations")
|
||||
@click.option(
|
||||
"--eol",
|
||||
default="CRLF",
|
||||
type=click.Choice(["CR", "LF", "CRLF"]),
|
||||
help="End of line mode, default=CRLF",
|
||||
)
|
||||
@click.option("--raw", is_flag=True, help="Do not apply any encodings/transformations")
|
||||
@click.option(
|
||||
"--exit-char",
|
||||
type=int,
|
||||
default=3,
|
||||
help="ASCII code of special character that is used to exit "
|
||||
"the application, default=3 (Ctrl+C)",
|
||||
)
|
||||
@click.option(
|
||||
"--menu-char",
|
||||
type=int,
|
||||
default=20,
|
||||
help="ASCII code of special character that is used to "
|
||||
"control miniterm (menu), default=20 (DEC)",
|
||||
)
|
||||
@click.option(
|
||||
"--quiet",
|
||||
is_flag=True,
|
||||
help="Diagnostics: suppress non-error messages, default=Off",
|
||||
)
|
||||
@click.option(
|
||||
"-d",
|
||||
"--project-dir",
|
||||
default=os.getcwd,
|
||||
type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True),
|
||||
)
|
||||
@click.option(
|
||||
"-e",
|
||||
"--environment",
|
||||
help="Load configuration from `platformio.ini` and specified environment",
|
||||
)
|
||||
def device_monitor(**kwargs): # pylint: disable=too-many-branches
|
||||
# load default monitor filters
|
||||
filters_dir = os.path.join(fs.get_source_dir(), "commands", "device", "filters")
|
||||
for name in os.listdir(filters_dir):
|
||||
if not name.endswith(".py"):
|
||||
continue
|
||||
device_helpers.load_monitor_filter(
|
||||
os.path.join(filters_dir, name), options=kwargs
|
||||
)
|
||||
|
||||
project_options = {}
|
||||
try:
|
||||
with fs.cd(kwargs["project_dir"]):
|
||||
project_options = device_helpers.get_project_options(kwargs["environment"])
|
||||
kwargs = device_helpers.apply_project_monitor_options(kwargs, project_options)
|
||||
except NotPlatformIOProjectError:
|
||||
pass
|
||||
|
||||
platform = None
|
||||
if "platform" in project_options:
|
||||
with fs.cd(kwargs["project_dir"]):
|
||||
platform = PlatformFactory.new(project_options["platform"])
|
||||
device_helpers.register_platform_filters(platform, options=kwargs)
|
||||
|
||||
if not kwargs["port"]:
|
||||
ports = util.get_serial_ports(filter_hwid=True)
|
||||
if len(ports) == 1:
|
||||
kwargs["port"] = ports[0]["port"]
|
||||
elif "platform" in project_options and "board" in project_options:
|
||||
board_hwids = device_helpers.get_board_hwids(
|
||||
kwargs["project_dir"],
|
||||
platform,
|
||||
project_options["board"],
|
||||
)
|
||||
for item in ports:
|
||||
for hwid in board_hwids:
|
||||
hwid_str = ("%s:%s" % (hwid[0], hwid[1])).replace("0x", "")
|
||||
if hwid_str in item["hwid"]:
|
||||
kwargs["port"] = item["port"]
|
||||
break
|
||||
if kwargs["port"]:
|
||||
break
|
||||
elif kwargs["port"] and (set(["*", "?", "[", "]"]) & set(kwargs["port"])):
|
||||
for item in util.get_serial_ports():
|
||||
if fnmatch(item["port"], kwargs["port"]):
|
||||
kwargs["port"] = item["port"]
|
||||
break
|
||||
|
||||
# override system argv with patched options
|
||||
sys.argv = ["monitor"] + device_helpers.options_to_argv(
|
||||
kwargs,
|
||||
project_options,
|
||||
ignore=("port", "baud", "rts", "dtr", "environment", "project_dir"),
|
||||
)
|
||||
|
||||
if not kwargs["quiet"]:
|
||||
click.echo(
|
||||
"--- Available filters and text transformations: %s"
|
||||
% ", ".join(sorted(miniterm.TRANSFORMATIONS.keys()))
|
||||
)
|
||||
click.echo("--- More details at http://bit.ly/pio-monitor-filters")
|
||||
try:
|
||||
miniterm.main(
|
||||
default_port=kwargs["port"],
|
||||
default_baudrate=kwargs["baud"] or 9600,
|
||||
default_rts=kwargs["rts"],
|
||||
default_dtr=kwargs["dtr"],
|
||||
)
|
||||
except Exception as e:
|
||||
raise exception.MinitermException(e)
|
||||
@@ -1,43 +0,0 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from serial.tools import miniterm
|
||||
|
||||
from platformio.project.config import ProjectConfig
|
||||
|
||||
|
||||
class DeviceMonitorFilter(miniterm.Transform):
|
||||
def __init__(self, options=None):
|
||||
""" Called by PlatformIO to pass context """
|
||||
miniterm.Transform.__init__(self)
|
||||
|
||||
self.options = options or {}
|
||||
self.project_dir = self.options.get("project_dir")
|
||||
self.environment = self.options.get("environment")
|
||||
|
||||
self.config = ProjectConfig.get_instance()
|
||||
if not self.environment:
|
||||
default_envs = self.config.default_envs()
|
||||
if default_envs:
|
||||
self.environment = default_envs[0]
|
||||
elif self.config.envs():
|
||||
self.environment = self.config.envs()[0]
|
||||
|
||||
def __call__(self):
|
||||
""" Called by the miniterm library when the filter is actually used """
|
||||
return self
|
||||
|
||||
@property
|
||||
def NAME(self):
|
||||
raise NotImplementedError("Please declare NAME attribute for the filter class")
|
||||
@@ -1,106 +0,0 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import inspect
|
||||
import os
|
||||
|
||||
from serial.tools import miniterm
|
||||
|
||||
from platformio import fs
|
||||
from platformio.commands.device import DeviceMonitorFilter
|
||||
from platformio.compat import get_object_members, load_python_module
|
||||
from platformio.project.config import ProjectConfig
|
||||
|
||||
|
||||
def apply_project_monitor_options(cli_options, project_options):
|
||||
for k in ("port", "speed", "rts", "dtr"):
|
||||
k2 = "monitor_%s" % k
|
||||
if k == "speed":
|
||||
k = "baud"
|
||||
if cli_options[k] is None and k2 in project_options:
|
||||
cli_options[k] = project_options[k2]
|
||||
if k != "port":
|
||||
cli_options[k] = int(cli_options[k])
|
||||
return cli_options
|
||||
|
||||
|
||||
def options_to_argv(cli_options, project_options, ignore=None):
|
||||
confmon_flags = project_options.get("monitor_flags", [])
|
||||
result = confmon_flags[::]
|
||||
|
||||
for f in project_options.get("monitor_filters", []):
|
||||
result.extend(["--filter", f])
|
||||
|
||||
for k, v in cli_options.items():
|
||||
if v is None or (ignore and k in ignore):
|
||||
continue
|
||||
k = "--" + k.replace("_", "-")
|
||||
if k in confmon_flags:
|
||||
continue
|
||||
if isinstance(v, bool):
|
||||
if v:
|
||||
result.append(k)
|
||||
elif isinstance(v, tuple):
|
||||
for i in v:
|
||||
result.extend([k, i])
|
||||
else:
|
||||
result.extend([k, str(v)])
|
||||
return result
|
||||
|
||||
|
||||
def get_project_options(environment=None):
|
||||
config = ProjectConfig.get_instance()
|
||||
config.validate(envs=[environment] if environment else None)
|
||||
if not environment:
|
||||
default_envs = config.default_envs()
|
||||
if default_envs:
|
||||
environment = default_envs[0]
|
||||
else:
|
||||
environment = config.envs()[0]
|
||||
return config.items(env=environment, as_dict=True)
|
||||
|
||||
|
||||
def get_board_hwids(project_dir, platform, board):
|
||||
with fs.cd(project_dir):
|
||||
return platform.board_config(board).get("build.hwids", [])
|
||||
|
||||
|
||||
def load_monitor_filter(path, options=None):
|
||||
name = os.path.basename(path)
|
||||
name = name[: name.find(".")]
|
||||
module = load_python_module("platformio.commands.device.filters.%s" % name, path)
|
||||
for cls in get_object_members(module).values():
|
||||
if (
|
||||
not inspect.isclass(cls)
|
||||
or not issubclass(cls, DeviceMonitorFilter)
|
||||
or cls == DeviceMonitorFilter
|
||||
):
|
||||
continue
|
||||
obj = cls(options)
|
||||
miniterm.TRANSFORMATIONS[obj.NAME] = obj
|
||||
return True
|
||||
|
||||
|
||||
def register_platform_filters(platform, options=None):
|
||||
monitor_dir = os.path.join(platform.get_dir(), "monitor")
|
||||
if not os.path.isdir(monitor_dir):
|
||||
return
|
||||
|
||||
for name in os.listdir(monitor_dir):
|
||||
if not name.startswith("filter_") or not name.endswith(".py"):
|
||||
continue
|
||||
path = os.path.join(monitor_dir, name)
|
||||
if not os.path.isfile(path):
|
||||
continue
|
||||
load_monitor_filter(path, options)
|
||||
@@ -1,48 +0,0 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import time
|
||||
|
||||
import jsonrpc
|
||||
|
||||
from platformio.compat import get_running_loop
|
||||
|
||||
|
||||
class IDERPC:
|
||||
def __init__(self):
|
||||
self._queue = {}
|
||||
|
||||
def send_command(self, sid, command, params):
|
||||
if not self._queue.get(sid):
|
||||
raise jsonrpc.exceptions.JSONRPCDispatchException(
|
||||
code=4005, message="PIO Home IDE agent is not started"
|
||||
)
|
||||
while self._queue[sid]:
|
||||
self._queue[sid].pop().set_result(
|
||||
{"id": time.time(), "method": command, "params": params}
|
||||
)
|
||||
|
||||
def listen_commands(self, sid=0):
|
||||
if sid not in self._queue:
|
||||
self._queue[sid] = []
|
||||
self._queue[sid].append(get_running_loop().create_future())
|
||||
return self._queue[sid][-1]
|
||||
|
||||
def open_project(self, sid, project_dir):
|
||||
return self.send_command(sid, "open_project", project_dir)
|
||||
|
||||
def open_text_document(self, sid, path, line=None, column=None):
|
||||
return self.send_command(
|
||||
sid, "open_text_document", dict(path=path, line=line, column=column)
|
||||
)
|
||||
461
platformio/commands/lib.py
Normal file
461
platformio/commands/lib.py
Normal file
@@ -0,0 +1,461 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# pylint: disable=too-many-branches, too-many-locals
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
import click
|
||||
|
||||
from platformio import exception, fs
|
||||
from platformio.cli import PlatformioCLI
|
||||
from platformio.package.commands.install import package_install_cmd
|
||||
from platformio.package.commands.list import package_list_cmd
|
||||
from platformio.package.commands.search import package_search_cmd
|
||||
from platformio.package.commands.show import package_show_cmd
|
||||
from platformio.package.commands.uninstall import package_uninstall_cmd
|
||||
from platformio.package.commands.update import package_update_cmd
|
||||
from platformio.package.exception import NotGlobalLibDir
|
||||
from platformio.package.manager.library import LibraryPackageManager
|
||||
from platformio.package.meta import PackageItem, PackageSpec
|
||||
from platformio.proc import is_ci
|
||||
from platformio.project.config import ProjectConfig
|
||||
from platformio.project.helpers import get_project_dir, is_platformio_project
|
||||
|
||||
CTX_META_INPUT_DIRS_KEY = __name__ + ".input_dirs"
|
||||
CTX_META_PROJECT_ENVIRONMENTS_KEY = __name__ + ".project_environments"
|
||||
CTX_META_STORAGE_DIRS_KEY = __name__ + ".storage_dirs"
|
||||
CTX_META_STORAGE_LIBDEPS_KEY = __name__ + ".storage_lib_deps"
|
||||
|
||||
|
||||
def get_project_global_lib_dir():
|
||||
return ProjectConfig.get_instance().get("platformio", "globallib_dir")
|
||||
|
||||
|
||||
def invoke_command(ctx, cmd, **kwargs):
|
||||
input_dirs = ctx.meta.get(CTX_META_INPUT_DIRS_KEY, [])
|
||||
project_environments = ctx.meta[CTX_META_PROJECT_ENVIRONMENTS_KEY]
|
||||
for input_dir in input_dirs:
|
||||
cmd_kwargs = kwargs.copy()
|
||||
if is_platformio_project(input_dir):
|
||||
cmd_kwargs["project_dir"] = input_dir
|
||||
cmd_kwargs["environments"] = project_environments
|
||||
else:
|
||||
cmd_kwargs["global"] = True
|
||||
cmd_kwargs["storage_dir"] = input_dir
|
||||
ctx.invoke(cmd, **cmd_kwargs)
|
||||
|
||||
|
||||
@click.group(short_help="Library manager", hidden=True)
|
||||
@click.option(
|
||||
"-d",
|
||||
"--storage-dir",
|
||||
multiple=True,
|
||||
default=None,
|
||||
type=click.Path(
|
||||
exists=True, file_okay=False, dir_okay=True, writable=True, resolve_path=True
|
||||
),
|
||||
help="Manage custom library storage",
|
||||
)
|
||||
@click.option(
|
||||
"-g", "--global", is_flag=True, help="Manage global PlatformIO library storage"
|
||||
)
|
||||
@click.option(
|
||||
"-e",
|
||||
"--environment",
|
||||
multiple=True,
|
||||
help=(
|
||||
"Manage libraries for the specific project build environments "
|
||||
"declared in `platformio.ini`"
|
||||
),
|
||||
)
|
||||
@click.pass_context
|
||||
def cli(ctx, **options):
|
||||
in_silence = PlatformioCLI.in_silence()
|
||||
storage_cmds = ("install", "uninstall", "update", "list")
|
||||
# skip commands that don't need storage folder
|
||||
if ctx.invoked_subcommand not in storage_cmds or (
|
||||
len(ctx.args) == 2 and ctx.args[1] in ("-h", "--help")
|
||||
):
|
||||
return
|
||||
storage_dirs = list(options["storage_dir"])
|
||||
if options["global"]:
|
||||
storage_dirs.append(get_project_global_lib_dir())
|
||||
if not storage_dirs:
|
||||
if is_platformio_project():
|
||||
storage_dirs = [get_project_dir()]
|
||||
elif is_ci():
|
||||
storage_dirs = [get_project_global_lib_dir()]
|
||||
click.secho(
|
||||
"Warning! Global library storage is used automatically. "
|
||||
"Please use `platformio lib --global %s` command to remove "
|
||||
"this warning." % ctx.invoked_subcommand,
|
||||
fg="yellow",
|
||||
)
|
||||
|
||||
if not storage_dirs:
|
||||
raise NotGlobalLibDir(
|
||||
get_project_dir(), get_project_global_lib_dir(), ctx.invoked_subcommand
|
||||
)
|
||||
|
||||
ctx.meta[CTX_META_PROJECT_ENVIRONMENTS_KEY] = options["environment"]
|
||||
ctx.meta[CTX_META_INPUT_DIRS_KEY] = storage_dirs
|
||||
ctx.meta[CTX_META_STORAGE_DIRS_KEY] = []
|
||||
ctx.meta[CTX_META_STORAGE_LIBDEPS_KEY] = {}
|
||||
for storage_dir in storage_dirs:
|
||||
if not is_platformio_project(storage_dir):
|
||||
ctx.meta[CTX_META_STORAGE_DIRS_KEY].append(storage_dir)
|
||||
continue
|
||||
with fs.cd(storage_dir):
|
||||
config = ProjectConfig.get_instance(
|
||||
os.path.join(storage_dir, "platformio.ini")
|
||||
)
|
||||
config.validate(options["environment"], silent=in_silence)
|
||||
libdeps_dir = config.get("platformio", "libdeps_dir")
|
||||
for env in config.envs():
|
||||
if options["environment"] and env not in options["environment"]:
|
||||
continue
|
||||
storage_dir = os.path.join(libdeps_dir, env)
|
||||
ctx.meta[CTX_META_STORAGE_DIRS_KEY].append(storage_dir)
|
||||
ctx.meta[CTX_META_STORAGE_LIBDEPS_KEY][storage_dir] = config.get(
|
||||
"env:" + env, "lib_deps", []
|
||||
)
|
||||
|
||||
|
||||
@cli.command("install", short_help="Install library")
|
||||
@click.argument("libraries", required=False, nargs=-1, metavar="[LIBRARY...]")
|
||||
@click.option(
|
||||
"--save/--no-save",
|
||||
is_flag=True,
|
||||
default=True,
|
||||
help="Save installed libraries into the `platformio.ini` dependency list"
|
||||
" (enabled by default)",
|
||||
)
|
||||
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
|
||||
@click.option(
|
||||
"--interactive",
|
||||
is_flag=True,
|
||||
help="Deprecated! Please use a strict dependency specification (owner/libname)",
|
||||
)
|
||||
@click.option(
|
||||
"-f", "--force", is_flag=True, help="Reinstall/redownload library if exists"
|
||||
)
|
||||
@click.pass_context
|
||||
def lib_install( # pylint: disable=too-many-arguments,unused-argument
|
||||
ctx, libraries, save, silent, interactive, force
|
||||
):
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg install` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
return invoke_command(
|
||||
ctx,
|
||||
package_install_cmd,
|
||||
libraries=libraries,
|
||||
no_save=not save,
|
||||
force=force,
|
||||
silent=silent,
|
||||
)
|
||||
|
||||
|
||||
@cli.command("uninstall", short_help="Remove libraries")
|
||||
@click.argument("libraries", nargs=-1, metavar="[LIBRARY...]")
|
||||
@click.option(
|
||||
"--save/--no-save",
|
||||
is_flag=True,
|
||||
default=True,
|
||||
help="Remove libraries from the `platformio.ini` dependency list and save changes"
|
||||
" (enabled by default)",
|
||||
)
|
||||
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
|
||||
@click.pass_context
|
||||
def lib_uninstall(ctx, libraries, save, silent):
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg uninstall` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
invoke_command(
|
||||
ctx,
|
||||
package_uninstall_cmd,
|
||||
libraries=libraries,
|
||||
no_save=not save,
|
||||
silent=silent,
|
||||
)
|
||||
|
||||
|
||||
@cli.command("update", short_help="Update installed libraries")
|
||||
@click.argument("libraries", required=False, nargs=-1, metavar="[LIBRARY...]")
|
||||
@click.option(
|
||||
"-c",
|
||||
"--only-check",
|
||||
is_flag=True,
|
||||
help="DEPRECATED. Please use `--dry-run` instead",
|
||||
)
|
||||
@click.option(
|
||||
"--dry-run", is_flag=True, help="Do not update, only check for the new versions"
|
||||
)
|
||||
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.pass_context
|
||||
def lib_update( # pylint: disable=too-many-arguments
|
||||
ctx, libraries, only_check, dry_run, silent, json_output
|
||||
):
|
||||
only_check = dry_run or only_check
|
||||
if only_check and not json_output:
|
||||
raise exception.UserSideException(
|
||||
"This command is deprecated, please use `pio pkg outdated` instead"
|
||||
)
|
||||
|
||||
if not json_output:
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg update` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
return invoke_command(
|
||||
ctx,
|
||||
package_update_cmd,
|
||||
libraries=libraries,
|
||||
silent=silent,
|
||||
)
|
||||
|
||||
storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]
|
||||
json_result = {}
|
||||
for storage_dir in storage_dirs:
|
||||
lib_deps = ctx.meta.get(CTX_META_STORAGE_LIBDEPS_KEY, {}).get(storage_dir, [])
|
||||
lm = LibraryPackageManager(storage_dir)
|
||||
lm.set_log_level(logging.WARN if silent else logging.DEBUG)
|
||||
_libraries = libraries or lib_deps or lm.get_installed()
|
||||
|
||||
result = []
|
||||
for library in _libraries:
|
||||
spec = None
|
||||
pkg = None
|
||||
if isinstance(library, PackageItem):
|
||||
pkg = library
|
||||
else:
|
||||
spec = PackageSpec(library)
|
||||
pkg = lm.get_package(spec)
|
||||
if not pkg:
|
||||
continue
|
||||
outdated = lm.outdated(pkg, spec)
|
||||
if not outdated.is_outdated(allow_incompatible=True):
|
||||
continue
|
||||
manifest = lm.legacy_load_manifest(pkg)
|
||||
manifest["versionWanted"] = (
|
||||
str(outdated.wanted) if outdated.wanted else None
|
||||
)
|
||||
manifest["versionLatest"] = (
|
||||
str(outdated.latest) if outdated.latest else None
|
||||
)
|
||||
result.append(manifest)
|
||||
|
||||
json_result[storage_dir] = result
|
||||
|
||||
return click.echo(
|
||||
json.dumps(
|
||||
json_result[storage_dirs[0]] if len(storage_dirs) == 1 else json_result
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@cli.command("list", short_help="List installed libraries")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.pass_context
|
||||
def lib_list(ctx, json_output):
|
||||
if not json_output:
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg list` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
return invoke_command(ctx, package_list_cmd, only_libraries=True)
|
||||
|
||||
storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]
|
||||
json_result = {}
|
||||
for storage_dir in storage_dirs:
|
||||
lm = LibraryPackageManager(storage_dir)
|
||||
json_result[storage_dir] = lm.legacy_get_installed()
|
||||
return click.echo(
|
||||
json.dumps(
|
||||
json_result[storage_dirs[0]] if len(storage_dirs) == 1 else json_result
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@cli.command("search", short_help="Search for a library")
|
||||
@click.argument("query", required=False, nargs=-1)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.option("--page", type=click.INT, default=1)
|
||||
@click.option("--id", multiple=True)
|
||||
@click.option("-o", "--owner", multiple=True)
|
||||
@click.option("-n", "--name", multiple=True)
|
||||
@click.option("-a", "--author", multiple=True)
|
||||
@click.option("-k", "--keyword", multiple=True)
|
||||
@click.option("-f", "--framework", multiple=True)
|
||||
@click.option("-p", "--platform", multiple=True)
|
||||
@click.option("-i", "--header", multiple=True)
|
||||
@click.option(
|
||||
"--noninteractive",
|
||||
is_flag=True,
|
||||
help="Do not prompt, automatically paginate with delay",
|
||||
)
|
||||
@click.pass_context
|
||||
def lib_search( # pylint: disable=unused-argument
|
||||
ctx, query, json_output, page, noninteractive, **filters
|
||||
):
|
||||
if not query:
|
||||
query = []
|
||||
if not isinstance(query, list):
|
||||
query = list(query)
|
||||
|
||||
for key, values in filters.items():
|
||||
for value in values:
|
||||
query.append('%s:"%s"' % (key, value))
|
||||
|
||||
if not json_output:
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg search` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
query.append("type:library")
|
||||
return ctx.invoke(package_search_cmd, query=" ".join(query), page=page)
|
||||
|
||||
regclient = LibraryPackageManager().get_registry_client_instance()
|
||||
result = regclient.fetch_json_data(
|
||||
"get",
|
||||
"/v2/lib/search",
|
||||
params=dict(query=" ".join(query), page=page),
|
||||
x_cache_valid="1d",
|
||||
)
|
||||
return click.echo(json.dumps(result))
|
||||
|
||||
|
||||
@cli.command("builtin", short_help="List built-in libraries")
|
||||
@click.option("--storage", multiple=True)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def lib_builtin(storage, json_output):
|
||||
items = LibraryPackageManager.get_builtin_libs(storage)
|
||||
if json_output:
|
||||
return click.echo(json.dumps(items))
|
||||
|
||||
for storage_ in items:
|
||||
if not storage_["items"]:
|
||||
continue
|
||||
click.secho(storage_["name"], fg="green")
|
||||
click.echo("*" * len(storage_["name"]))
|
||||
click.echo()
|
||||
|
||||
for item in sorted(storage_["items"], key=lambda i: i["name"]):
|
||||
print_lib_item(item)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@cli.command("show", short_help="Show detailed info about a library")
|
||||
@click.argument("library", metavar="[LIBRARY]")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.pass_context
|
||||
def lib_show(ctx, library, json_output):
|
||||
if not json_output:
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg show` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
return ctx.invoke(package_show_cmd, pkg_type="library", spec=library)
|
||||
|
||||
lm = LibraryPackageManager()
|
||||
lm.set_log_level(logging.ERROR if json_output else logging.DEBUG)
|
||||
lib_id = lm.reveal_registry_package_id(library)
|
||||
regclient = lm.get_registry_client_instance()
|
||||
lib = regclient.fetch_json_data(
|
||||
"get", "/v2/lib/info/%d" % lib_id, x_cache_valid="1h"
|
||||
)
|
||||
return click.echo(json.dumps(lib))
|
||||
|
||||
|
||||
@cli.command("register", short_help="Deprecated")
|
||||
@click.argument("config_url")
|
||||
def lib_register(config_url): # pylint: disable=unused-argument
|
||||
raise exception.UserSideException(
|
||||
"This command is deprecated. Please use `pio pkg publish` command."
|
||||
)
|
||||
|
||||
|
||||
@cli.command("stats", short_help="Library Registry Statistics")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def lib_stats(json_output):
|
||||
if not json_output:
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease visit "
|
||||
"https://registry.platformio.org\n",
|
||||
fg="yellow",
|
||||
)
|
||||
return None
|
||||
|
||||
regclient = LibraryPackageManager().get_registry_client_instance()
|
||||
result = regclient.fetch_json_data("get", "/v2/lib/stats", x_cache_valid="1h")
|
||||
return click.echo(json.dumps(result))
|
||||
|
||||
|
||||
def print_lib_item(item):
|
||||
click.secho(item["name"], fg="cyan")
|
||||
click.echo("=" * len(item["name"]))
|
||||
if "id" in item:
|
||||
click.secho("#ID: %d" % item["id"], bold=True)
|
||||
if "description" in item or "url" in item:
|
||||
click.echo(item.get("description", item.get("url", "")))
|
||||
click.echo()
|
||||
|
||||
for key in ("version", "homepage", "license", "keywords"):
|
||||
if key not in item or not item[key]:
|
||||
continue
|
||||
if isinstance(item[key], list):
|
||||
click.echo("%s: %s" % (key.capitalize(), ", ".join(item[key])))
|
||||
else:
|
||||
click.echo("%s: %s" % (key.capitalize(), item[key]))
|
||||
|
||||
for key in ("frameworks", "platforms"):
|
||||
if key not in item:
|
||||
continue
|
||||
click.echo(
|
||||
"Compatible %s: %s"
|
||||
% (
|
||||
key,
|
||||
", ".join(
|
||||
[i["title"] if isinstance(i, dict) else i for i in item[key]]
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
if "authors" in item or "authornames" in item:
|
||||
click.echo(
|
||||
"Authors: %s"
|
||||
% ", ".join(
|
||||
item.get(
|
||||
"authornames", [a.get("name", "") for a in item.get("authors", [])]
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if "__src_url" in item:
|
||||
click.secho("Source: %s" % item["__src_url"])
|
||||
click.echo()
|
||||
@@ -1,651 +0,0 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# pylint: disable=too-many-branches, too-many-locals
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
import click
|
||||
from tabulate import tabulate
|
||||
|
||||
from platformio import exception, fs, util
|
||||
from platformio.commands import PlatformioCLI
|
||||
from platformio.commands.lib.helpers import get_builtin_libs, save_project_libdeps
|
||||
from platformio.compat import dump_json_to_unicode
|
||||
from platformio.package.exception import NotGlobalLibDir, UnknownPackageError
|
||||
from platformio.package.manager.library import LibraryPackageManager
|
||||
from platformio.package.meta import PackageItem, PackageSpec
|
||||
from platformio.proc import is_ci
|
||||
from platformio.project.config import ProjectConfig
|
||||
from platformio.project.helpers import get_project_dir, is_platformio_project
|
||||
|
||||
try:
|
||||
from urllib.parse import quote
|
||||
except ImportError:
|
||||
from urllib import quote
|
||||
|
||||
CTX_META_INPUT_DIRS_KEY = __name__ + ".input_dirs"
|
||||
CTX_META_PROJECT_ENVIRONMENTS_KEY = __name__ + ".project_environments"
|
||||
CTX_META_STORAGE_DIRS_KEY = __name__ + ".storage_dirs"
|
||||
CTX_META_STORAGE_LIBDEPS_KEY = __name__ + ".storage_lib_deps"
|
||||
|
||||
|
||||
def get_project_global_lib_dir():
|
||||
return ProjectConfig.get_instance().get_optional_dir("globallib")
|
||||
|
||||
|
||||
@click.group(short_help="Library manager")
|
||||
@click.option(
|
||||
"-d",
|
||||
"--storage-dir",
|
||||
multiple=True,
|
||||
default=None,
|
||||
type=click.Path(
|
||||
exists=True, file_okay=False, dir_okay=True, writable=True, resolve_path=True
|
||||
),
|
||||
help="Manage custom library storage",
|
||||
)
|
||||
@click.option(
|
||||
"-g", "--global", is_flag=True, help="Manage global PlatformIO library storage"
|
||||
)
|
||||
@click.option(
|
||||
"-e",
|
||||
"--environment",
|
||||
multiple=True,
|
||||
help=(
|
||||
"Manage libraries for the specific project build environments "
|
||||
"declared in `platformio.ini`"
|
||||
),
|
||||
)
|
||||
@click.pass_context
|
||||
def cli(ctx, **options):
|
||||
storage_cmds = ("install", "uninstall", "update", "list")
|
||||
# skip commands that don't need storage folder
|
||||
if ctx.invoked_subcommand not in storage_cmds or (
|
||||
len(ctx.args) == 2 and ctx.args[1] in ("-h", "--help")
|
||||
):
|
||||
return
|
||||
storage_dirs = list(options["storage_dir"])
|
||||
if options["global"]:
|
||||
storage_dirs.append(get_project_global_lib_dir())
|
||||
if not storage_dirs:
|
||||
if is_platformio_project():
|
||||
storage_dirs = [get_project_dir()]
|
||||
elif is_ci():
|
||||
storage_dirs = [get_project_global_lib_dir()]
|
||||
click.secho(
|
||||
"Warning! Global library storage is used automatically. "
|
||||
"Please use `platformio lib --global %s` command to remove "
|
||||
"this warning." % ctx.invoked_subcommand,
|
||||
fg="yellow",
|
||||
)
|
||||
|
||||
if not storage_dirs:
|
||||
raise NotGlobalLibDir(
|
||||
get_project_dir(), get_project_global_lib_dir(), ctx.invoked_subcommand
|
||||
)
|
||||
|
||||
in_silence = PlatformioCLI.in_silence()
|
||||
ctx.meta[CTX_META_PROJECT_ENVIRONMENTS_KEY] = options["environment"]
|
||||
ctx.meta[CTX_META_INPUT_DIRS_KEY] = storage_dirs
|
||||
ctx.meta[CTX_META_STORAGE_DIRS_KEY] = []
|
||||
ctx.meta[CTX_META_STORAGE_LIBDEPS_KEY] = {}
|
||||
for storage_dir in storage_dirs:
|
||||
if not is_platformio_project(storage_dir):
|
||||
ctx.meta[CTX_META_STORAGE_DIRS_KEY].append(storage_dir)
|
||||
continue
|
||||
with fs.cd(storage_dir):
|
||||
config = ProjectConfig.get_instance(
|
||||
os.path.join(storage_dir, "platformio.ini")
|
||||
)
|
||||
config.validate(options["environment"], silent=in_silence)
|
||||
libdeps_dir = config.get_optional_dir("libdeps")
|
||||
for env in config.envs():
|
||||
if options["environment"] and env not in options["environment"]:
|
||||
continue
|
||||
storage_dir = os.path.join(libdeps_dir, env)
|
||||
ctx.meta[CTX_META_STORAGE_DIRS_KEY].append(storage_dir)
|
||||
ctx.meta[CTX_META_STORAGE_LIBDEPS_KEY][storage_dir] = config.get(
|
||||
"env:" + env, "lib_deps", []
|
||||
)
|
||||
|
||||
|
||||
@cli.command("install", short_help="Install library")
|
||||
@click.argument("libraries", required=False, nargs=-1, metavar="[LIBRARY...]")
|
||||
@click.option(
|
||||
"--save/--no-save",
|
||||
is_flag=True,
|
||||
default=True,
|
||||
help="Save installed libraries into the `platformio.ini` dependency list"
|
||||
" (enabled by default)",
|
||||
)
|
||||
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
|
||||
@click.option(
|
||||
"--interactive",
|
||||
is_flag=True,
|
||||
help="Deprecated! Please use a strict dependency specification (owner/libname)",
|
||||
)
|
||||
@click.option(
|
||||
"-f", "--force", is_flag=True, help="Reinstall/redownload library if exists"
|
||||
)
|
||||
@click.pass_context
|
||||
def lib_install( # pylint: disable=too-many-arguments,unused-argument
|
||||
ctx, libraries, save, silent, interactive, force
|
||||
):
|
||||
storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]
|
||||
storage_libdeps = ctx.meta.get(CTX_META_STORAGE_LIBDEPS_KEY, [])
|
||||
|
||||
installed_pkgs = {}
|
||||
for storage_dir in storage_dirs:
|
||||
if not silent and (libraries or storage_dir in storage_libdeps):
|
||||
print_storage_header(storage_dirs, storage_dir)
|
||||
lm = LibraryPackageManager(storage_dir)
|
||||
|
||||
if libraries:
|
||||
installed_pkgs = {
|
||||
library: lm.install(library, silent=silent, force=force)
|
||||
for library in libraries
|
||||
}
|
||||
|
||||
elif storage_dir in storage_libdeps:
|
||||
for library in storage_libdeps[storage_dir]:
|
||||
lm.install(library, silent=silent, force=force)
|
||||
|
||||
if save and installed_pkgs:
|
||||
_save_deps(ctx, installed_pkgs)
|
||||
|
||||
|
||||
def _save_deps(ctx, pkgs, action="add"):
|
||||
specs = []
|
||||
for library, pkg in pkgs.items():
|
||||
spec = PackageSpec(library)
|
||||
if spec.external:
|
||||
specs.append(spec)
|
||||
else:
|
||||
specs.append(
|
||||
PackageSpec(
|
||||
owner=pkg.metadata.spec.owner,
|
||||
name=pkg.metadata.spec.name,
|
||||
requirements=spec.requirements
|
||||
or (
|
||||
("^%s" % pkg.metadata.version)
|
||||
if not pkg.metadata.version.build
|
||||
else pkg.metadata.version
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
input_dirs = ctx.meta.get(CTX_META_INPUT_DIRS_KEY, [])
|
||||
project_environments = ctx.meta[CTX_META_PROJECT_ENVIRONMENTS_KEY]
|
||||
for input_dir in input_dirs:
|
||||
if not is_platformio_project(input_dir):
|
||||
continue
|
||||
save_project_libdeps(input_dir, specs, project_environments, action=action)
|
||||
|
||||
|
||||
@cli.command("uninstall", short_help="Remove libraries")
|
||||
@click.argument("libraries", nargs=-1, metavar="[LIBRARY...]")
|
||||
@click.option(
|
||||
"--save/--no-save",
|
||||
is_flag=True,
|
||||
default=True,
|
||||
help="Remove libraries from the `platformio.ini` dependency list and save changes"
|
||||
" (enabled by default)",
|
||||
)
|
||||
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
|
||||
@click.pass_context
|
||||
def lib_uninstall(ctx, libraries, save, silent):
|
||||
storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]
|
||||
uninstalled_pkgs = {}
|
||||
for storage_dir in storage_dirs:
|
||||
print_storage_header(storage_dirs, storage_dir)
|
||||
lm = LibraryPackageManager(storage_dir)
|
||||
uninstalled_pkgs = {
|
||||
library: lm.uninstall(library, silent=silent) for library in libraries
|
||||
}
|
||||
|
||||
if save and uninstalled_pkgs:
|
||||
_save_deps(ctx, uninstalled_pkgs, action="remove")
|
||||
|
||||
|
||||
@cli.command("update", short_help="Update installed libraries")
|
||||
@click.argument("libraries", required=False, nargs=-1, metavar="[LIBRARY...]")
|
||||
@click.option(
|
||||
"-c",
|
||||
"--only-check",
|
||||
is_flag=True,
|
||||
help="DEPRECATED. Please use `--dry-run` instead",
|
||||
)
|
||||
@click.option(
|
||||
"--dry-run", is_flag=True, help="Do not update, only check for the new versions"
|
||||
)
|
||||
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.pass_context
|
||||
def lib_update( # pylint: disable=too-many-arguments
|
||||
ctx, libraries, only_check, dry_run, silent, json_output
|
||||
):
|
||||
storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]
|
||||
only_check = dry_run or only_check
|
||||
json_result = {}
|
||||
for storage_dir in storage_dirs:
|
||||
if not json_output:
|
||||
print_storage_header(storage_dirs, storage_dir)
|
||||
lib_deps = ctx.meta.get(CTX_META_STORAGE_LIBDEPS_KEY, {}).get(storage_dir, [])
|
||||
lm = LibraryPackageManager(storage_dir)
|
||||
_libraries = libraries or lib_deps or lm.get_installed()
|
||||
|
||||
if only_check and json_output:
|
||||
result = []
|
||||
for library in _libraries:
|
||||
spec = None
|
||||
pkg = None
|
||||
if isinstance(library, PackageItem):
|
||||
pkg = library
|
||||
else:
|
||||
spec = PackageSpec(library)
|
||||
pkg = lm.get_package(spec)
|
||||
if not pkg:
|
||||
continue
|
||||
outdated = lm.outdated(pkg, spec)
|
||||
if not outdated.is_outdated(allow_incompatible=True):
|
||||
continue
|
||||
manifest = lm.legacy_load_manifest(pkg)
|
||||
manifest["versionWanted"] = (
|
||||
str(outdated.wanted) if outdated.wanted else None
|
||||
)
|
||||
manifest["versionLatest"] = (
|
||||
str(outdated.latest) if outdated.latest else None
|
||||
)
|
||||
result.append(manifest)
|
||||
json_result[storage_dir] = result
|
||||
else:
|
||||
for library in _libraries:
|
||||
to_spec = (
|
||||
None if isinstance(library, PackageItem) else PackageSpec(library)
|
||||
)
|
||||
try:
|
||||
lm.update(
|
||||
library, to_spec=to_spec, only_check=only_check, silent=silent
|
||||
)
|
||||
except UnknownPackageError as e:
|
||||
if library not in lib_deps:
|
||||
raise e
|
||||
|
||||
if json_output:
|
||||
return click.echo(
|
||||
dump_json_to_unicode(
|
||||
json_result[storage_dirs[0]] if len(storage_dirs) == 1 else json_result
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@cli.command("list", short_help="List installed libraries")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.pass_context
|
||||
def lib_list(ctx, json_output):
|
||||
storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]
|
||||
json_result = {}
|
||||
for storage_dir in storage_dirs:
|
||||
if not json_output:
|
||||
print_storage_header(storage_dirs, storage_dir)
|
||||
lm = LibraryPackageManager(storage_dir)
|
||||
items = lm.legacy_get_installed()
|
||||
if json_output:
|
||||
json_result[storage_dir] = items
|
||||
elif items:
|
||||
for item in sorted(items, key=lambda i: i["name"]):
|
||||
print_lib_item(item)
|
||||
else:
|
||||
click.echo("No items found")
|
||||
|
||||
if json_output:
|
||||
return click.echo(
|
||||
dump_json_to_unicode(
|
||||
json_result[storage_dirs[0]] if len(storage_dirs) == 1 else json_result
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@cli.command("search", short_help="Search for a library")
|
||||
@click.argument("query", required=False, nargs=-1)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.option("--page", type=click.INT, default=1)
|
||||
@click.option("--id", multiple=True)
|
||||
@click.option("-o", "--owner", multiple=True)
|
||||
@click.option("-n", "--name", multiple=True)
|
||||
@click.option("-a", "--author", multiple=True)
|
||||
@click.option("-k", "--keyword", multiple=True)
|
||||
@click.option("-f", "--framework", multiple=True)
|
||||
@click.option("-p", "--platform", multiple=True)
|
||||
@click.option("-i", "--header", multiple=True)
|
||||
@click.option(
|
||||
"--noninteractive",
|
||||
is_flag=True,
|
||||
help="Do not prompt, automatically paginate with delay",
|
||||
)
|
||||
def lib_search(query, json_output, page, noninteractive, **filters):
|
||||
regclient = LibraryPackageManager().get_registry_client_instance()
|
||||
if not query:
|
||||
query = []
|
||||
if not isinstance(query, list):
|
||||
query = list(query)
|
||||
|
||||
for key, values in filters.items():
|
||||
for value in values:
|
||||
query.append('%s:"%s"' % (key, value))
|
||||
|
||||
result = regclient.fetch_json_data(
|
||||
"get",
|
||||
"/v2/lib/search",
|
||||
params=dict(query=" ".join(query), page=page),
|
||||
cache_valid="1d",
|
||||
)
|
||||
|
||||
if json_output:
|
||||
click.echo(dump_json_to_unicode(result))
|
||||
return
|
||||
|
||||
if result["total"] == 0:
|
||||
click.secho(
|
||||
"Nothing has been found by your request\n"
|
||||
"Try a less-specific search or use truncation (or wildcard) "
|
||||
"operator",
|
||||
fg="yellow",
|
||||
nl=False,
|
||||
)
|
||||
click.secho(" *", fg="green")
|
||||
click.secho("For example: DS*, PCA*, DHT* and etc.\n", fg="yellow")
|
||||
click.echo(
|
||||
"For more examples and advanced search syntax, please use documentation:"
|
||||
)
|
||||
click.secho(
|
||||
"https://docs.platformio.org/page/userguide/lib/cmd_search.html\n",
|
||||
fg="cyan",
|
||||
)
|
||||
return
|
||||
|
||||
click.secho(
|
||||
"Found %d libraries:\n" % result["total"],
|
||||
fg="green" if result["total"] else "yellow",
|
||||
)
|
||||
|
||||
while True:
|
||||
for item in result["items"]:
|
||||
print_lib_item(item)
|
||||
|
||||
if int(result["page"]) * int(result["perpage"]) >= int(result["total"]):
|
||||
break
|
||||
|
||||
if noninteractive:
|
||||
click.echo()
|
||||
click.secho(
|
||||
"Loading next %d libraries... Press Ctrl+C to stop!"
|
||||
% result["perpage"],
|
||||
fg="yellow",
|
||||
)
|
||||
click.echo()
|
||||
time.sleep(5)
|
||||
elif not click.confirm("Show next libraries?"):
|
||||
break
|
||||
result = regclient.fetch_json_data(
|
||||
"get",
|
||||
"/v2/lib/search",
|
||||
params=dict(query=" ".join(query), page=int(result["page"]) + 1),
|
||||
cache_valid="1d",
|
||||
)
|
||||
|
||||
|
||||
@cli.command("builtin", short_help="List built-in libraries")
|
||||
@click.option("--storage", multiple=True)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def lib_builtin(storage, json_output):
|
||||
items = get_builtin_libs(storage)
|
||||
if json_output:
|
||||
return click.echo(dump_json_to_unicode(items))
|
||||
|
||||
for storage_ in items:
|
||||
if not storage_["items"]:
|
||||
continue
|
||||
click.secho(storage_["name"], fg="green")
|
||||
click.echo("*" * len(storage_["name"]))
|
||||
click.echo()
|
||||
|
||||
for item in sorted(storage_["items"], key=lambda i: i["name"]):
|
||||
print_lib_item(item)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@cli.command("show", short_help="Show detailed info about a library")
|
||||
@click.argument("library", metavar="[LIBRARY]")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def lib_show(library, json_output):
|
||||
lm = LibraryPackageManager()
|
||||
lib_id = lm.reveal_registry_package_id(library, silent=json_output)
|
||||
regclient = lm.get_registry_client_instance()
|
||||
lib = regclient.fetch_json_data("get", "/v2/lib/info/%d" % lib_id, cache_valid="1h")
|
||||
if json_output:
|
||||
return click.echo(dump_json_to_unicode(lib))
|
||||
|
||||
title = "{ownername}/{name}".format(**lib)
|
||||
click.secho(title, fg="cyan")
|
||||
click.echo("=" * len(title))
|
||||
click.echo(lib["description"])
|
||||
click.echo()
|
||||
|
||||
click.secho("ID: %d" % lib["id"])
|
||||
click.echo(
|
||||
"Version: %s, released %s"
|
||||
% (
|
||||
lib["version"]["name"],
|
||||
time.strftime("%c", util.parse_date(lib["version"]["released"])),
|
||||
)
|
||||
)
|
||||
click.echo("Manifest: %s" % lib["confurl"])
|
||||
for key in ("homepage", "repository", "license"):
|
||||
if key not in lib or not lib[key]:
|
||||
continue
|
||||
if isinstance(lib[key], list):
|
||||
click.echo("%s: %s" % (key.title(), ", ".join(lib[key])))
|
||||
else:
|
||||
click.echo("%s: %s" % (key.title(), lib[key]))
|
||||
|
||||
blocks = []
|
||||
|
||||
_authors = []
|
||||
for author in lib.get("authors", []):
|
||||
_data = []
|
||||
for key in ("name", "email", "url", "maintainer"):
|
||||
if not author.get(key):
|
||||
continue
|
||||
if key == "email":
|
||||
_data.append("<%s>" % author[key])
|
||||
elif key == "maintainer":
|
||||
_data.append("(maintainer)")
|
||||
else:
|
||||
_data.append(author[key])
|
||||
_authors.append(" ".join(_data))
|
||||
if _authors:
|
||||
blocks.append(("Authors", _authors))
|
||||
|
||||
blocks.append(("Keywords", lib["keywords"]))
|
||||
for key in ("frameworks", "platforms"):
|
||||
if key not in lib or not lib[key]:
|
||||
continue
|
||||
blocks.append(("Compatible %s" % key, [i["title"] for i in lib[key]]))
|
||||
blocks.append(("Headers", lib["headers"]))
|
||||
blocks.append(("Examples", lib["examples"]))
|
||||
blocks.append(
|
||||
(
|
||||
"Versions",
|
||||
[
|
||||
"%s, released %s"
|
||||
% (v["name"], time.strftime("%c", util.parse_date(v["released"])))
|
||||
for v in lib["versions"]
|
||||
],
|
||||
)
|
||||
)
|
||||
blocks.append(
|
||||
(
|
||||
"Unique Downloads",
|
||||
[
|
||||
"Today: %s" % lib["dlstats"]["day"],
|
||||
"Week: %s" % lib["dlstats"]["week"],
|
||||
"Month: %s" % lib["dlstats"]["month"],
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
for (title, rows) in blocks:
|
||||
click.echo()
|
||||
click.secho(title, bold=True)
|
||||
click.echo("-" * len(title))
|
||||
for row in rows:
|
||||
click.echo(row)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@cli.command("register", short_help="Deprecated")
|
||||
@click.argument("config_url")
|
||||
def lib_register(config_url): # pylint: disable=unused-argument
|
||||
raise exception.UserSideException(
|
||||
"This command is deprecated. Please use `pio package publish` command."
|
||||
)
|
||||
|
||||
|
||||
@cli.command("stats", short_help="Library Registry Statistics")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def lib_stats(json_output):
|
||||
regclient = LibraryPackageManager().get_registry_client_instance()
|
||||
result = regclient.fetch_json_data("get", "/v2/lib/stats", cache_valid="1h")
|
||||
|
||||
if json_output:
|
||||
return click.echo(dump_json_to_unicode(result))
|
||||
|
||||
for key in ("updated", "added"):
|
||||
tabular_data = [
|
||||
(
|
||||
click.style(item["name"], fg="cyan"),
|
||||
time.strftime("%c", util.parse_date(item["date"])),
|
||||
"https://platformio.org/lib/show/%s/%s"
|
||||
% (item["id"], quote(item["name"])),
|
||||
)
|
||||
for item in result.get(key, [])
|
||||
]
|
||||
table = tabulate(
|
||||
tabular_data,
|
||||
headers=[click.style("RECENTLY " + key.upper(), bold=True), "Date", "URL"],
|
||||
)
|
||||
click.echo(table)
|
||||
click.echo()
|
||||
|
||||
for key in ("lastkeywords", "topkeywords"):
|
||||
tabular_data = [
|
||||
(
|
||||
click.style(name, fg="cyan"),
|
||||
"https://platformio.org/lib/search?query=" + quote("keyword:%s" % name),
|
||||
)
|
||||
for name in result.get(key, [])
|
||||
]
|
||||
table = tabulate(
|
||||
tabular_data,
|
||||
headers=[
|
||||
click.style(
|
||||
("RECENT" if key == "lastkeywords" else "POPULAR") + " KEYWORDS",
|
||||
bold=True,
|
||||
),
|
||||
"URL",
|
||||
],
|
||||
)
|
||||
click.echo(table)
|
||||
click.echo()
|
||||
|
||||
for key, title in (("dlday", "Today"), ("dlweek", "Week"), ("dlmonth", "Month")):
|
||||
tabular_data = [
|
||||
(
|
||||
click.style(item["name"], fg="cyan"),
|
||||
"https://platformio.org/lib/show/%s/%s"
|
||||
% (item["id"], quote(item["name"])),
|
||||
)
|
||||
for item in result.get(key, [])
|
||||
]
|
||||
table = tabulate(
|
||||
tabular_data,
|
||||
headers=[click.style("FEATURED: " + title.upper(), bold=True), "URL"],
|
||||
)
|
||||
click.echo(table)
|
||||
click.echo()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def print_storage_header(storage_dirs, storage_dir):
|
||||
if storage_dirs and storage_dirs[0] != storage_dir:
|
||||
click.echo("")
|
||||
click.echo(
|
||||
click.style("Library Storage: ", bold=True)
|
||||
+ click.style(storage_dir, fg="blue")
|
||||
)
|
||||
|
||||
|
||||
def print_lib_item(item):
|
||||
click.secho(item["name"], fg="cyan")
|
||||
click.echo("=" * len(item["name"]))
|
||||
if "id" in item:
|
||||
click.secho("#ID: %d" % item["id"], bold=True)
|
||||
if "description" in item or "url" in item:
|
||||
click.echo(item.get("description", item.get("url", "")))
|
||||
click.echo()
|
||||
|
||||
for key in ("version", "homepage", "license", "keywords"):
|
||||
if key not in item or not item[key]:
|
||||
continue
|
||||
if isinstance(item[key], list):
|
||||
click.echo("%s: %s" % (key.title(), ", ".join(item[key])))
|
||||
else:
|
||||
click.echo("%s: %s" % (key.title(), item[key]))
|
||||
|
||||
for key in ("frameworks", "platforms"):
|
||||
if key not in item:
|
||||
continue
|
||||
click.echo(
|
||||
"Compatible %s: %s"
|
||||
% (
|
||||
key,
|
||||
", ".join(
|
||||
[i["title"] if isinstance(i, dict) else i for i in item[key]]
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
if "authors" in item or "authornames" in item:
|
||||
click.echo(
|
||||
"Authors: %s"
|
||||
% ", ".join(
|
||||
item.get(
|
||||
"authornames", [a.get("name", "") for a in item.get("authors", [])]
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if "__src_url" in item:
|
||||
click.secho("Source: %s" % item["__src_url"])
|
||||
click.echo()
|
||||
@@ -1,102 +0,0 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
|
||||
from platformio.compat import ci_strings_are_equal
|
||||
from platformio.package.manager.platform import PlatformPackageManager
|
||||
from platformio.package.meta import PackageSpec
|
||||
from platformio.platform.factory import PlatformFactory
|
||||
from platformio.project.config import ProjectConfig
|
||||
from platformio.project.exception import InvalidProjectConfError
|
||||
|
||||
|
||||
def get_builtin_libs(storage_names=None):
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from platformio.package.manager.library import LibraryPackageManager
|
||||
|
||||
items = []
|
||||
storage_names = storage_names or []
|
||||
pm = PlatformPackageManager()
|
||||
for pkg in pm.get_installed():
|
||||
p = PlatformFactory.new(pkg)
|
||||
for storage in p.get_lib_storages():
|
||||
if storage_names and storage["name"] not in storage_names:
|
||||
continue
|
||||
lm = LibraryPackageManager(storage["path"])
|
||||
items.append(
|
||||
{
|
||||
"name": storage["name"],
|
||||
"path": storage["path"],
|
||||
"items": lm.legacy_get_installed(),
|
||||
}
|
||||
)
|
||||
return items
|
||||
|
||||
|
||||
def is_builtin_lib(name, storages=None):
|
||||
for storage in storages or get_builtin_libs():
|
||||
for lib in storage["items"]:
|
||||
if lib.get("name") == name:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def ignore_deps_by_specs(deps, specs):
|
||||
result = []
|
||||
for dep in deps:
|
||||
depspec = PackageSpec(dep)
|
||||
if depspec.external:
|
||||
result.append(dep)
|
||||
continue
|
||||
ignore_conditions = []
|
||||
for spec in specs:
|
||||
if depspec.owner:
|
||||
ignore_conditions.append(
|
||||
ci_strings_are_equal(depspec.owner, spec.owner)
|
||||
and ci_strings_are_equal(depspec.name, spec.name)
|
||||
)
|
||||
else:
|
||||
ignore_conditions.append(ci_strings_are_equal(depspec.name, spec.name))
|
||||
if not any(ignore_conditions):
|
||||
result.append(dep)
|
||||
return result
|
||||
|
||||
|
||||
def save_project_libdeps(project_dir, specs, environments=None, action="add"):
|
||||
config = ProjectConfig.get_instance(os.path.join(project_dir, "platformio.ini"))
|
||||
config.validate(environments)
|
||||
for env in config.envs():
|
||||
if environments and env not in environments:
|
||||
continue
|
||||
config.expand_interpolations = False
|
||||
candidates = []
|
||||
try:
|
||||
candidates = ignore_deps_by_specs(
|
||||
config.get("env:" + env, "lib_deps"), specs
|
||||
)
|
||||
except InvalidProjectConfError:
|
||||
pass
|
||||
if action == "add":
|
||||
candidates.extend(spec.as_dependency() for spec in specs)
|
||||
if candidates:
|
||||
result = []
|
||||
for item in candidates:
|
||||
item = item.strip()
|
||||
if item and item not in result:
|
||||
result.append(item)
|
||||
config.set("env:" + env, "lib_deps", result)
|
||||
elif config.has_option("env:" + env, "lib_deps"):
|
||||
config.remove_option("env:" + env, "lib_deps")
|
||||
config.save()
|
||||
@@ -1,165 +0,0 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
import json
|
||||
|
||||
import click
|
||||
from tabulate import tabulate
|
||||
|
||||
from platformio.clients.account import AccountClient
|
||||
from platformio.commands.account import validate_email, validate_username
|
||||
|
||||
|
||||
@click.group("org", short_help="Manage organizations")
|
||||
def cli():
|
||||
pass
|
||||
|
||||
|
||||
def validate_orgname(value):
|
||||
return validate_username(value, "Organization name")
|
||||
|
||||
|
||||
@cli.command("create", short_help="Create a new organization")
|
||||
@click.argument(
|
||||
"orgname",
|
||||
callback=lambda _, __, value: validate_orgname(value),
|
||||
)
|
||||
@click.option(
|
||||
"--email", callback=lambda _, __, value: validate_email(value) if value else value
|
||||
)
|
||||
@click.option(
|
||||
"--displayname",
|
||||
)
|
||||
def org_create(orgname, email, displayname):
|
||||
client = AccountClient()
|
||||
client.create_org(orgname, email, displayname)
|
||||
return click.secho(
|
||||
"The organization `%s` has been successfully created." % orgname,
|
||||
fg="green",
|
||||
)
|
||||
|
||||
|
||||
@cli.command("list", short_help="List organizations and their members")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def org_list(json_output):
|
||||
client = AccountClient()
|
||||
orgs = client.list_orgs()
|
||||
if json_output:
|
||||
return click.echo(json.dumps(orgs))
|
||||
if not orgs:
|
||||
return click.echo("You do not have any organization")
|
||||
for org in orgs:
|
||||
click.echo()
|
||||
click.secho(org.get("orgname"), fg="cyan")
|
||||
click.echo("-" * len(org.get("orgname")))
|
||||
data = []
|
||||
if org.get("displayname"):
|
||||
data.append(("Display Name:", org.get("displayname")))
|
||||
if org.get("email"):
|
||||
data.append(("Email:", org.get("email")))
|
||||
data.append(
|
||||
(
|
||||
"Owners:",
|
||||
", ".join((owner.get("username") for owner in org.get("owners"))),
|
||||
)
|
||||
)
|
||||
click.echo(tabulate(data, tablefmt="plain"))
|
||||
return click.echo()
|
||||
|
||||
|
||||
@cli.command("update", short_help="Update organization")
|
||||
@click.argument("cur_orgname")
|
||||
@click.option(
|
||||
"--orgname",
|
||||
callback=lambda _, __, value: validate_orgname(value),
|
||||
help="A new orgname",
|
||||
)
|
||||
@click.option("--email")
|
||||
@click.option("--displayname")
|
||||
def org_update(cur_orgname, **kwargs):
|
||||
client = AccountClient()
|
||||
org = client.get_org(cur_orgname)
|
||||
del org["owners"]
|
||||
new_org = org.copy()
|
||||
if not any(kwargs.values()):
|
||||
for field in org:
|
||||
new_org[field] = click.prompt(
|
||||
field.replace("_", " ").capitalize(), default=org[field]
|
||||
)
|
||||
if field == "email":
|
||||
validate_email(new_org[field])
|
||||
if field == "orgname":
|
||||
validate_orgname(new_org[field])
|
||||
else:
|
||||
new_org.update(
|
||||
{key.replace("new_", ""): value for key, value in kwargs.items() if value}
|
||||
)
|
||||
client.update_org(cur_orgname, new_org)
|
||||
return click.secho(
|
||||
"The organization `%s` has been successfully updated." % cur_orgname,
|
||||
fg="green",
|
||||
)
|
||||
|
||||
|
||||
@cli.command("destroy", short_help="Destroy organization")
|
||||
@click.argument("orgname")
|
||||
def account_destroy(orgname):
|
||||
client = AccountClient()
|
||||
click.confirm(
|
||||
"Are you sure you want to delete the `%s` organization account?\n"
|
||||
"Warning! All linked data will be permanently removed and can not be restored."
|
||||
% orgname,
|
||||
abort=True,
|
||||
)
|
||||
client.destroy_org(orgname)
|
||||
return click.secho(
|
||||
"Organization `%s` has been destroyed." % orgname,
|
||||
fg="green",
|
||||
)
|
||||
|
||||
|
||||
@cli.command("add", short_help="Add a new owner to organization")
|
||||
@click.argument(
|
||||
"orgname",
|
||||
)
|
||||
@click.argument(
|
||||
"username",
|
||||
)
|
||||
def org_add_owner(orgname, username):
|
||||
client = AccountClient()
|
||||
client.add_org_owner(orgname, username)
|
||||
return click.secho(
|
||||
"The new owner `%s` has been successfully added to the `%s` organization."
|
||||
% (username, orgname),
|
||||
fg="green",
|
||||
)
|
||||
|
||||
|
||||
@cli.command("remove", short_help="Remove an owner from organization")
|
||||
@click.argument(
|
||||
"orgname",
|
||||
)
|
||||
@click.argument(
|
||||
"username",
|
||||
)
|
||||
def org_remove_owner(orgname, username):
|
||||
client = AccountClient()
|
||||
client.remove_org_owner(orgname, username)
|
||||
return click.secho(
|
||||
"The `%s` owner has been successfully removed from the `%s` organization."
|
||||
% (username, orgname),
|
||||
fg="green",
|
||||
)
|
||||
@@ -1,131 +0,0 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
|
||||
import click
|
||||
|
||||
from platformio import fs
|
||||
from platformio.clients.registry import RegistryClient
|
||||
from platformio.compat import ensure_python3
|
||||
from platformio.package.meta import PackageSpec, PackageType
|
||||
from platformio.package.pack import PackagePacker
|
||||
from platformio.package.unpack import FileUnpacker, TARArchiver
|
||||
|
||||
|
||||
def validate_datetime(ctx, param, value): # pylint: disable=unused-argument
|
||||
if not value:
|
||||
return value
|
||||
try:
|
||||
datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
|
||||
except ValueError as e:
|
||||
raise click.BadParameter(e)
|
||||
return value
|
||||
|
||||
|
||||
@click.group("package", short_help="Package manager")
|
||||
def cli():
|
||||
pass
|
||||
|
||||
|
||||
@cli.command("pack", short_help="Create a tarball from a package")
|
||||
@click.argument(
|
||||
"package",
|
||||
required=True,
|
||||
default=os.getcwd,
|
||||
metavar="<source directory, tar.gz or zip>",
|
||||
)
|
||||
@click.option(
|
||||
"-o", "--output", help="A destination path (folder or a full path to file)"
|
||||
)
|
||||
def package_pack(package, output):
|
||||
p = PackagePacker(package)
|
||||
archive_path = p.pack(output)
|
||||
click.secho('Wrote a tarball to "%s"' % archive_path, fg="green")
|
||||
|
||||
|
||||
@cli.command("publish", short_help="Publish a package to the registry")
|
||||
@click.argument(
|
||||
"package",
|
||||
required=True,
|
||||
default=os.getcwd,
|
||||
metavar="<source directory, tar.gz or zip>",
|
||||
)
|
||||
@click.option(
|
||||
"--owner",
|
||||
help="PIO Account username (can be organization username). "
|
||||
"Default is set to a username of the authorized PIO Account",
|
||||
)
|
||||
@click.option(
|
||||
"--released-at",
|
||||
callback=validate_datetime,
|
||||
help="Custom release date and time in the next format (UTC): 2014-06-13 17:08:52",
|
||||
)
|
||||
@click.option("--private", is_flag=True, help="Restricted access (not a public)")
|
||||
@click.option(
|
||||
"--notify/--no-notify",
|
||||
default=True,
|
||||
help="Notify by email when package is processed",
|
||||
)
|
||||
def package_publish(package, owner, released_at, private, notify):
|
||||
assert ensure_python3()
|
||||
|
||||
# publish .tar.gz instantly without repacking
|
||||
if not os.path.isdir(package) and isinstance(
|
||||
FileUnpacker.new_archiver(package), TARArchiver
|
||||
):
|
||||
response = RegistryClient().publish_package(
|
||||
package, owner, released_at, private, notify
|
||||
)
|
||||
click.secho(response.get("message"), fg="green")
|
||||
return
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp_dir: # pylint: disable=no-member
|
||||
with fs.cd(tmp_dir):
|
||||
p = PackagePacker(package)
|
||||
archive_path = p.pack()
|
||||
response = RegistryClient().publish_package(
|
||||
archive_path, owner, released_at, private, notify
|
||||
)
|
||||
os.remove(archive_path)
|
||||
click.secho(response.get("message"), fg="green")
|
||||
|
||||
|
||||
@cli.command("unpublish", short_help="Remove a pushed package from the registry")
|
||||
@click.argument(
|
||||
"package", required=True, metavar="[<organization>/]<pkgname>[@<version>]"
|
||||
)
|
||||
@click.option(
|
||||
"--type",
|
||||
type=click.Choice(list(PackageType.items().values())),
|
||||
default="library",
|
||||
help="Package type, default is set to `library`",
|
||||
)
|
||||
@click.option(
|
||||
"--undo",
|
||||
is_flag=True,
|
||||
help="Undo a remove, putting a version back into the registry",
|
||||
)
|
||||
def package_unpublish(package, type, undo): # pylint: disable=redefined-builtin
|
||||
spec = PackageSpec(package)
|
||||
response = RegistryClient().unpublish_package(
|
||||
type=type,
|
||||
name=spec.name,
|
||||
owner=spec.owner,
|
||||
version=str(spec.requirements),
|
||||
undo=undo,
|
||||
)
|
||||
click.secho(response.get("message"), fg="green")
|
||||
@@ -12,13 +12,19 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
import click
|
||||
|
||||
from platformio.cache import cleanup_content_cache
|
||||
from platformio.commands.boards import print_boards
|
||||
from platformio.compat import dump_json_to_unicode
|
||||
from platformio.exception import UserSideException
|
||||
from platformio.package.commands.install import package_install_cmd
|
||||
from platformio.package.commands.list import package_list_cmd
|
||||
from platformio.package.commands.search import package_search_cmd
|
||||
from platformio.package.commands.show import package_show_cmd
|
||||
from platformio.package.commands.uninstall import package_uninstall_cmd
|
||||
from platformio.package.commands.update import package_update_cmd
|
||||
from platformio.package.manager.platform import PlatformPackageManager
|
||||
from platformio.package.meta import PackageItem, PackageSpec
|
||||
from platformio.package.version import get_original_version
|
||||
@@ -26,40 +32,261 @@ from platformio.platform.exception import UnknownPlatform
|
||||
from platformio.platform.factory import PlatformFactory
|
||||
|
||||
|
||||
@click.group(short_help="Platform manager")
|
||||
@click.group(short_help="Platform manager", hidden=True)
|
||||
def cli():
|
||||
pass
|
||||
|
||||
|
||||
def _print_platforms(platforms):
|
||||
for platform in platforms:
|
||||
click.echo(
|
||||
"{name} ~ {title}".format(
|
||||
name=click.style(platform["name"], fg="cyan"), title=platform["title"]
|
||||
@cli.command("search", short_help="Search for development platform")
|
||||
@click.argument("query", required=False)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.pass_context
|
||||
def platform_search(ctx, query, json_output):
|
||||
if not json_output:
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg search` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
query = query or ""
|
||||
return ctx.invoke(package_search_cmd, query=f"type:platform {query}".strip())
|
||||
|
||||
platforms = []
|
||||
for platform in _get_registry_platforms():
|
||||
if query == "all":
|
||||
query = ""
|
||||
search_data = json.dumps(platform)
|
||||
if query and query.lower() not in search_data.lower():
|
||||
continue
|
||||
platforms.append(
|
||||
_get_registry_platform_data(
|
||||
platform["name"], with_boards=False, expose_packages=False
|
||||
)
|
||||
)
|
||||
click.echo("=" * (3 + len(platform["name"] + platform["title"])))
|
||||
click.echo(platform["description"])
|
||||
click.echo()
|
||||
if "homepage" in platform:
|
||||
click.echo("Home: %s" % platform["homepage"])
|
||||
if "frameworks" in platform and platform["frameworks"]:
|
||||
click.echo("Frameworks: %s" % ", ".join(platform["frameworks"]))
|
||||
if "packages" in platform:
|
||||
click.echo("Packages: %s" % ", ".join(platform["packages"]))
|
||||
if "version" in platform:
|
||||
if "__src_url" in platform:
|
||||
click.echo(
|
||||
"Version: %s (%s)" % (platform["version"], platform["__src_url"])
|
||||
)
|
||||
else:
|
||||
click.echo("Version: " + platform["version"])
|
||||
click.echo()
|
||||
click.echo(json.dumps(platforms))
|
||||
return None
|
||||
|
||||
|
||||
@cli.command("frameworks", short_help="List supported frameworks, SDKs")
|
||||
@click.argument("query", required=False)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def platform_frameworks(query, json_output):
|
||||
if not json_output:
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease visit https://docs.platformio.org"
|
||||
"/en/latest/frameworks/index.html\n",
|
||||
fg="yellow",
|
||||
)
|
||||
return
|
||||
|
||||
regclient = PlatformPackageManager().get_registry_client_instance()
|
||||
frameworks = []
|
||||
for framework in regclient.fetch_json_data(
|
||||
"get", "/v2/frameworks", x_cache_valid="1d"
|
||||
):
|
||||
if query == "all":
|
||||
query = ""
|
||||
search_data = json.dumps(framework)
|
||||
if query and query.lower() not in search_data.lower():
|
||||
continue
|
||||
framework["homepage"] = "https://platformio.org/frameworks/" + framework["name"]
|
||||
framework["platforms"] = [
|
||||
platform["name"]
|
||||
for platform in _get_registry_platforms()
|
||||
if framework["name"] in platform["frameworks"]
|
||||
]
|
||||
frameworks.append(framework)
|
||||
|
||||
frameworks = sorted(frameworks, key=lambda manifest: manifest["name"])
|
||||
click.echo(json.dumps(frameworks))
|
||||
|
||||
|
||||
@cli.command("list", short_help="List installed development platforms")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.pass_context
|
||||
def platform_list(ctx, json_output):
|
||||
if not json_output:
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg list` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
return ctx.invoke(package_list_cmd, **{"global": True, "only_platforms": True})
|
||||
|
||||
platforms = []
|
||||
pm = PlatformPackageManager()
|
||||
for pkg in pm.get_installed():
|
||||
platforms.append(
|
||||
_get_installed_platform_data(pkg, with_boards=False, expose_packages=False)
|
||||
)
|
||||
|
||||
platforms = sorted(platforms, key=lambda manifest: manifest["name"])
|
||||
click.echo(json.dumps(platforms))
|
||||
return None
|
||||
|
||||
|
||||
@cli.command("show", short_help="Show details about development platform")
|
||||
@click.argument("platform")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.pass_context
|
||||
def platform_show(ctx, platform, json_output): # pylint: disable=too-many-branches
|
||||
if not json_output:
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg show` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
return ctx.invoke(package_show_cmd, pkg_type="platform", spec=platform)
|
||||
|
||||
data = _get_platform_data(platform)
|
||||
if not data:
|
||||
raise UnknownPlatform(platform)
|
||||
return click.echo(json.dumps(data))
|
||||
|
||||
|
||||
@cli.command("install", short_help="Install new development platform")
|
||||
@click.argument("platforms", nargs=-1, required=True, metavar="[PLATFORM...]")
|
||||
@click.option("--with-package", multiple=True)
|
||||
@click.option("--without-package", multiple=True)
|
||||
@click.option("--skip-default-package", is_flag=True)
|
||||
@click.option("--with-all-packages", is_flag=True)
|
||||
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
|
||||
@click.option(
|
||||
"-f",
|
||||
"--force",
|
||||
is_flag=True,
|
||||
help="Reinstall/redownload dev/platform and its packages if exist",
|
||||
)
|
||||
@click.pass_context
|
||||
def platform_install( # pylint: disable=too-many-arguments
|
||||
ctx,
|
||||
platforms,
|
||||
with_package,
|
||||
without_package,
|
||||
skip_default_package,
|
||||
with_all_packages,
|
||||
silent,
|
||||
force,
|
||||
):
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg install` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
ctx.invoke(
|
||||
package_install_cmd,
|
||||
**{
|
||||
"global": True,
|
||||
"platforms": platforms,
|
||||
"skip_dependencies": (
|
||||
not with_all_packages
|
||||
and (with_package or without_package or skip_default_package)
|
||||
),
|
||||
"silent": silent,
|
||||
"force": force,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@cli.command("uninstall", short_help="Uninstall development platform")
|
||||
@click.argument("platforms", nargs=-1, required=True, metavar="[PLATFORM...]")
|
||||
@click.pass_context
|
||||
def platform_uninstall(ctx, platforms):
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg uninstall` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
ctx.invoke(
|
||||
package_uninstall_cmd,
|
||||
**{
|
||||
"global": True,
|
||||
"platforms": platforms,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@cli.command("update", short_help="Update installed development platforms")
|
||||
@click.argument("platforms", nargs=-1, required=False, metavar="[PLATFORM...]")
|
||||
@click.option(
|
||||
"-p", "--only-packages", is_flag=True, help="Update only the platform packages"
|
||||
)
|
||||
@click.option(
|
||||
"-c",
|
||||
"--only-check",
|
||||
is_flag=True,
|
||||
help="DEPRECATED. Please use `--dry-run` instead",
|
||||
)
|
||||
@click.option(
|
||||
"--dry-run", is_flag=True, help="Do not update, only check for the new versions"
|
||||
)
|
||||
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.pass_context
|
||||
def platform_update( # pylint: disable=too-many-locals, too-many-arguments
|
||||
ctx, platforms, only_check, dry_run, silent, json_output, **_
|
||||
):
|
||||
only_check = dry_run or only_check
|
||||
|
||||
if only_check and not json_output:
|
||||
raise UserSideException(
|
||||
"This command is deprecated, please use `pio pkg outdated` instead"
|
||||
)
|
||||
|
||||
if not json_output:
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg update` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
return ctx.invoke(
|
||||
package_update_cmd,
|
||||
**{
|
||||
"global": True,
|
||||
"platforms": platforms,
|
||||
"silent": silent,
|
||||
},
|
||||
)
|
||||
|
||||
pm = PlatformPackageManager()
|
||||
pm.set_log_level(logging.WARN if silent else logging.DEBUG)
|
||||
platforms = platforms or pm.get_installed()
|
||||
result = []
|
||||
for platform in platforms:
|
||||
spec = None
|
||||
pkg = None
|
||||
if isinstance(platform, PackageItem):
|
||||
pkg = platform
|
||||
else:
|
||||
spec = PackageSpec(platform)
|
||||
pkg = pm.get_package(spec)
|
||||
if not pkg:
|
||||
continue
|
||||
outdated = pm.outdated(pkg, spec)
|
||||
if (
|
||||
not outdated.is_outdated(allow_incompatible=True)
|
||||
and not PlatformFactory.new(pkg).are_outdated_packages()
|
||||
):
|
||||
continue
|
||||
data = _get_installed_platform_data(
|
||||
pkg, with_boards=False, expose_packages=False
|
||||
)
|
||||
if outdated.is_outdated(allow_incompatible=True):
|
||||
data["versionLatest"] = str(outdated.latest) if outdated.latest else None
|
||||
result.append(data)
|
||||
click.echo(json.dumps(result))
|
||||
return True
|
||||
|
||||
|
||||
#
|
||||
# Helpers
|
||||
#
|
||||
|
||||
|
||||
def _get_registry_platforms():
|
||||
regclient = PlatformPackageManager().get_registry_client_instance()
|
||||
return regclient.fetch_json_data("get", "/v2/platforms", cache_valid="1d")
|
||||
return regclient.fetch_json_data("get", "/v2/platforms", x_cache_valid="1d")
|
||||
|
||||
|
||||
def _get_platform_data(*args, **kwargs):
|
||||
@@ -162,264 +389,3 @@ def _get_registry_platform_data( # pylint: disable=unused-argument
|
||||
]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@cli.command("search", short_help="Search for development platform")
|
||||
@click.argument("query", required=False)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def platform_search(query, json_output):
|
||||
platforms = []
|
||||
for platform in _get_registry_platforms():
|
||||
if query == "all":
|
||||
query = ""
|
||||
search_data = dump_json_to_unicode(platform)
|
||||
if query and query.lower() not in search_data.lower():
|
||||
continue
|
||||
platforms.append(
|
||||
_get_registry_platform_data(
|
||||
platform["name"], with_boards=False, expose_packages=False
|
||||
)
|
||||
)
|
||||
|
||||
if json_output:
|
||||
click.echo(dump_json_to_unicode(platforms))
|
||||
else:
|
||||
_print_platforms(platforms)
|
||||
|
||||
|
||||
@cli.command("frameworks", short_help="List supported frameworks, SDKs")
|
||||
@click.argument("query", required=False)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def platform_frameworks(query, json_output):
|
||||
regclient = PlatformPackageManager().get_registry_client_instance()
|
||||
frameworks = []
|
||||
for framework in regclient.fetch_json_data(
|
||||
"get", "/v2/frameworks", cache_valid="1d"
|
||||
):
|
||||
if query == "all":
|
||||
query = ""
|
||||
search_data = dump_json_to_unicode(framework)
|
||||
if query and query.lower() not in search_data.lower():
|
||||
continue
|
||||
framework["homepage"] = "https://platformio.org/frameworks/" + framework["name"]
|
||||
framework["platforms"] = [
|
||||
platform["name"]
|
||||
for platform in _get_registry_platforms()
|
||||
if framework["name"] in platform["frameworks"]
|
||||
]
|
||||
frameworks.append(framework)
|
||||
|
||||
frameworks = sorted(frameworks, key=lambda manifest: manifest["name"])
|
||||
if json_output:
|
||||
click.echo(dump_json_to_unicode(frameworks))
|
||||
else:
|
||||
_print_platforms(frameworks)
|
||||
|
||||
|
||||
@cli.command("list", short_help="List installed development platforms")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def platform_list(json_output):
|
||||
platforms = []
|
||||
pm = PlatformPackageManager()
|
||||
for pkg in pm.get_installed():
|
||||
platforms.append(
|
||||
_get_installed_platform_data(pkg, with_boards=False, expose_packages=False)
|
||||
)
|
||||
|
||||
platforms = sorted(platforms, key=lambda manifest: manifest["name"])
|
||||
if json_output:
|
||||
click.echo(dump_json_to_unicode(platforms))
|
||||
else:
|
||||
_print_platforms(platforms)
|
||||
|
||||
|
||||
@cli.command("show", short_help="Show details about development platform")
|
||||
@click.argument("platform")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def platform_show(platform, json_output): # pylint: disable=too-many-branches
|
||||
data = _get_platform_data(platform)
|
||||
if not data:
|
||||
raise UnknownPlatform(platform)
|
||||
if json_output:
|
||||
return click.echo(dump_json_to_unicode(data))
|
||||
|
||||
dep = "{ownername}/{name}".format(**data) if "ownername" in data else data["name"]
|
||||
click.echo(
|
||||
"{dep} ~ {title}".format(dep=click.style(dep, fg="cyan"), title=data["title"])
|
||||
)
|
||||
click.echo("=" * (3 + len(dep + data["title"])))
|
||||
click.echo(data["description"])
|
||||
click.echo()
|
||||
if "version" in data:
|
||||
click.echo("Version: %s" % data["version"])
|
||||
if data["homepage"]:
|
||||
click.echo("Home: %s" % data["homepage"])
|
||||
if data["repository"]:
|
||||
click.echo("Repository: %s" % data["repository"])
|
||||
if data["url"]:
|
||||
click.echo("Vendor: %s" % data["url"])
|
||||
if data["license"]:
|
||||
click.echo("License: %s" % data["license"])
|
||||
if data["frameworks"]:
|
||||
click.echo("Frameworks: %s" % ", ".join(data["frameworks"]))
|
||||
|
||||
if not data["packages"]:
|
||||
return None
|
||||
|
||||
if not isinstance(data["packages"][0], dict):
|
||||
click.echo("Packages: %s" % ", ".join(data["packages"]))
|
||||
else:
|
||||
click.echo()
|
||||
click.secho("Packages", bold=True)
|
||||
click.echo("--------")
|
||||
for item in data["packages"]:
|
||||
click.echo()
|
||||
click.echo("Package %s" % click.style(item["name"], fg="yellow"))
|
||||
click.echo("-" * (8 + len(item["name"])))
|
||||
if item["type"]:
|
||||
click.echo("Type: %s" % item["type"])
|
||||
click.echo("Requirements: %s" % item["requirements"])
|
||||
click.echo(
|
||||
"Installed: %s" % ("Yes" if item.get("version") else "No (optional)")
|
||||
)
|
||||
if "version" in item:
|
||||
click.echo("Version: %s" % item["version"])
|
||||
if "originalVersion" in item:
|
||||
click.echo("Original version: %s" % item["originalVersion"])
|
||||
if "description" in item:
|
||||
click.echo("Description: %s" % item["description"])
|
||||
|
||||
if data["boards"]:
|
||||
click.echo()
|
||||
click.secho("Boards", bold=True)
|
||||
click.echo("------")
|
||||
print_boards(data["boards"])
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@cli.command("install", short_help="Install new development platform")
|
||||
@click.argument("platforms", nargs=-1, required=True, metavar="[PLATFORM...]")
|
||||
@click.option("--with-package", multiple=True)
|
||||
@click.option("--without-package", multiple=True)
|
||||
@click.option("--skip-default-package", is_flag=True)
|
||||
@click.option("--with-all-packages", is_flag=True)
|
||||
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
|
||||
@click.option(
|
||||
"-f",
|
||||
"--force",
|
||||
is_flag=True,
|
||||
help="Reinstall/redownload dev/platform and its packages if exist",
|
||||
)
|
||||
def platform_install( # pylint: disable=too-many-arguments
|
||||
platforms,
|
||||
with_package,
|
||||
without_package,
|
||||
skip_default_package,
|
||||
with_all_packages,
|
||||
silent,
|
||||
force,
|
||||
):
|
||||
pm = PlatformPackageManager()
|
||||
for platform in platforms:
|
||||
pkg = pm.install(
|
||||
spec=platform,
|
||||
with_packages=with_package,
|
||||
without_packages=without_package,
|
||||
skip_default_package=skip_default_package,
|
||||
with_all_packages=with_all_packages,
|
||||
silent=silent,
|
||||
force=force,
|
||||
)
|
||||
if pkg and not silent:
|
||||
click.secho(
|
||||
"The platform '%s' has been successfully installed!\n"
|
||||
"The rest of the packages will be installed later "
|
||||
"depending on your build environment." % platform,
|
||||
fg="green",
|
||||
)
|
||||
|
||||
|
||||
@cli.command("uninstall", short_help="Uninstall development platform")
|
||||
@click.argument("platforms", nargs=-1, required=True, metavar="[PLATFORM...]")
|
||||
def platform_uninstall(platforms):
|
||||
pm = PlatformPackageManager()
|
||||
for platform in platforms:
|
||||
if pm.uninstall(platform):
|
||||
click.secho(
|
||||
"The platform '%s' has been successfully removed!" % platform,
|
||||
fg="green",
|
||||
)
|
||||
|
||||
|
||||
@cli.command("update", short_help="Update installed development platforms")
|
||||
@click.argument("platforms", nargs=-1, required=False, metavar="[PLATFORM...]")
|
||||
@click.option(
|
||||
"-p", "--only-packages", is_flag=True, help="Update only the platform packages"
|
||||
)
|
||||
@click.option(
|
||||
"-c",
|
||||
"--only-check",
|
||||
is_flag=True,
|
||||
help="DEPRECATED. Please use `--dry-run` instead",
|
||||
)
|
||||
@click.option(
|
||||
"--dry-run", is_flag=True, help="Do not update, only check for the new versions"
|
||||
)
|
||||
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def platform_update( # pylint: disable=too-many-locals, too-many-arguments
|
||||
platforms, only_packages, only_check, dry_run, silent, json_output
|
||||
):
|
||||
pm = PlatformPackageManager()
|
||||
platforms = platforms or pm.get_installed()
|
||||
only_check = dry_run or only_check
|
||||
|
||||
if only_check and json_output:
|
||||
result = []
|
||||
for platform in platforms:
|
||||
spec = None
|
||||
pkg = None
|
||||
if isinstance(platform, PackageItem):
|
||||
pkg = platform
|
||||
else:
|
||||
spec = PackageSpec(platform)
|
||||
pkg = pm.get_package(spec)
|
||||
if not pkg:
|
||||
continue
|
||||
outdated = pm.outdated(pkg, spec)
|
||||
if (
|
||||
not outdated.is_outdated(allow_incompatible=True)
|
||||
and not PlatformFactory.new(pkg).are_outdated_packages()
|
||||
):
|
||||
continue
|
||||
data = _get_installed_platform_data(
|
||||
pkg, with_boards=False, expose_packages=False
|
||||
)
|
||||
if outdated.is_outdated(allow_incompatible=True):
|
||||
data["versionLatest"] = (
|
||||
str(outdated.latest) if outdated.latest else None
|
||||
)
|
||||
result.append(data)
|
||||
return click.echo(dump_json_to_unicode(result))
|
||||
|
||||
# cleanup cached board and platform lists
|
||||
cleanup_content_cache("http")
|
||||
|
||||
for platform in platforms:
|
||||
click.echo(
|
||||
"Platform %s"
|
||||
% click.style(
|
||||
platform.metadata.name
|
||||
if isinstance(platform, PackageItem)
|
||||
else platform,
|
||||
fg="cyan",
|
||||
)
|
||||
)
|
||||
click.echo("--------")
|
||||
pm.update(
|
||||
platform, only_packages=only_packages, only_check=only_check, silent=silent
|
||||
)
|
||||
click.echo()
|
||||
|
||||
return True
|
||||
|
||||
@@ -1,214 +0,0 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import click
|
||||
from tabulate import tabulate
|
||||
|
||||
from platformio import __version__, compat, fs, proc, util
|
||||
from platformio.commands.system.completion import (
|
||||
get_completion_install_path,
|
||||
install_completion_code,
|
||||
uninstall_completion_code,
|
||||
)
|
||||
from platformio.commands.system.prune import (
|
||||
prune_cached_data,
|
||||
prune_core_packages,
|
||||
prune_platform_packages,
|
||||
)
|
||||
from platformio.package.manager.library import LibraryPackageManager
|
||||
from platformio.package.manager.platform import PlatformPackageManager
|
||||
from platformio.package.manager.tool import ToolPackageManager
|
||||
from platformio.project.config import ProjectConfig
|
||||
|
||||
|
||||
@click.group("system", short_help="Miscellaneous system commands")
|
||||
def cli():
|
||||
pass
|
||||
|
||||
|
||||
@cli.command("info", short_help="Display system-wide information")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def system_info(json_output):
|
||||
project_config = ProjectConfig()
|
||||
data = {}
|
||||
data["core_version"] = {"title": "PlatformIO Core", "value": __version__}
|
||||
data["python_version"] = {
|
||||
"title": "Python",
|
||||
"value": "{0}.{1}.{2}-{3}.{4}".format(*list(sys.version_info)),
|
||||
}
|
||||
data["system"] = {"title": "System Type", "value": util.get_systype()}
|
||||
data["platform"] = {"title": "Platform", "value": platform.platform(terse=True)}
|
||||
data["filesystem_encoding"] = {
|
||||
"title": "File System Encoding",
|
||||
"value": compat.get_filesystem_encoding(),
|
||||
}
|
||||
data["locale_encoding"] = {
|
||||
"title": "Locale Encoding",
|
||||
"value": compat.get_locale_encoding(),
|
||||
}
|
||||
data["core_dir"] = {
|
||||
"title": "PlatformIO Core Directory",
|
||||
"value": project_config.get_optional_dir("core"),
|
||||
}
|
||||
data["platformio_exe"] = {
|
||||
"title": "PlatformIO Core Executable",
|
||||
"value": proc.where_is_program(
|
||||
"platformio.exe" if proc.WINDOWS else "platformio"
|
||||
),
|
||||
}
|
||||
data["python_exe"] = {
|
||||
"title": "Python Executable",
|
||||
"value": proc.get_pythonexe_path(),
|
||||
}
|
||||
data["global_lib_nums"] = {
|
||||
"title": "Global Libraries",
|
||||
"value": len(LibraryPackageManager().get_installed()),
|
||||
}
|
||||
data["dev_platform_nums"] = {
|
||||
"title": "Development Platforms",
|
||||
"value": len(PlatformPackageManager().get_installed()),
|
||||
}
|
||||
data["package_tool_nums"] = {
|
||||
"title": "Tools & Toolchains",
|
||||
"value": len(
|
||||
ToolPackageManager(
|
||||
project_config.get_optional_dir("packages")
|
||||
).get_installed()
|
||||
),
|
||||
}
|
||||
|
||||
click.echo(
|
||||
json.dumps(data)
|
||||
if json_output
|
||||
else tabulate([(item["title"], item["value"]) for item in data.values()])
|
||||
)
|
||||
|
||||
|
||||
@cli.command("prune", short_help="Remove unused data")
|
||||
@click.option("--force", "-f", is_flag=True, help="Do not prompt for confirmation")
|
||||
@click.option(
|
||||
"--dry-run", is_flag=True, help="Do not prune, only show data that will be removed"
|
||||
)
|
||||
@click.option("--cache", is_flag=True, help="Prune only cached data")
|
||||
@click.option(
|
||||
"--core-packages", is_flag=True, help="Prune only unnecessary core packages"
|
||||
)
|
||||
@click.option(
|
||||
"--platform-packages",
|
||||
is_flag=True,
|
||||
help="Prune only unnecessary development platform packages",
|
||||
)
|
||||
def system_prune(force, dry_run, cache, core_packages, platform_packages):
|
||||
if dry_run:
|
||||
click.secho(
|
||||
"Dry run mode (do not prune, only show data that will be removed)",
|
||||
fg="yellow",
|
||||
)
|
||||
click.echo()
|
||||
|
||||
reclaimed_cache = 0
|
||||
reclaimed_core_packages = 0
|
||||
reclaimed_platform_packages = 0
|
||||
prune_all = not any([cache, core_packages, platform_packages])
|
||||
|
||||
if cache or prune_all:
|
||||
reclaimed_cache = prune_cached_data(force, dry_run)
|
||||
click.echo()
|
||||
|
||||
if core_packages or prune_all:
|
||||
reclaimed_core_packages = prune_core_packages(force, dry_run)
|
||||
click.echo()
|
||||
|
||||
if platform_packages or prune_all:
|
||||
reclaimed_platform_packages = prune_platform_packages(force, dry_run)
|
||||
click.echo()
|
||||
|
||||
click.secho(
|
||||
"Total reclaimed space: %s"
|
||||
% fs.humanize_file_size(
|
||||
reclaimed_cache + reclaimed_core_packages + reclaimed_platform_packages
|
||||
),
|
||||
fg="green",
|
||||
)
|
||||
|
||||
|
||||
@cli.group("completion", short_help="Shell completion support")
|
||||
def completion():
|
||||
# pylint: disable=import-error,import-outside-toplevel
|
||||
try:
|
||||
import click_completion # pylint: disable=unused-import,unused-variable
|
||||
except ImportError:
|
||||
click.echo("Installing dependent packages...")
|
||||
subprocess.check_call(
|
||||
[proc.get_pythonexe_path(), "-m", "pip", "install", "click-completion"],
|
||||
)
|
||||
|
||||
|
||||
@completion.command("install", short_help="Install shell completion files/code")
|
||||
@click.option(
|
||||
"--shell",
|
||||
default=None,
|
||||
type=click.Choice(["fish", "bash", "zsh", "powershell", "auto"]),
|
||||
help="The shell type, default=auto",
|
||||
)
|
||||
@click.option(
|
||||
"--path",
|
||||
type=click.Path(file_okay=True, dir_okay=False, readable=True, resolve_path=True),
|
||||
help="Custom installation path of the code to be evaluated by the shell. "
|
||||
"The standard installation path is used by default.",
|
||||
)
|
||||
def completion_install(shell, path):
|
||||
|
||||
import click_completion # pylint: disable=import-outside-toplevel,import-error
|
||||
|
||||
shell = shell or click_completion.get_auto_shell()
|
||||
path = path or get_completion_install_path(shell)
|
||||
install_completion_code(shell, path)
|
||||
click.echo(
|
||||
"PlatformIO CLI completion has been installed for %s shell to %s \n"
|
||||
"Please restart a current shell session."
|
||||
% (click.style(shell, fg="cyan"), click.style(path, fg="blue"))
|
||||
)
|
||||
|
||||
|
||||
@completion.command("uninstall", short_help="Uninstall shell completion files/code")
|
||||
@click.option(
|
||||
"--shell",
|
||||
default=None,
|
||||
type=click.Choice(["fish", "bash", "zsh", "powershell", "auto"]),
|
||||
help="The shell type, default=auto",
|
||||
)
|
||||
@click.option(
|
||||
"--path",
|
||||
type=click.Path(file_okay=True, dir_okay=False, readable=True, resolve_path=True),
|
||||
help="Custom installation path of the code to be evaluated by the shell. "
|
||||
"The standard installation path is used by default.",
|
||||
)
|
||||
def completion_uninstall(shell, path):
|
||||
|
||||
import click_completion # pylint: disable=import-outside-toplevel,import-error
|
||||
|
||||
shell = shell or click_completion.get_auto_shell()
|
||||
path = path or get_completion_install_path(shell)
|
||||
uninstall_completion_code(shell, path)
|
||||
click.echo(
|
||||
"PlatformIO CLI completion has been uninstalled for %s shell from %s \n"
|
||||
"Please restart a current shell session."
|
||||
% (click.style(shell, fg="cyan"), click.style(path, fg="blue"))
|
||||
)
|
||||
@@ -1,73 +0,0 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import click
|
||||
|
||||
|
||||
def get_completion_install_path(shell):
|
||||
home_dir = os.path.expanduser("~")
|
||||
prog_name = click.get_current_context().find_root().info_name
|
||||
if shell == "fish":
|
||||
return os.path.join(
|
||||
home_dir, ".config", "fish", "completions", "%s.fish" % prog_name
|
||||
)
|
||||
if shell == "bash":
|
||||
return os.path.join(home_dir, ".bash_completion")
|
||||
if shell == "zsh":
|
||||
return os.path.join(home_dir, ".zshrc")
|
||||
if shell == "powershell":
|
||||
return subprocess.check_output(
|
||||
["powershell", "-NoProfile", "echo $profile"]
|
||||
).strip()
|
||||
raise click.ClickException("%s is not supported." % shell)
|
||||
|
||||
|
||||
def is_completion_code_installed(shell, path):
|
||||
if shell == "fish" or not os.path.exists(path):
|
||||
return False
|
||||
|
||||
import click_completion # pylint: disable=import-error,import-outside-toplevel
|
||||
|
||||
with open(path) as fp:
|
||||
return click_completion.get_code(shell=shell) in fp.read()
|
||||
|
||||
|
||||
def install_completion_code(shell, path):
|
||||
import click_completion # pylint: disable=import-error,import-outside-toplevel
|
||||
|
||||
if is_completion_code_installed(shell, path):
|
||||
return None
|
||||
|
||||
return click_completion.install(shell=shell, path=path, append=shell != "fish")
|
||||
|
||||
|
||||
def uninstall_completion_code(shell, path):
|
||||
if not os.path.exists(path):
|
||||
return True
|
||||
if shell == "fish":
|
||||
os.remove(path)
|
||||
return True
|
||||
|
||||
import click_completion # pylint: disable=import-error,import-outside-toplevel
|
||||
|
||||
with open(path, "r+") as fp:
|
||||
contents = fp.read()
|
||||
fp.seek(0)
|
||||
fp.truncate()
|
||||
fp.write(contents.replace(click_completion.get_code(shell=shell), ""))
|
||||
|
||||
return True
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user