Skip to content

Commit

Permalink
Merge pull request #696 from ubuntu/update
Browse files Browse the repository at this point in the history
  • Loading branch information
LyzardKing authored Apr 27, 2024
2 parents f4dd3ee + 95076c9 commit c4f3921
Show file tree
Hide file tree
Showing 17 changed files with 534 additions and 73 deletions.
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
3 changes: 2 additions & 1 deletion umake/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ 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('-y', '--assume-yes', action='store_true', help=_('Assume yes at interactive prompts'))
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
20 changes: 18 additions & 2 deletions 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 @@ -321,15 +325,27 @@ def run_for(self, args):
install_path = None
auto_accept_license = False
dry_run = False
assume_yes = False
if args.destdir:
install_path = os.path.abspath(os.path.expanduser(args.destdir))
if self.expect_license and args.accept_license:
auto_accept_license = True
if args.dry_run:
dry_run = True
if args.assume_yes:
assume_yes = True
self.setup(install_path=install_path,
auto_accept_license=auto_accept_license,
dry_run=dry_run)
dry_run=dry_run,
assume_yes=assume_yes)

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


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
141 changes: 85 additions & 56 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 @@ -102,18 +103,22 @@ def is_installed(self):
logger.debug("{} is installed".format(self.name))
return True

def setup(self, install_path=None, auto_accept_license=False, dry_run=False):
def setup(self, install_path=None, auto_accept_license=False, dry_run=False, assume_yes=False):
self.arg_install_path = install_path
self.auto_accept_license = auto_accept_license
self.dry_run = dry_run
self.assume_yes = assume_yes
super().setup()

# first step, check if installed or dry_run
if self.dry_run:
self.download_provider_page()
elif self.is_installed:
UI.display(YesNo("{} is already installed on your system, do you want to reinstall "
"it anyway?".format(self.name), self.reinstall, UI.return_main_screen))
if self.assume_yes:
self.reinstall()
else:
UI.display(YesNo("{} is already installed on your system, do you want to reinstall "
"it anyway?".format(self.name), self.reinstall, UI.return_main_screen))
else:
self.confirm_path(self.arg_install_path)

Expand Down Expand Up @@ -162,6 +167,10 @@ def set_exec_path(self):
def confirm_path(self, path_dir=""):
"""Confirm path dir"""

if self.assume_yes:
UI.display(DisplayMessage("Assuming default path: " + self.install_path))
path_dir = self.install_path

if not path_dir:
logger.debug("No installation path provided. Requesting one.")
UI.display(InputText("Choose installation path:", self.confirm_path, self.install_path))
Expand Down Expand Up @@ -211,6 +220,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 +305,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
Loading

0 comments on commit c4f3921

Please sign in to comment.