mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 02:45:05 +00:00
Fixes
This commit is contained in:
parent
80a0906867
commit
6baf92581b
@ -52,36 +52,59 @@ def read_installed_tools_hash(version_directory):
|
||||
return None
|
||||
|
||||
|
||||
def extract(archive_data, target_directory):
|
||||
"""Extracts next to target_directory and then replaces it, refusing entries outside of it"""
|
||||
staging_directory = f"{target_directory}.new"
|
||||
shutil.rmtree(staging_directory, ignore_errors=True)
|
||||
os.makedirs(staging_directory)
|
||||
root = os.path.realpath(staging_directory)
|
||||
try:
|
||||
with zipfile.ZipFile(io.BytesIO(archive_data)) as archive:
|
||||
for member in archive.infolist():
|
||||
destination = os.path.realpath(os.path.join(root, member.filename))
|
||||
if not destination.startswith(root + os.sep):
|
||||
fail(f"invalid entry in the archive: {member.filename}")
|
||||
archive.extractall(root)
|
||||
except zipfile.BadZipFile:
|
||||
class Update:
|
||||
"""Stages every component next to its installed folder, so a failed download leaves the installation untouched"""
|
||||
|
||||
def __init__(self):
|
||||
# (target directory, staging directory, metadata path, metadata)
|
||||
self.staged = []
|
||||
|
||||
def stage(self, archive_data, target_directory, metadata_path, metadata):
|
||||
"""Extracts into a staging folder, refusing entries outside of it"""
|
||||
staging_directory = f"{target_directory}.new"
|
||||
shutil.rmtree(staging_directory, ignore_errors=True)
|
||||
fail("the downloaded archive is invalid")
|
||||
shutil.rmtree(target_directory, ignore_errors=True)
|
||||
os.replace(staging_directory, target_directory)
|
||||
os.makedirs(staging_directory)
|
||||
self.staged.append((target_directory, staging_directory, metadata_path, metadata))
|
||||
root = os.path.realpath(staging_directory)
|
||||
try:
|
||||
with zipfile.ZipFile(io.BytesIO(archive_data)) as archive:
|
||||
for member in archive.infolist():
|
||||
destination = os.path.realpath(os.path.join(root, member.filename))
|
||||
if not destination.startswith(root + os.sep):
|
||||
fail(f"invalid entry in the archive: {member.filename}")
|
||||
archive.extractall(root)
|
||||
except zipfile.BadZipFile:
|
||||
fail("the downloaded archive is invalid")
|
||||
|
||||
def discard(self):
|
||||
for _, staging_directory, _, _ in self.staged:
|
||||
shutil.rmtree(staging_directory, ignore_errors=True)
|
||||
self.staged = []
|
||||
|
||||
def install_platform(platform_directory, archive_data, commit):
|
||||
extract(archive_data, os.path.join(platform_directory, "TactilitySDK"))
|
||||
with open(os.path.join(platform_directory, "commit"), "w") as file:
|
||||
file.write(commit)
|
||||
|
||||
|
||||
def install_tools(version_directory, archive_data, sha256):
|
||||
extract(archive_data, os.path.join(version_directory, "tools"))
|
||||
with open(os.path.join(version_directory, "tools.sha256"), "w") as file:
|
||||
file.write(sha256)
|
||||
def commit(self):
|
||||
"""Replaces the installed folders, and puts the previous ones back when that fails"""
|
||||
replaced = []
|
||||
try:
|
||||
for target_directory, staging_directory, _, _ in self.staged:
|
||||
backup_directory = f"{target_directory}.old"
|
||||
shutil.rmtree(backup_directory, ignore_errors=True)
|
||||
if os.path.isdir(target_directory):
|
||||
os.replace(target_directory, backup_directory)
|
||||
replaced.append((target_directory, backup_directory))
|
||||
os.replace(staging_directory, target_directory)
|
||||
except OSError as error:
|
||||
for target_directory, backup_directory in reversed(replaced):
|
||||
shutil.rmtree(target_directory, ignore_errors=True)
|
||||
if os.path.isdir(backup_directory):
|
||||
os.replace(backup_directory, target_directory)
|
||||
self.discard()
|
||||
fail(f"failed to install the update: {error}")
|
||||
for _, _, metadata_path, metadata in self.staged:
|
||||
with open(metadata_path, "w") as file:
|
||||
file.write(metadata)
|
||||
for _, backup_directory in replaced:
|
||||
shutil.rmtree(backup_directory, ignore_errors=True)
|
||||
self.staged = []
|
||||
|
||||
|
||||
def main():
|
||||
@ -99,22 +122,28 @@ def main():
|
||||
fail("the downloaded index.json is invalid")
|
||||
commit = index.get("gitCommit", "")
|
||||
available = index.get("platforms", {})
|
||||
updated = False
|
||||
tools = index.get("tools")
|
||||
if tools is not None and read_installed_tools_hash(version_directory) != tools.get("sha256"):
|
||||
print(f"Updating the tools of SDK {version}")
|
||||
archive_data = fetch(f"{CDN_URL}/sdk/{version}/{tools['file']}")
|
||||
if hashlib.sha256(archive_data).hexdigest() != tools["sha256"]:
|
||||
fail("the downloaded tools don't match their checksum")
|
||||
install_tools(version_directory, archive_data, tools["sha256"])
|
||||
updated = True
|
||||
for platform in installed_platforms(version_directory):
|
||||
platform_directory = os.path.join(version_directory, f"platform-{platform}")
|
||||
if read_commit(platform_directory) == commit or platform not in available:
|
||||
continue
|
||||
print(f"Updating SDK {version} for {platform}")
|
||||
install_platform(platform_directory, fetch(f"{CDN_URL}/sdk/{version}/{available[platform]}"), commit)
|
||||
updated = True
|
||||
update = Update()
|
||||
try:
|
||||
tools = index.get("tools")
|
||||
if tools is not None and read_installed_tools_hash(version_directory) != tools.get("sha256"):
|
||||
print(f"Downloading the tools of SDK {version}", flush=True)
|
||||
archive_data = fetch(f"{CDN_URL}/sdk/{version}/{tools['file']}")
|
||||
if hashlib.sha256(archive_data).hexdigest() != tools["sha256"]:
|
||||
fail("the downloaded tools don't match their checksum")
|
||||
update.stage(archive_data, os.path.join(version_directory, "tools"), os.path.join(version_directory, "tools.sha256"), tools["sha256"])
|
||||
for platform in installed_platforms(version_directory):
|
||||
platform_directory = os.path.join(version_directory, f"platform-{platform}")
|
||||
if read_commit(platform_directory) == commit or platform not in available:
|
||||
continue
|
||||
print(f"Downloading SDK {version} for {platform}", flush=True)
|
||||
archive_data = fetch(f"{CDN_URL}/sdk/{version}/{available[platform]}")
|
||||
update.stage(archive_data, os.path.join(platform_directory, "TactilitySDK"), os.path.join(platform_directory, "commit"), commit)
|
||||
except SystemExit:
|
||||
update.discard()
|
||||
print(f"SDK {version} was not changed", file=sys.stderr)
|
||||
raise
|
||||
updated = len(update.staged) > 0
|
||||
update.commit()
|
||||
with open(os.path.join(version_directory, "index.json"), "wb") as file:
|
||||
file.write(index_data)
|
||||
print(f"SDK {version} was updated" if updated else f"SDK {version} is up to date")
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fails when the files of tt changed since a base revision, but TT_VERSION in tt.py wasn't increased.
|
||||
"""Fails when the files of tt or its packaging changed since a base revision, but TT_VERSION in tt.py wasn't increased.
|
||||
Usage: check-tt-version.py <base revision>, e.g. origin/main"""
|
||||
|
||||
import importlib.util
|
||||
@ -10,6 +10,8 @@ import sys
|
||||
|
||||
ROOT_PATH = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
VERSION_PATTERN = re.compile(r'^TT_VERSION = "([^"]+)"', re.MULTILINE)
|
||||
# Changes to what tt.zip contains, e.g. an added or removed file
|
||||
PACKAGING_FILE_NAME = "Buildscripts/release-tt.py"
|
||||
|
||||
|
||||
def load_file_names():
|
||||
@ -39,7 +41,7 @@ def main():
|
||||
sys.exit(2)
|
||||
base = sys.argv[1]
|
||||
file_names = load_file_names()
|
||||
diff = git("diff", "--name-only", f"{base}...HEAD", "--", *file_names)
|
||||
diff = git("diff", "--name-only", f"{base}...HEAD", "--", *file_names, PACKAGING_FILE_NAME)
|
||||
if diff.returncode != 0:
|
||||
print(f"git diff failed: {diff.stderr.strip()}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
|
||||
## Higher Priority
|
||||
|
||||
- Remove screenshot app and replace with tt.py + screenshot.py: "tt screenshot [filepath]". if filepath isn't specified, save as screenshot-[yyyy-mm-dd].png
|
||||
- BlueTooth as radio backend for MeshCore/Meshtastic?
|
||||
- ESP-NOW as radio backend. Use it for MeshCore/Meshtastic?
|
||||
- Find out why the first build of an app with a clean build cache folder always fails
|
||||
|
||||
@ -40,6 +40,7 @@ static int report_error(const char* action, error_t error) {
|
||||
/**
|
||||
* Queues an operation and waits for its result.
|
||||
* The subscription is made before queueing, so the operation can't finish unnoticed.
|
||||
* It times out after OPERATION_TIMEOUT without events of the operation, so a long update of many apps doesn't.
|
||||
* @param[in] finished_type the event that ends the operation and carries its result
|
||||
* @param[in] on_event called for the other events of the operation (optional)
|
||||
*/
|
||||
@ -62,7 +63,7 @@ static error_t run_and_wait(
|
||||
error = operation(id, &operation_id);
|
||||
if (error == ERROR_NONE) {
|
||||
error = ERROR_TIMEOUT;
|
||||
const TickType_t start = get_ticks();
|
||||
TickType_t start = get_ticks();
|
||||
TickType_t elapsed = 0;
|
||||
bool finished = false;
|
||||
while (!finished && elapsed < OPERATION_TIMEOUT) {
|
||||
@ -72,6 +73,7 @@ static error_t run_and_wait(
|
||||
if (event.operation_id != operation_id) {
|
||||
continue;
|
||||
}
|
||||
start = get_ticks();
|
||||
if (event.type == finished_type) {
|
||||
error = event.result;
|
||||
finished = true;
|
||||
|
||||
@ -20,5 +20,8 @@ bool http_parse_status_line(const std::string& line, int& statusCode);
|
||||
/** Parses a header line such as "Content-Length: 42". The name is returned in lower case, the value without surrounding whitespace. */
|
||||
bool http_parse_header_line(const std::string& line, std::string& name, std::string& value);
|
||||
|
||||
/** Parses a Content-Length value: decimal digits only, without sign or overflow */
|
||||
bool http_parse_content_length(const std::string& value, long& length);
|
||||
|
||||
/** Parses the size line of a chunk in a chunked body, e.g. "1a2b" or "1a2b;name=value" */
|
||||
bool http_parse_chunk_size(const std::string& line, size_t& size);
|
||||
|
||||
@ -299,6 +299,8 @@ HttpDownloadEvent http_download_run(const std::string& url, const std::string& c
|
||||
|
||||
// -1 means the length is unknown: the body is chunked or ends when the server closes the connection
|
||||
long contentLength = -1;
|
||||
std::string contentLengthValue;
|
||||
bool hasContentLength = false;
|
||||
bool chunked = false;
|
||||
while (true) {
|
||||
if (!connection.readLine(line)) {
|
||||
@ -313,7 +315,8 @@ HttpDownloadEvent http_download_run(const std::string& url, const std::string& c
|
||||
continue;
|
||||
}
|
||||
if (name == "content-length") {
|
||||
contentLength = strtol(value.c_str(), nullptr, 10);
|
||||
contentLengthValue = value;
|
||||
hasContentLength = true;
|
||||
} else if (name == "transfer-encoding" && value.find("chunked") != std::string::npos) {
|
||||
chunked = true;
|
||||
}
|
||||
@ -322,6 +325,10 @@ HttpDownloadEvent http_download_run(const std::string& url, const std::string& c
|
||||
if (statusCode < 200 || statusCode >= 300) {
|
||||
return http_download_make_error_event("Server response is not OK", statusCode);
|
||||
}
|
||||
// A chunked body ignores the Content-Length
|
||||
if (hasContentLength && !chunked && !http_parse_content_length(contentLengthValue, contentLength)) {
|
||||
return http_download_make_error_event("Invalid Content-Length", statusCode);
|
||||
}
|
||||
if (http_download_is_cancelled(link)) {
|
||||
return http_download_make_cancelled_event(statusCode);
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
#include <http/private/http_parsing.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <climits>
|
||||
#include <cstdlib>
|
||||
#include <string_view>
|
||||
|
||||
@ -110,6 +111,22 @@ bool http_parse_header_line(const std::string& line, std::string& name, std::str
|
||||
return !name.empty();
|
||||
}
|
||||
|
||||
bool http_parse_content_length(const std::string& value, long& length) {
|
||||
if (value.empty() || value.find_first_not_of("0123456789") != std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
long result = 0;
|
||||
for (const char c : value) {
|
||||
const long digit = c - '0';
|
||||
if (result > (LONG_MAX - digit) / 10) {
|
||||
return false;
|
||||
}
|
||||
result = result * 10 + digit;
|
||||
}
|
||||
length = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool http_parse_chunk_size(const std::string& line, size_t& size) {
|
||||
const std::string digits = trim(line.substr(0, line.find(';')));
|
||||
if (digits.empty() || digits.size() > sizeof(size_t) * 2) {
|
||||
|
||||
@ -46,6 +46,21 @@ TEST_CASE("http_parse_header_line lowercases the name and trims the value") {
|
||||
CHECK_FALSE(http_parse_header_line(": empty name", name, value));
|
||||
}
|
||||
|
||||
TEST_CASE("http_parse_content_length accepts decimal digits only") {
|
||||
long length = -1;
|
||||
REQUIRE(http_parse_content_length("42", length));
|
||||
CHECK_EQ(length, 42);
|
||||
REQUIRE(http_parse_content_length("0", length));
|
||||
CHECK_EQ(length, 0);
|
||||
CHECK_FALSE(http_parse_content_length("", length));
|
||||
CHECK_FALSE(http_parse_content_length("-5", length));
|
||||
CHECK_FALSE(http_parse_content_length("+5", length));
|
||||
CHECK_FALSE(http_parse_content_length("12abc", length));
|
||||
CHECK_FALSE(http_parse_content_length("1 2", length));
|
||||
CHECK_FALSE(http_parse_content_length("99999999999999999999999", length));
|
||||
CHECK_EQ(length, 0);
|
||||
}
|
||||
|
||||
TEST_CASE("http_parse_chunk_size reads hexadecimal sizes and ignores extensions") {
|
||||
size_t size = 0;
|
||||
REQUIRE(http_parse_chunk_size("1a2B", size));
|
||||
|
||||
@ -27,7 +27,7 @@ public:
|
||||
|
||||
explicit TimeZoneView(OnSelected onSelected);
|
||||
|
||||
/** Waits for a running detection, which can take a few seconds. Destroy it without holding the LVGL lock. */
|
||||
/** Cancels a running detection and waits for its lookup to end, which can take a few seconds */
|
||||
~TimeZoneView();
|
||||
|
||||
TimeZoneView(const TimeZoneView&) = delete;
|
||||
@ -59,6 +59,8 @@ private:
|
||||
std::unique_ptr<Timer> filterTimer;
|
||||
std::unique_ptr<Thread> detectThread;
|
||||
std::atomic<bool> detecting = false;
|
||||
// Set when the view is destroyed, so a running detection ends without using it
|
||||
std::atomic<bool> cancelled = false;
|
||||
|
||||
// Valid while the widgets exist, guarded by the LVGL lock
|
||||
lv_obj_t* textarea = nullptr;
|
||||
|
||||
@ -90,6 +90,40 @@ void updatePageIndicatorWidth(void* data) {
|
||||
}
|
||||
}
|
||||
|
||||
// A page indicator below the grid gets the width of its parent.
|
||||
// It shows the page number when its dots are wider than its max width, which must be in pixels.
|
||||
void updateCenteredPageIndicatorWidth(lv_obj_t* indicator) {
|
||||
const int32_t margins = lv_obj_get_style_margin_left(indicator, LV_PART_MAIN) + lv_obj_get_style_margin_right(indicator, LV_PART_MAIN);
|
||||
const int32_t max_width = std::max<int32_t>(0, lv_obj_get_content_width(lv_obj_get_parent(indicator)) - margins);
|
||||
// Setting a style marks the layout as dirty, which would resize the parent and call this again
|
||||
if (lv_obj_get_style_max_width(indicator, LV_PART_MAIN) != max_width) {
|
||||
lv_obj_set_style_max_width(indicator, max_width, LV_STATE_DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
void onCenteredPageIndicatorParentSizeChanged(lv_event_t* event) {
|
||||
updateCenteredPageIndicatorWidth(static_cast<lv_obj_t*>(lv_event_get_user_data(event)));
|
||||
}
|
||||
|
||||
void onCenteredPageIndicatorDeleted(lv_event_t* event) {
|
||||
lv_obj_t* indicator = lv_event_get_target_obj(event);
|
||||
lv_obj_remove_event_cb_with_user_data(lv_obj_get_parent(indicator), onCenteredPageIndicatorParentSizeChanged, indicator);
|
||||
}
|
||||
|
||||
/** Call after moving the page indicator below the grid */
|
||||
void centerPageIndicator(lv_obj_t* indicator) {
|
||||
lv_obj_set_width(indicator, LV_PCT(100));
|
||||
lv_obj_add_event_cb(lv_obj_get_parent(indicator), onCenteredPageIndicatorParentSizeChanged, LV_EVENT_SIZE_CHANGED, indicator);
|
||||
lv_obj_add_event_cb(indicator, onCenteredPageIndicatorDeleted, LV_EVENT_DELETE, nullptr);
|
||||
updateCenteredPageIndicatorWidth(indicator);
|
||||
}
|
||||
|
||||
/** Call before moving the page indicator away from below the grid */
|
||||
void uncenterPageIndicator(lv_obj_t* indicator) {
|
||||
lv_obj_remove_event_cb_with_user_data(lv_obj_get_parent(indicator), onCenteredPageIndicatorParentSizeChanged, indicator);
|
||||
lv_obj_remove_event_cb(indicator, onCenteredPageIndicatorDeleted);
|
||||
}
|
||||
|
||||
void onBottomBarSizeChanged(lv_event_t* event) {
|
||||
lv_async_call_cancel(updatePageIndicatorWidth, lv_event_get_target_obj(event));
|
||||
lv_async_call(updatePageIndicatorWidth, lv_event_get_target_obj(event));
|
||||
@ -448,7 +482,7 @@ void TileGrid::createWidgets(lv_obj_t* parent) {
|
||||
createGrid(parent);
|
||||
// Centered below the grid
|
||||
pageIndicator = lvgl_page_indicator_create(parent);
|
||||
lv_obj_set_width(pageIndicator, LV_PCT(100));
|
||||
centerPageIndicator(pageIndicator);
|
||||
// Keeps the dots off the screen's bottom edge
|
||||
lv_obj_set_style_margin_bottom(pageIndicator, lvgl_theme_is_compact() ? LV_DPX(2) : LV_DPX(8), LV_STATE_DEFAULT);
|
||||
finishCreate(parent);
|
||||
@ -531,8 +565,7 @@ void TileGrid::setBarSideContainer(lv_obj_t* sideContainer) {
|
||||
// The page indicator is centered below the grid
|
||||
lv_obj_set_parent(pageIndicator, barParent);
|
||||
lv_obj_move_to_index(pageIndicator, lv_obj_get_index(grid) + 1);
|
||||
lv_obj_set_width(pageIndicator, LV_PCT(100));
|
||||
lv_obj_remove_local_style_prop(pageIndicator, LV_STYLE_MAX_WIDTH, LV_PART_MAIN);
|
||||
centerPageIndicator(pageIndicator);
|
||||
// Keeps the dots off the screen's bottom edge
|
||||
lv_obj_set_style_margin_bottom(pageIndicator, lvgl_theme_is_compact() ? 1 : 2, LV_STATE_DEFAULT);
|
||||
} else {
|
||||
@ -543,9 +576,13 @@ void TileGrid::setBarSideContainer(lv_obj_t* sideContainer) {
|
||||
lv_obj_remove_local_style_prop(bottomBar, LV_STYLE_BG_OPA, LV_PART_MAIN);
|
||||
lv_obj_remove_local_style_prop(bottomBar, LV_STYLE_PAD_ROW, LV_PART_MAIN);
|
||||
lv_obj_set_hidden(barSpacer, false);
|
||||
uncenterPageIndicator(pageIndicator);
|
||||
lv_obj_set_parent(pageIndicator, bottomBar);
|
||||
lv_obj_set_width(pageIndicator, LV_SIZE_CONTENT);
|
||||
lv_obj_remove_local_style_prop(pageIndicator, LV_STYLE_MARGIN_BOTTOM, LV_PART_MAIN);
|
||||
// The bar's size can stay the same, which wouldn't update the max width
|
||||
lv_async_call_cancel(updatePageIndicatorWidth, bottomBar);
|
||||
lv_async_call(updatePageIndicatorWidth, bottomBar);
|
||||
}
|
||||
// The keys move through the bar where it's shown: below the statusbar's icons in the side strip, or below the grid
|
||||
setGroupOrder(grid, bottomBar, sideContainer != nullptr);
|
||||
|
||||
@ -78,6 +78,7 @@ TimeZoneView::TimeZoneView(OnSelected onSelected) : onSelected(std::move(onSelec
|
||||
|
||||
TimeZoneView::~TimeZoneView() {
|
||||
filterTimer->stop();
|
||||
cancelled = true;
|
||||
if (detectThread != nullptr) {
|
||||
detectThread->join();
|
||||
}
|
||||
@ -268,7 +269,16 @@ void TimeZoneView::detect() {
|
||||
failure = "The time zone couldn't be detected. Check the internet connection and try again.";
|
||||
}
|
||||
|
||||
lvgl_lock();
|
||||
// Waits for the LVGL lock without blocking the destructor, which can be called with the lock held
|
||||
while (!lvgl_try_lock(pdMS_TO_TICKS(50))) {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (cancelled) {
|
||||
lvgl_unlock();
|
||||
return;
|
||||
}
|
||||
detecting = false;
|
||||
mutex.lock();
|
||||
message = failure;
|
||||
|
||||
40
tt.py
40
tt.py
@ -20,7 +20,7 @@ import time
|
||||
import urllib.request
|
||||
import zipfile
|
||||
|
||||
TT_VERSION = "1.1.0"
|
||||
TT_VERSION = "1.1.1"
|
||||
CDN_URL = os.environ.get("TT_CDN", "https://cdn.tactilityproject.org")
|
||||
HOME_DIRECTORY = os.path.join(os.path.expanduser("~"), ".tactility")
|
||||
INDEX_VALIDITY_SECONDS = 3600
|
||||
@ -173,15 +173,47 @@ def install_platforms(version, version_directory, index, platforms):
|
||||
file.write(index.get("gitCommit", ""))
|
||||
|
||||
|
||||
def read_text(path):
|
||||
try:
|
||||
with open(path) as file:
|
||||
return file.read().strip()
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
|
||||
def is_installed_build(version_directory, index):
|
||||
"""True when the installed tools and platforms are of the build that the index describes"""
|
||||
installed_tools_hash = read_text(os.path.join(version_directory, "tools.sha256"))
|
||||
if installed_tools_hash is not None and installed_tools_hash != index.get("tools", {}).get("sha256"):
|
||||
return False
|
||||
for name in os.listdir(version_directory):
|
||||
commit = read_text(os.path.join(version_directory, name, "commit"))
|
||||
if name.startswith("platform-") and commit is not None and commit != index.get("gitCommit", ""):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def prepare_sdk(version, platforms):
|
||||
"""Installs what's missing of the SDK version. Installed tools and platforms are only replaced by "tt update-sdk", so they stay from the same build."""
|
||||
version_directory = version_path(HOME_DIRECTORY, version)
|
||||
index = None
|
||||
# "target.platforms=all" means every platform of the SDK
|
||||
if platforms == ["all"]:
|
||||
os.makedirs(version_directory, exist_ok=True)
|
||||
index = load_index(version, version_directory)
|
||||
platforms = sorted(index.get("platforms", {}))
|
||||
has_tools = os.path.isdir(os.path.join(version_directory, "tools"))
|
||||
missing_platforms = [platform for platform in platforms if not os.path.isdir(os.path.join(version_directory, f"platform-{platform}", "TactilitySDK"))]
|
||||
if has_tools and not missing_platforms:
|
||||
return version_directory
|
||||
os.makedirs(version_directory, exist_ok=True)
|
||||
index = load_index(version, version_directory)
|
||||
if index is None:
|
||||
index = load_index(version, version_directory)
|
||||
# The CDN only has the latest build of a version, so a newer build can't be mixed with the installed one
|
||||
if not is_installed_build(version_directory, index):
|
||||
if has_tools:
|
||||
fail(f"SDK {version} was published again since it was installed: run 'tt update-sdk' first")
|
||||
fail(f"SDK {version} was published again since it was installed: remove {version_directory} and try again")
|
||||
if not has_tools:
|
||||
install_tools(version, version_directory, index)
|
||||
install_platforms(version, version_directory, index, missing_platforms)
|
||||
@ -255,7 +287,9 @@ def main(arguments):
|
||||
fail(f"tools not found: {tools_directory}")
|
||||
else:
|
||||
sdk_path = HOME_DIRECTORY
|
||||
tools_directory = os.path.join(prepare_sdk(version, platforms), "tools")
|
||||
# update-sdk replaces the installed build, so it only needs the tools
|
||||
required_platforms = [] if command == "update-sdk" else platforms
|
||||
tools_directory = os.path.join(prepare_sdk(version, required_platforms), "tools")
|
||||
environment = tool_environment(version, sdk_path, is_local)
|
||||
if is_help:
|
||||
print_help(version, tools_directory)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user