circup 2.2.6__py3-none-any.whl → 2.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- circup/bundle.py +57 -6
- circup/command_utils.py +171 -32
- circup/commands.py +85 -15
- circup/shared.py +0 -24
- {circup-2.2.6.dist-info → circup-2.3.0.dist-info}/METADATA +1 -1
- {circup-2.2.6.dist-info → circup-2.3.0.dist-info}/RECORD +10 -10
- {circup-2.2.6.dist-info → circup-2.3.0.dist-info}/WHEEL +0 -0
- {circup-2.2.6.dist-info → circup-2.3.0.dist-info}/entry_points.txt +0 -0
- {circup-2.2.6.dist-info → circup-2.3.0.dist-info}/licenses/LICENSE +0 -0
- {circup-2.2.6.dist-info → circup-2.3.0.dist-info}/top_level.txt +0 -0
circup/bundle.py
CHANGED
|
@@ -14,7 +14,6 @@ from circup.shared import (
|
|
|
14
14
|
DATA_DIR,
|
|
15
15
|
PLATFORMS,
|
|
16
16
|
REQUESTS_TIMEOUT,
|
|
17
|
-
tags_data_load,
|
|
18
17
|
get_latest_release_from_url,
|
|
19
18
|
)
|
|
20
19
|
|
|
@@ -46,6 +45,8 @@ class Bundle:
|
|
|
46
45
|
# tag
|
|
47
46
|
self._current = None
|
|
48
47
|
self._latest = None
|
|
48
|
+
self.pinned_tag = None
|
|
49
|
+
self._available = []
|
|
49
50
|
|
|
50
51
|
def lib_dir(self, platform):
|
|
51
52
|
"""
|
|
@@ -99,21 +100,27 @@ class Bundle:
|
|
|
99
100
|
@property
|
|
100
101
|
def current_tag(self):
|
|
101
102
|
"""
|
|
102
|
-
|
|
103
|
+
The current tag for the project. If the tag hasn't been explicitly set
|
|
104
|
+
this will be the pinned tag, if one is set. If there is no pinned tag,
|
|
105
|
+
this will be the latest available tag that is locally available.
|
|
103
106
|
|
|
104
|
-
:return: The current
|
|
107
|
+
:return: The current tag value for the project.
|
|
105
108
|
"""
|
|
106
109
|
if self._current is None:
|
|
107
|
-
self._current =
|
|
110
|
+
self._current = self.pinned_tag or (
|
|
111
|
+
# This represents the latest version locally available
|
|
112
|
+
self._available[-1]
|
|
113
|
+
if len(self._available) > 0
|
|
114
|
+
else None
|
|
115
|
+
)
|
|
108
116
|
return self._current
|
|
109
117
|
|
|
110
118
|
@current_tag.setter
|
|
111
119
|
def current_tag(self, tag):
|
|
112
120
|
"""
|
|
113
|
-
Set the current
|
|
121
|
+
Set the current tag (after updating).
|
|
114
122
|
|
|
115
123
|
:param str tag: The new value for the current tag.
|
|
116
|
-
:return: The current cached tag value for the project.
|
|
117
124
|
"""
|
|
118
125
|
self._current = tag
|
|
119
126
|
|
|
@@ -130,6 +137,48 @@ class Bundle:
|
|
|
130
137
|
)
|
|
131
138
|
return self._latest
|
|
132
139
|
|
|
140
|
+
@property
|
|
141
|
+
def available_tags(self):
|
|
142
|
+
"""
|
|
143
|
+
The locally available tags to use for the project.
|
|
144
|
+
|
|
145
|
+
:return: All tags available for the project.
|
|
146
|
+
"""
|
|
147
|
+
return tuple(self._available)
|
|
148
|
+
|
|
149
|
+
@available_tags.setter
|
|
150
|
+
def available_tags(self, tags):
|
|
151
|
+
"""
|
|
152
|
+
Set the available tags.
|
|
153
|
+
|
|
154
|
+
:param str|list tags: The new value for the locally available tags.
|
|
155
|
+
"""
|
|
156
|
+
if isinstance(tags, str):
|
|
157
|
+
tags = [tags]
|
|
158
|
+
self._available = sorted(tags)
|
|
159
|
+
|
|
160
|
+
def add_tag(self, tag: str) -> None:
|
|
161
|
+
"""
|
|
162
|
+
Add a tag to the list of available tags.
|
|
163
|
+
|
|
164
|
+
This will add the tag if it isn't already present in the list of
|
|
165
|
+
available tags. The tag will be added so that the list is sorted in an
|
|
166
|
+
increasing order. This ensures that that last tag is always the latest.
|
|
167
|
+
|
|
168
|
+
:param str tag: The tag to add to the list of available tags.
|
|
169
|
+
"""
|
|
170
|
+
if tag in self._available:
|
|
171
|
+
# The tag is already stored for some reason, lets not add it again
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
for rev_i, available_tag in enumerate(reversed(self._available)):
|
|
175
|
+
if int(tag) > int(available_tag):
|
|
176
|
+
i = len(self._available) - rev_i
|
|
177
|
+
self._available.insert(i, tag)
|
|
178
|
+
break
|
|
179
|
+
else:
|
|
180
|
+
self._available.insert(0, tag)
|
|
181
|
+
|
|
133
182
|
def validate(self):
|
|
134
183
|
"""
|
|
135
184
|
Test the existence of the expected URLs (not their content)
|
|
@@ -166,5 +215,7 @@ class Bundle:
|
|
|
166
215
|
"url_format": self.url_format,
|
|
167
216
|
"current": self._current,
|
|
168
217
|
"latest": self._latest,
|
|
218
|
+
"pinned": self.pinned_tag,
|
|
219
|
+
"available": self._available,
|
|
169
220
|
}
|
|
170
221
|
)
|
circup/command_utils.py
CHANGED
|
@@ -12,10 +12,10 @@ import os
|
|
|
12
12
|
|
|
13
13
|
from subprocess import check_output
|
|
14
14
|
import sys
|
|
15
|
-
import shutil
|
|
16
15
|
import zipfile
|
|
17
16
|
import json
|
|
18
17
|
import re
|
|
18
|
+
from pathlib import Path
|
|
19
19
|
import toml
|
|
20
20
|
import requests
|
|
21
21
|
import click
|
|
@@ -29,7 +29,6 @@ from circup.shared import (
|
|
|
29
29
|
BUNDLE_CONFIG_LOCAL,
|
|
30
30
|
BUNDLE_DATA,
|
|
31
31
|
NOT_MCU_LIBRARIES,
|
|
32
|
-
tags_data_load,
|
|
33
32
|
)
|
|
34
33
|
from circup.logging import logger
|
|
35
34
|
from circup.module import Module
|
|
@@ -106,7 +105,7 @@ def completion_for_install(ctx, param, incomplete):
|
|
|
106
105
|
with the ``circup install`` command.
|
|
107
106
|
"""
|
|
108
107
|
# pylint: disable=unused-argument
|
|
109
|
-
available_modules = get_bundle_versions(get_bundles_list(), avoid_download=True)
|
|
108
|
+
available_modules = get_bundle_versions(get_bundles_list(None), avoid_download=True)
|
|
110
109
|
module_names = {m.replace(".py", "") for m in available_modules}
|
|
111
110
|
if incomplete:
|
|
112
111
|
module_names = [name for name in module_names if name.startswith(incomplete)]
|
|
@@ -121,7 +120,9 @@ def completion_for_example(ctx, param, incomplete):
|
|
|
121
120
|
"""
|
|
122
121
|
|
|
123
122
|
# pylint: disable=unused-argument, consider-iterating-dictionary
|
|
124
|
-
available_examples = get_bundle_examples(
|
|
123
|
+
available_examples = get_bundle_examples(
|
|
124
|
+
get_bundles_list(None), avoid_download=True
|
|
125
|
+
)
|
|
125
126
|
matching_examples = []
|
|
126
127
|
for term in incomplete:
|
|
127
128
|
_examples = [
|
|
@@ -134,17 +135,18 @@ def completion_for_example(ctx, param, incomplete):
|
|
|
134
135
|
return sorted(matching_examples)
|
|
135
136
|
|
|
136
137
|
|
|
137
|
-
def
|
|
138
|
+
def ensure_bundle_tag(bundle, tag):
|
|
138
139
|
"""
|
|
139
|
-
Ensure that there's a copy of the
|
|
140
|
-
|
|
140
|
+
Ensure that there's a copy of the library bundle with the version referenced
|
|
141
|
+
by the tag.
|
|
141
142
|
|
|
142
143
|
:param Bundle bundle: the target Bundle object.
|
|
144
|
+
:param tag: the target bundle's tag (version).
|
|
145
|
+
|
|
146
|
+
:return: If the bundle is available.
|
|
143
147
|
"""
|
|
144
|
-
logger.info("Checking library updates for %s.", bundle.key)
|
|
145
|
-
tag = bundle.latest_tag
|
|
146
148
|
do_update = False
|
|
147
|
-
if tag
|
|
149
|
+
if tag in bundle.available_tags:
|
|
148
150
|
for platform in PLATFORMS:
|
|
149
151
|
# missing directories (new platform added on an existing install
|
|
150
152
|
# or side effect of pytest or network errors)
|
|
@@ -156,19 +158,78 @@ def ensure_latest_bundle(bundle):
|
|
|
156
158
|
logger.info("New version available (%s).", tag)
|
|
157
159
|
try:
|
|
158
160
|
get_bundle(bundle, tag)
|
|
159
|
-
|
|
161
|
+
tags_data_save_tags(bundle.key, bundle.available_tags)
|
|
160
162
|
except requests.exceptions.HTTPError as ex:
|
|
161
|
-
# See #20 for reason for this
|
|
162
163
|
click.secho(
|
|
163
|
-
|
|
164
|
-
"There was a problem downloading that platform bundle. "
|
|
165
|
-
"Skipping and using existing download if available."
|
|
166
|
-
),
|
|
167
|
-
fg="red",
|
|
164
|
+
f"There was a problem downloading the {bundle.key} bundle.", fg="red"
|
|
168
165
|
)
|
|
169
166
|
logger.exception(ex)
|
|
167
|
+
return False
|
|
170
168
|
else:
|
|
171
|
-
logger.info("Current bundle
|
|
169
|
+
logger.info("Current bundle version available (%s).", tag)
|
|
170
|
+
return True
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def ensure_latest_bundle(bundle):
|
|
174
|
+
"""
|
|
175
|
+
Ensure that there's a copy of the latest library bundle available so circup
|
|
176
|
+
can check the metadata contained therein.
|
|
177
|
+
|
|
178
|
+
:param Bundle bundle: the target Bundle object.
|
|
179
|
+
"""
|
|
180
|
+
logger.info("Checking library updates for %s.", bundle.key)
|
|
181
|
+
tag = bundle.latest_tag
|
|
182
|
+
is_available = ensure_bundle_tag(bundle, tag)
|
|
183
|
+
if is_available:
|
|
184
|
+
click.echo(f"Using latest bundle for {bundle.key} ({tag}).")
|
|
185
|
+
else:
|
|
186
|
+
if bundle.current_tag is None:
|
|
187
|
+
# See issue #20 for reason for this
|
|
188
|
+
click.secho("Please try again in a moment.", fg="red")
|
|
189
|
+
sys.exit(1)
|
|
190
|
+
else:
|
|
191
|
+
# See PR #184 for reason for this
|
|
192
|
+
click.secho(
|
|
193
|
+
f"Skipping and using existing bundle for {bundle.key} ({bundle.current_tag}).",
|
|
194
|
+
fg="red",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def ensure_pinned_bundle(bundle):
|
|
199
|
+
"""
|
|
200
|
+
Ensure that there's a copy of the pinned library bundle available so circup
|
|
201
|
+
can check the metadata contained therein.
|
|
202
|
+
|
|
203
|
+
:param Bundle bundle: the target Bundle object.
|
|
204
|
+
"""
|
|
205
|
+
logger.info("Checking library for %s.", bundle.key)
|
|
206
|
+
tag = bundle.pinned_tag
|
|
207
|
+
is_available = ensure_bundle_tag(bundle, tag)
|
|
208
|
+
if is_available:
|
|
209
|
+
click.echo(f"Using pinned bundle for {bundle.key} ({tag}).")
|
|
210
|
+
else:
|
|
211
|
+
click.secho(
|
|
212
|
+
(
|
|
213
|
+
"Check pinned version to make sure it is correct and check "
|
|
214
|
+
f"{bundle.url} to make sure the version ({tag}) exists."
|
|
215
|
+
),
|
|
216
|
+
fg="red",
|
|
217
|
+
)
|
|
218
|
+
sys.exit(1)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def ensure_bundle(bundle):
|
|
222
|
+
"""
|
|
223
|
+
Ensure that there's a copy of either the pinned library bundle, or if no
|
|
224
|
+
version is pinned, the latest library bundle available so circup can check
|
|
225
|
+
the metadata contained therein.
|
|
226
|
+
|
|
227
|
+
:param Bundle bundle: the target Bundle object.
|
|
228
|
+
"""
|
|
229
|
+
if bundle.pinned_tag is not None:
|
|
230
|
+
ensure_pinned_bundle(bundle)
|
|
231
|
+
else:
|
|
232
|
+
ensure_latest_bundle(bundle)
|
|
172
233
|
|
|
173
234
|
|
|
174
235
|
def find_device():
|
|
@@ -301,7 +362,7 @@ def get_bundle(bundle, tag):
|
|
|
301
362
|
:param Bundle bundle: the target Bundle object.
|
|
302
363
|
:param str tag: The GIT tag to use to download the bundle.
|
|
303
364
|
"""
|
|
304
|
-
click.echo(f"Downloading
|
|
365
|
+
click.echo(f"Downloading bundles for {bundle.key} ({tag}).")
|
|
305
366
|
for platform, github_string in PLATFORMS.items():
|
|
306
367
|
# Report the platform: "8.x-mpy", etc.
|
|
307
368
|
click.echo(f"{github_string}:")
|
|
@@ -323,10 +384,9 @@ def get_bundle(bundle, tag):
|
|
|
323
384
|
pbar.update(len(chunk))
|
|
324
385
|
logger.info("Saved to %s", temp_zip)
|
|
325
386
|
temp_dir = bundle.dir.format(platform=platform)
|
|
326
|
-
if os.path.isdir(temp_dir):
|
|
327
|
-
shutil.rmtree(temp_dir)
|
|
328
387
|
with zipfile.ZipFile(temp_zip, "r") as zfile:
|
|
329
388
|
zfile.extractall(temp_dir)
|
|
389
|
+
bundle.add_tag(tag)
|
|
330
390
|
bundle.current_tag = tag
|
|
331
391
|
click.echo("\nOK\n")
|
|
332
392
|
|
|
@@ -348,7 +408,7 @@ def get_bundle_examples(bundles_list, avoid_download=False):
|
|
|
348
408
|
try:
|
|
349
409
|
for bundle in bundles_list:
|
|
350
410
|
if not avoid_download or not os.path.isdir(bundle.lib_dir("py")):
|
|
351
|
-
|
|
411
|
+
ensure_bundle(bundle)
|
|
352
412
|
path = bundle.examples_dir("py")
|
|
353
413
|
meta_saved = os.path.join(path, "../bundle_examples.json")
|
|
354
414
|
if os.path.exists(meta_saved):
|
|
@@ -383,9 +443,10 @@ def get_bundle_examples(bundles_list, avoid_download=False):
|
|
|
383
443
|
|
|
384
444
|
def get_bundle_versions(bundles_list, avoid_download=False):
|
|
385
445
|
"""
|
|
386
|
-
Returns a dictionary of metadata from modules in the
|
|
387
|
-
|
|
388
|
-
version
|
|
446
|
+
Returns a dictionary of metadata from modules in either the pinned release
|
|
447
|
+
if one is present in 'pyproject.toml', or the latest known release of the
|
|
448
|
+
library bundle. Uses the Python version (rather than the compiled version)
|
|
449
|
+
of the library modules.
|
|
389
450
|
|
|
390
451
|
:param List[Bundle] bundles_list: List of supported bundles as Bundle objects.
|
|
391
452
|
:param bool avoid_download: if True, download the bundle only if missing.
|
|
@@ -395,7 +456,7 @@ def get_bundle_versions(bundles_list, avoid_download=False):
|
|
|
395
456
|
all_the_modules = dict()
|
|
396
457
|
for bundle in bundles_list:
|
|
397
458
|
if not avoid_download or not os.path.isdir(bundle.lib_dir("py")):
|
|
398
|
-
|
|
459
|
+
ensure_bundle(bundle)
|
|
399
460
|
path = bundle.lib_dir("py")
|
|
400
461
|
path_modules = _get_modules_file(path, logger)
|
|
401
462
|
for name, module in path_modules.items():
|
|
@@ -443,14 +504,29 @@ def get_bundles_local_dict():
|
|
|
443
504
|
return dict()
|
|
444
505
|
|
|
445
506
|
|
|
446
|
-
def get_bundles_list():
|
|
507
|
+
def get_bundles_list(bundle_tags):
|
|
447
508
|
"""
|
|
448
509
|
Retrieve the list of bundles from the config dictionary.
|
|
449
510
|
|
|
511
|
+
:param Dict[str,str]|None bundle_tags: Pinned bundle tags. These override
|
|
512
|
+
any tags found in the pyproject.toml.
|
|
450
513
|
:return: List of supported bundles as Bundle objects.
|
|
451
514
|
"""
|
|
452
515
|
bundle_config = get_bundles_dict()
|
|
516
|
+
tags = tags_data_load()
|
|
517
|
+
pyproject = find_pyproject()
|
|
518
|
+
pinned_tags = (
|
|
519
|
+
pyproject_bundle_versions(pyproject) if pyproject is not None else None
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
if bundle_tags is not None:
|
|
523
|
+
pinned_tags = bundle_tags if pinned_tags is None else pinned_tags | bundle_tags
|
|
524
|
+
|
|
453
525
|
bundles_list = [Bundle(bundle_config[b]) for b in bundle_config]
|
|
526
|
+
for bundle in bundles_list:
|
|
527
|
+
bundle.available_tags = tags.get(bundle.key, [])
|
|
528
|
+
if pinned_tags is not None:
|
|
529
|
+
bundle.pinned_tag = pinned_tags.get(bundle.key)
|
|
454
530
|
logger.info("Using bundles: %s", ", ".join(b.key for b in bundles_list))
|
|
455
531
|
return bundles_list
|
|
456
532
|
|
|
@@ -613,15 +689,38 @@ def save_local_bundles(bundles_data):
|
|
|
613
689
|
os.unlink(BUNDLE_CONFIG_LOCAL)
|
|
614
690
|
|
|
615
691
|
|
|
616
|
-
def
|
|
692
|
+
def tags_data_load():
|
|
693
|
+
"""
|
|
694
|
+
Load the list of the version tags of the bundles on disk.
|
|
695
|
+
|
|
696
|
+
:return: a dict() of tags indexed by Bundle identifiers/keys.
|
|
697
|
+
"""
|
|
698
|
+
tags_data = None
|
|
699
|
+
try:
|
|
700
|
+
with open(BUNDLE_DATA, encoding="utf-8") as data:
|
|
701
|
+
try:
|
|
702
|
+
tags_data = json.load(data)
|
|
703
|
+
except json.decoder.JSONDecodeError as ex:
|
|
704
|
+
# Sometimes (why?) the JSON file becomes corrupt. In which case
|
|
705
|
+
# log it and carry on as if setting up for first time.
|
|
706
|
+
logger.error("Could not parse %s", BUNDLE_DATA)
|
|
707
|
+
logger.exception(ex)
|
|
708
|
+
except FileNotFoundError:
|
|
709
|
+
pass
|
|
710
|
+
if not isinstance(tags_data, dict):
|
|
711
|
+
tags_data = {}
|
|
712
|
+
return tags_data
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def tags_data_save_tags(key, tags):
|
|
617
716
|
"""
|
|
618
|
-
Add or change the saved
|
|
717
|
+
Add or change the saved available tags value for a bundle.
|
|
619
718
|
|
|
620
719
|
:param str key: The bundle's identifier/key.
|
|
621
|
-
:param str
|
|
720
|
+
:param List[str] tags: The new tags for the bundle.
|
|
622
721
|
"""
|
|
623
|
-
tags_data = tags_data_load(
|
|
624
|
-
tags_data[key] =
|
|
722
|
+
tags_data = tags_data_load()
|
|
723
|
+
tags_data[key] = tags
|
|
625
724
|
with open(BUNDLE_DATA, "w", encoding="utf-8") as data:
|
|
626
725
|
json.dump(tags_data, data)
|
|
627
726
|
|
|
@@ -855,3 +954,43 @@ def is_virtual_env_active():
|
|
|
855
954
|
virtual environment, regardless how circup is installed.
|
|
856
955
|
"""
|
|
857
956
|
return "VIRTUAL_ENV" in os.environ
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
def find_pyproject():
|
|
960
|
+
"""
|
|
961
|
+
Look for a pyproject.toml in the current directory or its parent directories.
|
|
962
|
+
|
|
963
|
+
:return: The path to the pyproject.toml for the project, or None if it
|
|
964
|
+
couldn't be found.
|
|
965
|
+
"""
|
|
966
|
+
logger.info("Looking for pyproject.toml file.")
|
|
967
|
+
cwd = Path.cwd()
|
|
968
|
+
candidates = [cwd, cwd.parent]
|
|
969
|
+
|
|
970
|
+
for path in candidates:
|
|
971
|
+
pyproject_file = path / "pyproject.toml"
|
|
972
|
+
|
|
973
|
+
if pyproject_file.exists():
|
|
974
|
+
logger.info("Found pyproject.toml at '%s'", str(pyproject_file))
|
|
975
|
+
return pyproject_file
|
|
976
|
+
|
|
977
|
+
logger.info("No pyproject.toml file found.")
|
|
978
|
+
return None
|
|
979
|
+
|
|
980
|
+
|
|
981
|
+
def pyproject_bundle_versions(pyproject_file):
|
|
982
|
+
"""
|
|
983
|
+
Check for specified bundle versions.
|
|
984
|
+
"""
|
|
985
|
+
pyproject_toml_data = toml.load(pyproject_file)
|
|
986
|
+
return pyproject_toml_data.get("tool", {}).get("circup", {}).get("bundle-versions")
|
|
987
|
+
|
|
988
|
+
|
|
989
|
+
def parse_cli_bundle_tags(bundle_tags_cli):
|
|
990
|
+
"""Parse bundle tags that were provided from the command line."""
|
|
991
|
+
bundle_tags = {}
|
|
992
|
+
for bundle_tag_item in bundle_tags_cli:
|
|
993
|
+
item = bundle_tag_item.split("=")
|
|
994
|
+
if len(item) == 2:
|
|
995
|
+
bundle_tags[item[0].strip()] = item[1].strip()
|
|
996
|
+
return bundle_tags if len(bundle_tags) > 0 else None
|
circup/commands.py
CHANGED
|
@@ -37,6 +37,7 @@ from circup.command_utils import (
|
|
|
37
37
|
libraries_from_auto_file,
|
|
38
38
|
get_dependencies,
|
|
39
39
|
get_bundles_local_dict,
|
|
40
|
+
parse_cli_bundle_tags,
|
|
40
41
|
save_local_bundles,
|
|
41
42
|
get_bundles_dict,
|
|
42
43
|
completion_for_example,
|
|
@@ -84,13 +85,32 @@ from circup.command_utils import (
|
|
|
84
85
|
help="Manual CircuitPython version. If provided in combination "
|
|
85
86
|
"with --board-id, it overrides the detected CPy version.",
|
|
86
87
|
)
|
|
88
|
+
@click.option(
|
|
89
|
+
"bundle_versions",
|
|
90
|
+
"--bundle-version",
|
|
91
|
+
multiple=True,
|
|
92
|
+
help="Specify the version to use for a bundle. Include the bundle name and "
|
|
93
|
+
"the version separated by '=', similar to the format of requirements.txt. "
|
|
94
|
+
"This option can be used multiple times for different bundles. Bundle "
|
|
95
|
+
"version values provided here will override any pinned values from the "
|
|
96
|
+
"pyproject.toml.",
|
|
97
|
+
)
|
|
87
98
|
@click.version_option(
|
|
88
99
|
prog_name="Circup",
|
|
89
100
|
message="%(prog)s, A CircuitPython module updater. Version %(version)s",
|
|
90
101
|
)
|
|
91
102
|
@click.pass_context
|
|
92
103
|
def main( # pylint: disable=too-many-locals
|
|
93
|
-
ctx,
|
|
104
|
+
ctx,
|
|
105
|
+
verbose,
|
|
106
|
+
path,
|
|
107
|
+
host,
|
|
108
|
+
port,
|
|
109
|
+
password,
|
|
110
|
+
timeout,
|
|
111
|
+
board_id,
|
|
112
|
+
cpy_version,
|
|
113
|
+
bundle_versions,
|
|
94
114
|
): # pragma: no cover
|
|
95
115
|
"""
|
|
96
116
|
A tool to manage and update libraries on a CircuitPython device.
|
|
@@ -98,6 +118,9 @@ def main( # pylint: disable=too-many-locals
|
|
|
98
118
|
# pylint: disable=too-many-arguments,too-many-branches,too-many-statements,too-many-locals, R0801
|
|
99
119
|
ctx.ensure_object(dict)
|
|
100
120
|
ctx.obj["TIMEOUT"] = timeout
|
|
121
|
+
ctx.obj["BUNDLE_TAGS"] = (
|
|
122
|
+
parse_cli_bundle_tags(bundle_versions) if len(bundle_versions) > 0 else None
|
|
123
|
+
)
|
|
101
124
|
|
|
102
125
|
if password is None:
|
|
103
126
|
password = os.getenv("CIRCUP_WEBWORKFLOW_PASSWORD")
|
|
@@ -210,7 +233,7 @@ def freeze(ctx, requirement): # pragma: no cover
|
|
|
210
233
|
device. Option -r saves output to requirements.txt file
|
|
211
234
|
"""
|
|
212
235
|
logger.info("Freeze")
|
|
213
|
-
modules = find_modules(ctx.obj["backend"], get_bundles_list())
|
|
236
|
+
modules = find_modules(ctx.obj["backend"], get_bundles_list(ctx.obj["BUNDLE_TAGS"]))
|
|
214
237
|
if modules:
|
|
215
238
|
output = []
|
|
216
239
|
for module in modules:
|
|
@@ -258,7 +281,9 @@ def list_cli(ctx): # pragma: no cover
|
|
|
258
281
|
|
|
259
282
|
modules = [
|
|
260
283
|
m.row
|
|
261
|
-
for m in find_modules(
|
|
284
|
+
for m in find_modules(
|
|
285
|
+
ctx.obj["backend"], get_bundles_list(ctx.obj["BUNDLE_TAGS"])
|
|
286
|
+
)
|
|
262
287
|
if m.outofdate
|
|
263
288
|
]
|
|
264
289
|
if modules:
|
|
@@ -334,7 +359,7 @@ def install(
|
|
|
334
359
|
|
|
335
360
|
# pylint: disable=too-many-branches
|
|
336
361
|
# TODO: Ensure there's enough space on the device
|
|
337
|
-
available_modules = get_bundle_versions(get_bundles_list())
|
|
362
|
+
available_modules = get_bundle_versions(get_bundles_list(ctx.obj["BUNDLE_TAGS"]))
|
|
338
363
|
mod_names = {}
|
|
339
364
|
for module, metadata in available_modules.items():
|
|
340
365
|
mod_names[module.replace(".py", "").lower()] = metadata
|
|
@@ -440,7 +465,7 @@ def example(ctx, examples, op_list, rename, overwrite):
|
|
|
440
465
|
else:
|
|
441
466
|
click.echo("Available example libraries:")
|
|
442
467
|
available_examples = get_bundle_examples(
|
|
443
|
-
get_bundles_list(), avoid_download=True
|
|
468
|
+
get_bundles_list(ctx.obj["BUNDLE_TAGS"]), avoid_download=True
|
|
444
469
|
)
|
|
445
470
|
lib_names = {
|
|
446
471
|
str(key.split(os.path.sep)[0]): value
|
|
@@ -451,7 +476,7 @@ def example(ctx, examples, op_list, rename, overwrite):
|
|
|
451
476
|
|
|
452
477
|
for example_arg in examples:
|
|
453
478
|
available_examples = get_bundle_examples(
|
|
454
|
-
get_bundles_list(), avoid_download=True
|
|
479
|
+
get_bundles_list(ctx.obj["BUNDLE_TAGS"]), avoid_download=True
|
|
455
480
|
)
|
|
456
481
|
if example_arg in available_examples:
|
|
457
482
|
filename = available_examples[example_arg].split(os.path.sep)[-1]
|
|
@@ -485,14 +510,15 @@ def example(ctx, examples, op_list, rename, overwrite):
|
|
|
485
510
|
|
|
486
511
|
@main.command()
|
|
487
512
|
@click.argument("match", required=False, nargs=1)
|
|
488
|
-
|
|
513
|
+
@click.pass_context
|
|
514
|
+
def show(ctx, match): # pragma: no cover
|
|
489
515
|
"""
|
|
490
516
|
Show a list of available modules in the bundle. These are modules which
|
|
491
517
|
*could* be installed on the device.
|
|
492
518
|
|
|
493
519
|
If MATCH is specified only matching modules will be listed.
|
|
494
520
|
"""
|
|
495
|
-
available_modules = get_bundle_versions(get_bundles_list())
|
|
521
|
+
available_modules = get_bundle_versions(get_bundles_list(ctx.obj["BUNDLE_TAGS"]))
|
|
496
522
|
module_names = sorted([m.replace(".py", "") for m in available_modules])
|
|
497
523
|
if match is not None:
|
|
498
524
|
match = match.lower()
|
|
@@ -555,7 +581,7 @@ def update(ctx, update_all): # pragma: no cover
|
|
|
555
581
|
"""
|
|
556
582
|
logger.info("Update")
|
|
557
583
|
# Grab current modules.
|
|
558
|
-
bundles_list = get_bundles_list()
|
|
584
|
+
bundles_list = get_bundles_list(ctx.obj["BUNDLE_TAGS"])
|
|
559
585
|
installed_modules = find_modules(ctx.obj["backend"], bundles_list)
|
|
560
586
|
modules_to_update = [m for m in installed_modules if m.outofdate]
|
|
561
587
|
|
|
@@ -654,13 +680,14 @@ def update(ctx, update_all): # pragma: no cover
|
|
|
654
680
|
|
|
655
681
|
@main.command("bundle-show")
|
|
656
682
|
@click.option("--modules", is_flag=True, help="List all the modules per bundle.")
|
|
657
|
-
|
|
683
|
+
@click.pass_context
|
|
684
|
+
def bundle_show(ctx, modules):
|
|
658
685
|
"""
|
|
659
|
-
Show the list of bundles, default and local, with URL, current version
|
|
660
|
-
and latest version retrieved from the web.
|
|
686
|
+
Show the list of bundles, default and local, with URL, current version,
|
|
687
|
+
available versions, and latest version retrieved from the web.
|
|
661
688
|
"""
|
|
662
689
|
local_bundles = get_bundles_local_dict().values()
|
|
663
|
-
bundles = get_bundles_list()
|
|
690
|
+
bundles = get_bundles_list(ctx.obj["BUNDLE_TAGS"])
|
|
664
691
|
available_modules = get_bundle_versions(bundles)
|
|
665
692
|
|
|
666
693
|
for bundle in bundles:
|
|
@@ -669,7 +696,13 @@ def bundle_show(modules):
|
|
|
669
696
|
else:
|
|
670
697
|
click.secho(bundle.key, fg="green")
|
|
671
698
|
click.echo(" " + bundle.url)
|
|
672
|
-
click.echo(
|
|
699
|
+
click.echo(
|
|
700
|
+
" version = "
|
|
701
|
+
+ bundle.current_tag
|
|
702
|
+
+ (" (pinned)" if bundle.pinned_tag is not None else "")
|
|
703
|
+
)
|
|
704
|
+
click.echo(" available versions:")
|
|
705
|
+
click.echo(" " + "\n ".join(bundle.available_tags))
|
|
673
706
|
if modules:
|
|
674
707
|
click.echo("Modules:")
|
|
675
708
|
for name, mod in sorted(available_modules.items()):
|
|
@@ -739,7 +772,7 @@ def bundle_add(ctx, bundle):
|
|
|
739
772
|
# save the bundles list
|
|
740
773
|
save_local_bundles(bundles_dict)
|
|
741
774
|
# update and get the new bundles for the first time
|
|
742
|
-
get_bundle_versions(get_bundles_list())
|
|
775
|
+
get_bundle_versions(get_bundles_list(ctx.obj["BUNDLE_TAGS"]))
|
|
743
776
|
|
|
744
777
|
|
|
745
778
|
@main.command("bundle-remove")
|
|
@@ -788,3 +821,40 @@ def bundle_remove(bundle, reset):
|
|
|
788
821
|
)
|
|
789
822
|
if modified:
|
|
790
823
|
save_local_bundles(bundles_local_dict)
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
@main.command()
|
|
827
|
+
@click.pass_context
|
|
828
|
+
def bundle_freeze(ctx): # pragma: no cover
|
|
829
|
+
"""
|
|
830
|
+
Output details of all the bundles for modules found on the connected
|
|
831
|
+
CIRCUITPYTHON device. Copying the output into pyproject.toml will pin the
|
|
832
|
+
bundles.
|
|
833
|
+
"""
|
|
834
|
+
logger.info("Bundle Freeze")
|
|
835
|
+
device_modules = ctx.obj["backend"].get_device_versions()
|
|
836
|
+
if not device_modules:
|
|
837
|
+
click.echo("No modules found on the device.")
|
|
838
|
+
return
|
|
839
|
+
|
|
840
|
+
available_modules = get_bundle_versions(get_bundles_list(ctx.obj["BUNDLE_TAGS"]))
|
|
841
|
+
bundles_used = {}
|
|
842
|
+
for name in device_modules:
|
|
843
|
+
module = available_modules.get(name)
|
|
844
|
+
if module:
|
|
845
|
+
bundle = module["bundle"]
|
|
846
|
+
bundles_used[bundle.key] = bundle.current_tag
|
|
847
|
+
|
|
848
|
+
if bundles_used:
|
|
849
|
+
click.echo(
|
|
850
|
+
"Copy the following lines into your pyproject.toml to pin "
|
|
851
|
+
"the bundles used with modules on the device:\n"
|
|
852
|
+
)
|
|
853
|
+
output = ["[tool.circup.bundle-versions]"]
|
|
854
|
+
for bundle_name, version in bundles_used.items():
|
|
855
|
+
output.append(f'"{bundle_name}" = "{version}"')
|
|
856
|
+
for line in output:
|
|
857
|
+
click.echo(line)
|
|
858
|
+
logger.info(line)
|
|
859
|
+
else:
|
|
860
|
+
click.echo("No bundles used with the modules on the device.")
|
circup/shared.py
CHANGED
|
@@ -9,7 +9,6 @@ and Backend class functions.
|
|
|
9
9
|
import glob
|
|
10
10
|
import os
|
|
11
11
|
import re
|
|
12
|
-
import json
|
|
13
12
|
import importlib.resources
|
|
14
13
|
import appdirs
|
|
15
14
|
import requests
|
|
@@ -192,29 +191,6 @@ def extract_metadata(path, logger):
|
|
|
192
191
|
return result
|
|
193
192
|
|
|
194
193
|
|
|
195
|
-
def tags_data_load(logger):
|
|
196
|
-
"""
|
|
197
|
-
Load the list of the version tags of the bundles on disk.
|
|
198
|
-
|
|
199
|
-
:return: a dict() of tags indexed by Bundle identifiers/keys.
|
|
200
|
-
"""
|
|
201
|
-
tags_data = None
|
|
202
|
-
try:
|
|
203
|
-
with open(BUNDLE_DATA, encoding="utf-8") as data:
|
|
204
|
-
try:
|
|
205
|
-
tags_data = json.load(data)
|
|
206
|
-
except json.decoder.JSONDecodeError as ex:
|
|
207
|
-
# Sometimes (why?) the JSON file becomes corrupt. In which case
|
|
208
|
-
# log it and carry on as if setting up for first time.
|
|
209
|
-
logger.error("Could not parse %s", BUNDLE_DATA)
|
|
210
|
-
logger.exception(ex)
|
|
211
|
-
except FileNotFoundError:
|
|
212
|
-
pass
|
|
213
|
-
if not isinstance(tags_data, dict):
|
|
214
|
-
tags_data = {}
|
|
215
|
-
return tags_data
|
|
216
|
-
|
|
217
|
-
|
|
218
194
|
def get_latest_release_from_url(url, logger):
|
|
219
195
|
"""
|
|
220
196
|
Find the tag name of the latest release by using HTTP HEAD and decoding the redirect.
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
circup/__init__.py,sha256=9A98U3DyA14tm-7_d88fGSlLfEKz9T_85t8kXkErqRg,662
|
|
2
2
|
circup/backends.py,sha256=g9Q9xCGZidwsEDL2Ga2cm50YYB54IiqlKUPcxj-pWZA,40008
|
|
3
|
-
circup/bundle.py,sha256=
|
|
4
|
-
circup/command_utils.py,sha256=
|
|
5
|
-
circup/commands.py,sha256=
|
|
3
|
+
circup/bundle.py,sha256=eQ_PxMaDD_XKfxXMXcpzSnhHjx5oJh1YXZq00o5smiM,7075
|
|
4
|
+
circup/command_utils.py,sha256=_DfnRKinXflkPt-CGaIg_3nBZ6Q2J9yRFS9LXC73k3U,35180
|
|
5
|
+
circup/commands.py,sha256=vZ4iui7IpLIkeaO_LQ5JzIqCPkfcUbrbUmICcLrl9aE,31494
|
|
6
6
|
circup/lazy_metadata.py,sha256=69VidxGGWE13QwAAtMCPNTXTsQ2q5dJvMtclw4YaqEY,3764
|
|
7
7
|
circup/logging.py,sha256=hu4v8ljkXo8ru-cqs0W3PU-xEVvTO_qqMKDJM18OXbQ,1115
|
|
8
8
|
circup/module.py,sha256=33_kdy5BZn6COyIjAFZMpw00rTtPiryQZWFXQkMF8FY,7435
|
|
9
|
-
circup/shared.py,sha256=
|
|
9
|
+
circup/shared.py,sha256=O3_iA1mIsoVPObTzv5tfPCqejJSZitTWb0RMYIEM87w,8635
|
|
10
10
|
circup/config/bundle_config.json,sha256=zzpmfy0jD7TxpOOw2P_gqIISuMBG0enb_f3_VneQ5mI,135
|
|
11
11
|
circup/config/bundle_config.json.license,sha256=OOHNqDsViGFhmG9z8J0o98hYmub1CkYKiZB96Php6KE,80
|
|
12
12
|
circup/wwshell/README.rst,sha256=M_jFP0hwOcngF0RdosdeqmVOISNcPzyjTW3duzIu9A8,3617
|
|
13
13
|
circup/wwshell/README.rst.license,sha256=GhA0SoZGP7CReDam-JJk_UtIQIpQaZWQFzR26YSuMm4,107
|
|
14
14
|
circup/wwshell/__init__.py,sha256=CAPZiYrouWboyPx4KiWLBG_vf_n0MmArGqaFyTXGKWk,398
|
|
15
15
|
circup/wwshell/commands.py,sha256=-I5l7XeoDmvWWuZg5wHdt9qe__SBQ1EGmKwCDTBMeus,7454
|
|
16
|
-
circup-2.
|
|
17
|
-
circup-2.
|
|
18
|
-
circup-2.
|
|
19
|
-
circup-2.
|
|
20
|
-
circup-2.
|
|
21
|
-
circup-2.
|
|
16
|
+
circup-2.3.0.dist-info/licenses/LICENSE,sha256=bVlIMmSL_pqLCqae4hzixy9pYXD808IbgsMoQXTNLBk,1076
|
|
17
|
+
circup-2.3.0.dist-info/METADATA,sha256=0PRlXlC9q3DFjK27uGkOPIj3tNYxbhi7ciaL8epvyKQ,13617
|
|
18
|
+
circup-2.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
19
|
+
circup-2.3.0.dist-info/entry_points.txt,sha256=FjTmwYD_ApgLRGifUrr_Ui1voW6fEzodQc3DKNzoAPc,69
|
|
20
|
+
circup-2.3.0.dist-info/top_level.txt,sha256=Qx6E0eZgSBE10ciNKsLZx8-TTy_9fEVZh7NLmn24KcU,7
|
|
21
|
+
circup-2.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|