Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add --update option #693

Merged
merged 5 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions tests/small/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"""Tests the umake settings handler"""

import os
import re
import shutil
import tempfile
from ..tools import get_data_dir, LoggedTestCase
Expand Down Expand Up @@ -97,3 +98,24 @@ def test_version_git_not_installed(self, path_join_result):
path_join_result.side_effect = self.return_fake_version_path
os.environ["PATH"] = ""
self.assertEqual(settings.get_version(), "42.02+unknown")

def test_get_latest_version(self):
class DartSdk:
def __init__(self):
self.package_url = 'https://storage.googleapis.com/dart-archive/channels/stable/release/3.2.4/sdk/dartsdk-linux-x64-release.zip'
self.version_regex = r'/(\d+\.\d+\.\d+)'

def get_latest_version(self):
print(self.version_regex, self.package_url)
return (re.search(self.version_regex, self.package_url).group(1).replace('_', '.')
if self.package_url and self.version_regex else None)

framework = DartSdk()
self.assertEqual(framework.get_latest_version(), '3.2.4')

@patch("os.path.join")
def test_get_current_user_version(self, path_join_result):
# 1) install dart-sdk or a dummy framework and store the install_path
# 2) Initiate a framework object
# 3) assertEqual(framework.get_current_user_version(install_path), '3.2.4')
pass
2 changes: 1 addition & 1 deletion umake/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def main():
add_help=False)
parser.add_argument('--help', action=_HelpAction, help=_('Show this help')) # add custom help
parser.add_argument("-v", "--verbose", action="count", default=0, help=_("Increase output verbosity (2 levels)"))

parser.add_argument('-u', '--update', action='store_true', help=_('Update installed frameworks'))
parser.add_argument('-r', '--remove', action="store_true", help=_("Remove specified framework if installed"))

list_group = parser.add_argument_group("List frameworks").add_mutually_exclusive_group()
Expand Down
14 changes: 13 additions & 1 deletion umake/frameworks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import pkgutil
import sys
import subprocess
import re
from umake.network.requirements_handler import RequirementsHandler
from umake.settings import DEFAULT_INSTALL_TOOLS_PATH, UMAKE_FRAMEWORKS_ENVIRON_VARIABLE, DEFAULT_BINARY_LINK_PATH
from umake.tools import ConfigHandler, NoneDict, classproperty, get_current_arch, get_current_distro_version,\
Expand Down Expand Up @@ -140,7 +141,8 @@ class BaseFramework(metaclass=abc.ABCMeta):
def __init__(self, name, description, category, force_loading=False, logo_path=None, is_category_default=False,
install_path_dir=None, only_on_archs=None, only_ubuntu=False, only_ubuntu_version=None,
packages_requirements=None, only_for_removal=False, expect_license=False,
need_root_access=False, json=False, override_install_path=None):
need_root_access=False, json=False, override_install_path=None,
version_regex=None, supports_update=False):
self.name = name
self.description = description
self.logo_path = None
Expand All @@ -153,6 +155,8 @@ def __init__(self, name, description, category, force_loading=False, logo_path=N
self.packages_requirements.extend(self.category.packages_requirements)
self.only_for_removal = only_for_removal
self.expect_license = expect_license
self.version_regex = version_regex
self.supports_update = supports_update
# self.override_install_path = "" if override_install_path is None else override_install_path

# don't detect anything for completion mode (as we need to be quick), so avoid opening apt cache and detect
Expand Down Expand Up @@ -331,6 +335,14 @@ def run_for(self, args):
auto_accept_license=auto_accept_license,
dry_run=dry_run)

def get_latest_version(self):
return (re.search(self.version_regex, self.package_url).group(1).replace('_', '.')
if self.package_url and self.version_regex else None)

@staticmethod
def get_current_user_version(install_path):
return None

Copy link
Contributor Author

@NicolasKeita NicolasKeita Jan 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding two default methods for each framework.
get_latest_version() is getting the version from parsing the download_url.
get_current_user_version() is overridden in each of the framework. It is checking the installed files (like VERSION.txt for example) to fetch the version.


class MainCategory(BaseCategory):

Expand Down
17 changes: 15 additions & 2 deletions umake/frameworks/android.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@


"""Android module"""

import json
from contextlib import suppress
from gettext import gettext as _
import logging
Expand Down Expand Up @@ -86,7 +86,10 @@ def __init__(self, **kwargs):
checksum_type=ChecksumType.sha256,
dir_to_decompress_in_tarball="android-studio",
desktop_filename="android-studio.desktop",
required_files_path=[os.path.join("bin", "studio.sh")], **kwargs)
required_files_path=[os.path.join("bin", "studio.sh")],
version_regex=r'(\d+\.\d+)',
supports_update=True,
**kwargs)

def parse_license(self, line, license_txt, in_license):
"""Parse Android Studio download page for license"""
Expand All @@ -108,6 +111,16 @@ def post_install(self):
categories="Development;IDE;",
extra="StartupWMClass=jetbrains-studio"))

@staticmethod
def get_current_user_version(install_path):
try:
with open(os.path.join(install_path, 'product-info.json'), 'r') as file:
data = json.load(file)
version_not_formatted = data.get('dataDirectoryName')
return re.search(r'\d+\.\d+', version_not_formatted).group() if version_not_formatted else None
except FileNotFoundError:
return


class AndroidSDK(umake.frameworks.baseinstaller.BaseInstaller):

Expand Down
127 changes: 74 additions & 53 deletions umake/frameworks/baseinstaller.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ def __init__(self, *args, **kwargs):
"""The Downloader framework isn't instantiated directly, but is useful to inherit from for all frameworks

having a set of downloads to proceed, some eventual supported_archs."""
self.package_url = None
self.download_page = kwargs["download_page"]
self.checksum_type = kwargs.get("checksum_type", None)
self.dir_to_decompress_in_tarball = kwargs.get("dir_to_decompress_in_tarball", "")
Expand Down Expand Up @@ -211,6 +212,78 @@ def parse_download_link(self, line, in_download):
((url, md5sum), in_download=True/False)"""
pass

def store_package_url(self, result):
logger.debug("Parse download metadata")
self.auto_accept_license = True
self.dry_run = True

error_msg = result[self.download_page].error
if error_msg:
logger.error("An error occurred while downloading {}: {}".format(self.download_page, error_msg))

self.new_download_url = None
self.shasum_read_method = hasattr(self, 'get_sha_and_start_download')
with StringIO() as license_txt:
url, checksum = self.get_metadata(result, license_txt)
self.package_url = url

def get_metadata(self, result, license_txt):

url, checksum = (None, None)
page = result[self.download_page]
if self.json is True:
logger.debug("Using json parser")
try:
latest = json.loads(page.buffer.read().decode())
# On a download from github, if the page is not .../releases/latest
# we want to download the latest version (beta/development)
# So we get the first element in the json tree.
# In the framework we only change the url and this condition is satisfied.
if self.download_page.startswith("https://api.github.com") and \
not self.download_page.endswith("/latest"):
latest = latest[0]
url = None
in_download = False
(url, in_download) = self.parse_download_link(latest, in_download)
if not url:
if not self.url:
raise IndexError
else:
logger.debug("We set a temporary url while fetching the checksum")
url = self.url
except (json.JSONDecodeError, IndexError):
logger.error("Can't parse the download URL from the download page.")
UI.return_main_screen(status_code=1)
logger.debug("Found download URL: " + url)

else:
in_license = False
in_download = False
for line in page.buffer:
line_content = line.decode()

if self.expect_license and not self.auto_accept_license:
in_license = self.parse_license(line_content, license_txt, in_license)

# always take the first valid (url, checksum) if not match_last_link is set to True:
download = None
# if not in_download:
if (url is None or (self.checksum_type and not checksum) or
self.match_last_link) and \
not (self.shasum_read_method and self.new_download_url):
(download, in_download) = self.parse_download_link(line_content, in_download)

if download is not None:
(newurl, new_checksum) = download
url = newurl if newurl is not None else url
checksum = new_checksum if new_checksum is not None else checksum
if url is not None:
if self.checksum_type and checksum:
logger.debug("Found download link for {}, checksum: {}".format(url, checksum))
elif not self.checksum_type:
logger.debug("Found download link for {}".format(url))
return url, checksum

@MainLoop.in_mainloop_thread
def get_metadata_and_check_license(self, result):
"""Download files to download + license and check it"""
Expand All @@ -224,59 +297,7 @@ def get_metadata_and_check_license(self, result):
self.new_download_url = None
self.shasum_read_method = hasattr(self, 'get_sha_and_start_download')
with StringIO() as license_txt:
url, checksum = (None, None)
page = result[self.download_page]
if self.json is True:
logger.debug("Using json parser")
try:
latest = json.loads(page.buffer.read().decode())
# On a download from github, if the page is not .../releases/latest
# we want to download the latest version (beta/development)
# So we get the first element in the json tree.
# In the framework we only change the url and this condition is satisfied.
if self.download_page.startswith("https://api.github.com") and\
not self.download_page.endswith("/latest"):
latest = latest[0]
url = None
in_download = False
(url, in_download) = self.parse_download_link(latest, in_download)
if not url:
if not self.url:
raise IndexError
else:
logger.debug("We set a temporary url while fetching the checksum")
url = self.url
except (json.JSONDecodeError, IndexError):
logger.error("Can't parse the download URL from the download page.")
UI.return_main_screen(status_code=1)
logger.debug("Found download URL: " + url)

else:
in_license = False
in_download = False
for line in page.buffer:
line_content = line.decode()

if self.expect_license and not self.auto_accept_license:
in_license = self.parse_license(line_content, license_txt, in_license)

# always take the first valid (url, checksum) if not match_last_link is set to True:
download = None
# if not in_download:
if (url is None or (self.checksum_type and not checksum) or
self.match_last_link) and\
not(self.shasum_read_method and self.new_download_url):
(download, in_download) = self.parse_download_link(line_content, in_download)
if download is not None:
(newurl, new_checksum) = download
url = newurl if newurl is not None else url
checksum = new_checksum if new_checksum is not None else checksum
if url is not None:
if self.checksum_type and checksum:
logger.debug("Found download link for {}, checksum: {}".format(url, checksum))
elif not self.checksum_type:
logger.debug("Found download link for {}".format(url))

url, checksum = self.get_metadata(result, license_txt)
if hasattr(self, 'get_sha_and_start_download'):
logger.debug('Run get_sha_and_start_download')
DownloadCenter(urls=[DownloadItem(self.new_download_url, None)],
Expand Down
13 changes: 12 additions & 1 deletion umake/frameworks/dart.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ def __init__(self, **kwargs):
"stable/release/latest/VERSION",
dir_to_decompress_in_tarball="dart-sdk",
required_files_path=[os.path.join("bin", "dart")],
json=True, **kwargs)
json=True,
version_regex=r'/(\d+\.\d+\.\d+)',
supports_update=True,
**kwargs)

arch_trans = {
"amd64": "x64",
Expand All @@ -79,6 +82,14 @@ def post_install(self):
add_env_to_user(self.name, {"PATH": {"value": os.path.join(self.install_path, "bin")}})
UI.delayed_display(DisplayMessage(self.RELOGIN_REQUIRE_MSG.format(self.name)))

@staticmethod
def get_current_user_version(install_path):
try:
with open(os.path.join(install_path, 'version'), 'r') as file:
return file.readline().strip() if file else None
except FileNotFoundError:
return


class FlutterLang(umake.frameworks.baseinstaller.BaseInstaller):

Expand Down
19 changes: 17 additions & 2 deletions umake/frameworks/devops.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@


"""Devops module"""

import re
import subprocess
from gettext import gettext as _
import logging
import os
Expand All @@ -45,7 +46,10 @@ def __init__(self, **kwargs):
download_page="https://api.github.com/repos/hashicorp/terraform/releases/latest",
dir_to_decompress_in_tarball=".",
required_files_path=["terraform"],
json=True, **kwargs)
json=True,
version_regex=r'/(\d+\.\d+\.\d+)',
supports_update=True,
**kwargs)

arch_trans = {
"amd64": "amd64",
Expand All @@ -67,3 +71,14 @@ def post_install(self):
"""Add Terraform necessary env variables"""
add_env_to_user(self.name, {"PATH": {"value": os.path.join(self.install_path)}})
UI.delayed_display(DisplayMessage(self.RELOGIN_REQUIRE_MSG.format(self.name)))

@staticmethod
def get_current_user_version(install_path):
file = os.path.join(install_path, 'terraform')
command = f"{file} --version"
try:
result = subprocess.check_output(command, shell=True, text=True)
match = re.search(r'Terraform\s+v(\d+\.\d+\.\d+)', result)
return match.group(1) if match else None
except subprocess.CalledProcessError:
return
10 changes: 10 additions & 0 deletions umake/frameworks/electronics.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ def __init__(self, **kwargs):
desktop_filename="eagle.desktop",
required_files_path=["eagle"],
dir_to_decompress_in_tarball="eagle-*",
version_regex=r'/(\d+(?:_\d+)*)/',
supports_update=True,
**kwargs)

def parse_download_link(self, line, in_download):
Expand All @@ -205,6 +207,14 @@ def post_install(self):
comment=self.description,
categories="Development;"))

@staticmethod
def get_current_user_version(install_path):
try:
with open(os.path.join(install_path, 'bin', 'eagle.def'), 'r') as file:
return re.search(r'(\d+(\.\d+)+)', next(file)).group(1) if file else None
except FileNotFoundError:
return


class Fritzing(umake.frameworks.baseinstaller.BaseInstaller):

Expand Down
Loading