Installed-Size: 26
Section: misc
Priority: extra" > $control_file
[[ $multiarch == true ]] && echo "Multi-Arch: same" >> $control_file
echo "Description: Dummy package for testing
Package used for testing debs installation" >> $control_file
dpkg-deb -b $temp_dir ${package_name}_${version,}_${arch}.deb
rm -rf $temp_dir
}
extract_version() {
version=$(apt-cache policy $1 | grep Candidate | awk '{print $2}')
[ -z "$version" ] && version=1.0
echo $version
}
create_package() {
package_name=$1
arch=$2
version=$(extract_version $package_name)
generate_package $package_name $version $arch
}
# android studio and adt deps
mkdir -p $repo_root_dir/android
cd $repo_root_dir/android
create_package clang
create_package openjdk-7-jdk
create_package openjdk-8-jdk
create_package jayatana
create_package libncurses5 i386
create_package libstdc++6 i386
create_package zlib1g i386
dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz
# rubymine deps
mkdir -p $repo_root_dir/rubymine
cd $repo_root_dir/rubymine
create_package ruby
dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz
# stencyl deps
mkdir -p $repo_root_dir/stencyl
cd $repo_root_dir/stencyl
create_package libxtst6 i386
create_package libxext6 i386
create_package libxi6 i386
create_package libncurses5 i386
create_package libxt6 i386
create_package libxpm4 i386
create_package libxmu6 i386
create_package libxp6 i386
create_package libgtk2.0-0 i386
create_package libatk1.0-0 i386
create_package libc6 i386
create_package libcairo2 i386
create_package libexpat1 i386
create_package libfontconfig1 i386
create_package libfreetype6 i386
create_package libglib2.0-0 i386
create_package libice6 i386
create_package libpango1.0-0 i386
create_package libpng12-0 i386
create_package libsm6 i386
create_package libxau6 i386
create_package libxcursor1 i386
create_package libxdmcp6 i386
create_package libxfixes3 i386
create_package libx11-6 i386
create_package libxinerama1 i386
create_package libxrandr2 i386
create_package libxrender1 i386
create_package zlib1g i386
create_package libnss3-1d i386
create_package libnspr4-0d i386
create_package libcurl3 i386
create_package libasound2 i386
dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz
# visual studio code deps
mkdir -p $repo_root_dir/vscode
cd $repo_root_dir/vscode
create_package libgtk2.0-0
dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz
# arduino deps
mkdir -p $repo_root_dir/arduino
cd $repo_root_dir/arduino
create_package gcc-avr
create_package avr-libc
dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz
# scala deps
mkdir -p $repo_root_dir/scala
cd $repo_root_dir/scala
create_package openjdk-7-jre
create_package openjdk-8-jre
dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz
# kotlin deps
mkdir -p $repo_root_dir/kotlin
cd $repo_root_dir/kotlin
create_package openjdk-7-jre
create_package openjdk-8-jre
dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz
# swift deps
mkdir -p $repo_root_dir/swift
cd $repo_root_dir/swift
create_package clang
create_package libicu-dev
dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz
# unity3d deps
mkdir -p $repo_root_dir/unity3d
cd $repo_root_dir/unity3d
create_package gconf-service
create_package lib32gcc1
create_package lib32stdc++6
create_package libasound2
create_package libcairo2
create_package libcap2
create_package libcups2
create_package libfontconfig1
create_package libfreetype6
create_package libgconf-2-4
create_package libgdk-pixbuf2.0-0
create_package libgl1-mesa-glx
create_package libglu1-mesa
create_package libgtk2.0-0
create_package libnspr4
create_package libnss3
create_package libpango1.0-0
create_package libpq5
create_package libxcomposite1
create_package libxcursor1
create_package libxdamage1
create_package libxext6
create_package libxfixes3
create_package libxi6
create_package libxrandr2
create_package libxrender1
create_package libxtst6
create_package monodevelop
dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz
ubuntu-make-16.11.1ubuntu1/docker/umake_docker.pub 0000664 0000000 0000000 00000000614 13013560574 016747 0 ustar ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9iWeT1+JcYVzLUwqXJ4SnbpMmD2HhHBHAduFTLTFMHc5MZnwkHGzKiE3N9Usz7NRecnYzbmp5blx6bl0EabFmH8UrVwwiRJRhVyj67Xuyj07B0azOeIGpY561o8fsJX5D61IwFFqKao+J+ct3Nj2KJHHOKR9oJIkoPuEmdT81sBaKn9li137cUGbvPrppoNcWIIzEZ1q67SeeTsJrtOYharyGil2jwhjrXLUWm39ULZVHZSl0p06PcmgYLOTzxmOzPeVXmqxQhS7BimH8BOOACJuPeEGp0FYn41QOLiFIrcFtBOUbeYdU4x1arX0pWjaPj7LXC3tRQqdBcqJk38kX didrocks@tidus
ubuntu-make-16.11.1ubuntu1/docker/umake_docker 0000664 0000000 0000000 00000003213 13013560574 016160 0 ustar -----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAvYlnk9fiXGFcy1MKlyeEp26TJg9h4RwRwHbhUy0xTB3OTGZ8
JBxsyohNzfVLM+zUXnJ2M25qeW5cem5dBGmxZh/FK1cMIkSUYVco+u17so9OwdGs
zniBqWOetaPH7CV+Q+tSMBRaimqPifnLdzY9iiRxzikfaCSJKD7hJnU/NbAWip/Z
Ytd+3FBm7z66aaDXFiCMxGdauu0nnk7Ca7TmIWq8hopdo8IY61y1Fpt/VC2VR2Up
dKdOj3JoGCzk88Zjsz3lV5qsUIUuwYph/ATjgAibj3hBqdBWJ+NUDi4hSK3BbQTl
G3mHVOMdWq19KVo2j4+y1wt7UUKnQXKiZN/JFwIDAQABAoIBABmN4Q0p2jciWWSA
ebkPdu8sFWLYSBYVtr8ASDjyqubcTeg3GR21R2W3IuZV4CHMGIXzYMRmaqycmJNZ
NelWZriiJ+9D+TrVjDvjiH7sbfURJUk0f9wGm1S/PbK3tki8dV2q6JXa6Koo29l6
eFhGU93ANCfbm4RrCKMId0q8HB2cuH2zJBiTv/GAjmXBOhIlNDEbydkt9kMGqmVV
THh1pu9jGH+AnDfd0CBrW1IfRvjl49zkaEYq+FcWy/ksPFFMPKmiunPZWk+4jJUc
aJ5goYtTmNGu4W/yEfFDGLPrGAkLNAhSs2s2ssnwSQynDs+9SGQFav5S7fI71oJG
y67YL0ECgYEA8w7EeGCtn5UyqZmvAF0KD901INStHEfpzU5pD+y5xBh23kPDZw1a
+Zn0AKwkcc4oI0w0EcjaUshJSQc82cncdSGQGTzlnE6t6Z/CEJd9HjZG5DmlHx17
AV5Z0PRXMqgalSQEfu1Pm22KFKqa6gXA970gn7CODp0kgO9S7K4UEu8CgYEAx6ER
coQUiI5frwNKbgs4JQafxqZf+2+9xyHl4HQGn6EOhrBgaLmTC2+rz/RqCp8HRwV/
hBGwhJV4Eb1gEMxCTIBQJ+4YbuO+HPxv9cJdW3twtIROHoarRDwBhEnS4JnCbLvE
1uKoh99g0+5NWKZRvN8+xwS12uF+qgakWLkeDFkCgYB9ewA/TVoVawcuu+K3A1Fw
gzksa9+7G/0+Ot7Ok94LuL2VXdKBX0m6Vpq7xiNChbX/ExZGoDTmS/RZuVzW6vnf
lqY4AVJg8dWjKREdU7gKYucSaBgxCh04xacE00A5LMQcfu27QXS5v0FsDe/QJYxL
2d3/0zxjmwj/b46WFgDTDQKBgQCsD/kA0jT8inKQX268sLDgwPff+bELAAH77Ay9
zGOVHPVvRACk4yaJmePl5s2rf+x+249QHwsdC9OkvqxZbiTK2WG9OOwYT/Wh+Dbs
BW4AFsJK5SqMBxkBRBMumY7IBd9dZu4/JLeL/Q3xPRmvihMzjtwGH9o64VcSZ40p
58ytaQKBgBd+1TYt0U2nk2P6nqQDtGi2IfaY0dllbhYzjrwIXyfcslFriXjdHRBh
1d0X7CoRLFIlGBbQL59JyHxBK06Fdeght37xXlfzv2QjMdexqL92jbEu2OpwKcZ3
1uso5EgxFZAamQ9DP+v4k8KL1hFUsYLi/xFlBIvzzmzIq40mvpnW
-----END RSA PRIVATE KEY-----
ubuntu-make-16.11.1ubuntu1/README.md 0000664 0000000 0000000 00000016146 13013560574 013625 0 ustar # Ubuntu Make
Ubuntu Make is a project designed to enable quick and easy setup of common needs for developers on Ubuntu.
## Current project health
[](https://travis-ci.org/ubuntu/ubuntu-make) (pep8 and small tests)
[All test results](https://jenkins.qa.ubuntu.com/job/udtc-trusty-tests/) and [Coverage report](https://jenkins.qa.ubuntu.com/job/udtc-trusty-tests-collect/label=ps-trusty-desktop-amd64-1/lastSuccessfulBuild/artifact/html-coverage/index.html)
## Installing
We recommend to use the Ubuntu Make ppa to ensure you always have the latest and greatest version, even on older supported releases. We are available on the currently supported Ubuntu version.
```sh
$ sudo add-apt-repository ppa:ubuntu-desktop/ubuntu-make
$ sudo apt update
$ sudo apt install ubuntu-make
```
## Running the command line tool
To run the tool:
```sh
$ ./umake
```
You can use `--help` to get more information and change the verbosity of the output with `-v`, `-vv`.
## Requirements
> Note that this project uses python3 and requires at least python 3.3. All commands use the python 3 version. There are directions later on explaining how to install the corresponding virtualenv.
## Shell completion
To enable shell completion on bash or zsh, just run:
```sh
$ . enable_completion
```
## Different level of logging
Multiple logging profiles are available in *confs/* to be able to have different traces of your execution (particularly useful for debugging). For instance, you will find:
* **debug.logcfg**: Similar to using -vv, but also puts logs in a *debug.log*.
* **debug_network.logcfg**: The root logging level is INFO (-v), the network activities are in DEBUG mode and will be logged in *debug_network.log*.
* **testing.logcfg**: Mostly for coverage tests, do not set any logging config on stdout, but:
* DEBUG logs and above are available in *debug.log*.
* INFO logs and above are available in *info.log*.
* WARNING and ERROR logs are available in *error.log*.
Under normal circumstances, we expect *error.log* to remain empty../
To load one of those logging profiles:
```sh
$ LOG_CFG=confs/debug.logcfg bin/umake
```
## Development
### Providing user's framework
It's possible for anyone to have local frameworks for either development purposes or for special local or team use-cases.
* Any files in a directory set with the "UMAKE_FRAMEWORKS" environment variable will be loaded first.
* Any files inside ~/.umake/frameworks will be loaded next.
Any file should eventually contain a category or frameworks like the ones in umake/frameworks/*.
If category names are duplicated only one will be loaded. Ubuntu Make will first load the one controlled by the environment variable, then the one located in the home based directory, and finally, the system one.
Note that duplicate filenames are supported but not encouraged.
### Style guide and checking
We are running pep8, but the max line length has been relaxed to 120. env/ is excluded from the pep8 check as well.
Running this test, in particular:
```sh
$ ./runtests pep8
```
This will run those pep8 checks on the code.
You can also run the pep8 tool directly from the project directory:
```sh
$ pep8 .
```
### Tests
#### Types of tests
There are four types of tests that can be combined in runtests:
* **pep8**: Run the pep8 tests on all the umake and test code.
* **small**: Tests modules and components with mock content around them. Note that this uses a local webserver (http and https) to serve mock content.
* **medium**: Tests the whole workflow. It directly calls end user tools from the command line, but without affecting the local system. Requirements like installing packages are mocked, as well as the usage of a local webserver serving (smaller) content similar to what will be fetched in a real use case. The assets have the same formats and layout.
* **large**: Runs the same tests as the medium test, but with real server downloads and installation of dpkg packages. Most of these tests need root privileges. Be aware that these tests only run on a graphical environment. It will interfere with it and it is likely to install or remove packages on your system.
To run all the tests, with coverage report, like in our jenkins infra:
```sh
$ ./runtests
```
Use `--no-config` to disable the coverage report selection.
#### Running some tests with all debug infos
By default, **runtests** will not display any debug output if the tests are successful, similar to Nose. However, if only some tests are selected, runtests will a display full debug log,
```sh
$ ./runtests tests/small/test_tools.py:TestConfigHandler
```
Use `--no-config` to disable the debug output selection.
#### More information on runtests
**runtests** is a small nose wrapper used to simplify the testing process. By default, if no arguments are supplied or if "all" is supplied, runtests will run all available tests on the project using the production nose configuration.
It is possible to run only some types of tests:
```sh
$ ./runtests small medium
```
This will only run small and medium tests, with all nose defaults (no profile is selected).
Finally, you can run a selection of one or more tests:
```sh
$ ./runtests tests/small/test_tools.py:TestConfigHandler
```
This enables the debug profile by default, to display all outputs and logging information (in debug level).
You can activate/disable/change any of those default selected configurations with **--config/--coverage/--debug/--no-config** (see `runtests --help` for more information)
#### Nose configurations
Some nose configurations are available in **confs/**. You will find:
* **debug.nose**: this profile shows all outputs and logging information while turning debug logging on.
* **prod.nose**: this profile keep all outputs captured, but display tests coverage results.
#### Check for python warnings:
**runtests** is compatible with showing the python warnings:
```sh
$ PYTHONWARNINGS=d ./runtests
```
### Create your own environment and run from it
For an easier development workflow, we encourage the use of virtualenv to test and iterate on the project rather than installing all the requirements on your machine. In the project root directory run (env/ is already in .gitignore and excluded from pep8 checking):
```sh
$ virtualenv --python=python3 --system-site-packages env
$ sudo apt-get install -qq apt apt-utils libapt-pkg-dev # those are the requirements to compile python-apt
$ sudo apt-get install -qq python3-gi # not installable with pypi
$ sudo apt-get install -qq bzr python3-dev # requires for pip install -r
$ env/bin/pip install -r requirements.txt
$ source env/bin/activate
$ bin/umake
```
### Developing using system package
Instead of using a virtual environment, you can install system packages to be able to run the Ubuntu Make tests. The build dependencies are listed in *debian/control* and should be available in latest development ubuntu version. If you are using the latest LTS, you should find them in a dedicated [Ubuntu Make Build-dep ppa](https://launchpad.net/~ubuntu-desktop/+archive/ubuntu/ubuntu-make-builddeps).
## Release management
Refresh .pot files:
```sh
$ ./setup.py update_pot
```
ubuntu-make-16.11.1ubuntu1/umake/ 0000775 0000000 0000000 00000000000 13135606406 013440 5 ustar ubuntu-make-16.11.1ubuntu1/umake/__init__.py 0000664 0000000 0000000 00000011234 13013560574 015552 0 ustar # -*- coding: utf-8 -*-
# Copyright (C) 2014 Canonical
#
# Authors:
# Didier Roche
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import argparse
import gettext
from gettext import gettext as _
import locale
import logging
import logging.config
import os
import sys
from umake.frameworks import BaseCategory, load_frameworks
from umake.tools import MainLoop
from .ui import cli
import yaml
logger = logging.getLogger(__name__)
# if set locale isn't installed, don't load up translations (we don't know what's the locale
# user encoding is and python3 will fallback to ANSI_X3.4-1968, which isn't UTF-8 and creates
# thus UnicodeEncodeError)
try:
locale.setlocale(locale.LC_ALL, '')
gettext.textdomain("ubuntu-make")
except locale.Error:
logger.debug("Couldn't load default locale {}, fallback to English".format(locale.LC_ALL))
_default_log_level = logging.WARNING
_datadir = None
def _setup_logging(env_key='LOG_CFG', level=_default_log_level):
"""Setup logging configuration
Order of preference:
- manually define level
- env_key env variable if set (logging config file)
- fallback to _default_log_level
"""
path = os.getenv(env_key, '')
logging.basicConfig(level=level, format="%(levelname)s: %(message)s")
if level == _default_log_level:
if os.path.exists(path):
with open(path, 'rt') as f:
config = yaml.load(f.read())
logging.config.dictConfig(config)
logging.info("Logging level set to {}".format(logging.getLevelName(logging.root.getEffectiveLevel())))
def set_logging_from_args(args, parser):
"""Choose logging ignoring any unknown sys.argv options"""
result_verbosity_arg = []
for arg in args:
if arg.startswith("-v"):
for char in arg:
if char not in ['-', 'v']:
break
else:
result_verbosity_arg.append(arg)
args = parser.parse_args(result_verbosity_arg)
# setup logging level if set by the command line
if args.verbose == 1:
_setup_logging(level=logging.INFO)
elif args.verbose > 1:
_setup_logging(level=logging.DEBUG)
else:
_setup_logging()
class _HelpAction(argparse._HelpAction):
def __call__(self, parser, namespace, values, option_string=None):
parser.print_help()
# retrieve subparsers from parser
subparsers_actions = [
action for action in parser._actions
if isinstance(action, argparse._SubParsersAction)]
for subparsers_action in subparsers_actions:
# get all subparsers and print help
for choice, subparser in subparsers_action.choices.items():
print(_("* Command '{}':").format(choice))
print(subparser.format_help())
parser.exit()
def main():
"""Main entry point of the program"""
if "udtc" in sys.argv[0]:
print(_("WARNING: 'udtc' command is the previous name of Ubuntu Make. Please use the 'umake' command from now "
"on providing the exact same features. The 'udtc' command will be removed soon."))
parser = argparse.ArgumentParser(description=_("Deploy and setup developers environment easily on ubuntu"),
epilog=_("Note that you can also configure different debug logging behavior using "
"LOG_CFG that points to a log yaml profile."),
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('-r', '--remove', action="store_true", help=_("Remove specified framework if installed"))
parser.add_argument('--version', action="store_true", help=_("Print version and exit"))
# set logging ignoring unknown options
set_logging_from_args(sys.argv, parser)
mainloop = MainLoop()
# load frameworks and initialize parser
load_frameworks()
cli.main(parser)
mainloop.run()
ubuntu-make-16.11.1ubuntu1/umake/frameworks/ 0000775 0000000 0000000 00000000000 13135606406 015620 5 ustar ubuntu-make-16.11.1ubuntu1/umake/frameworks/dart.py 0000664 0000000 0000000 00000007206 13013560574 017131 0 ustar # -*- coding: utf-8 -*-
# Copyright (C) 2014 Canonical
#
# Authors:
# Didier Roche
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Dartlang module"""
from contextlib import suppress
from gettext import gettext as _
import logging
import os
import platform
import re
import umake.frameworks.baseinstaller
from umake.interactions import DisplayMessage
from umake.network.download_center import DownloadItem
from umake.tools import add_env_to_user, MainLoop
from umake.ui import UI
logger = logging.getLogger(__name__)
_supported_archs = ['i386', 'amd64']
class DartCategory(umake.frameworks.BaseCategory):
def __init__(self):
super().__init__(name="Dart", description=_("Dartlang Development Environment"), logo_path=None)
class DartLangEditorRemoval(umake.frameworks.baseinstaller.BaseInstaller):
def __init__(self, category):
super().__init__(name="Dart Editor", description=_("Dart SDK with editor (not supported upstream anyymore)"),
download_page=None, category=category, only_on_archs=_supported_archs, only_for_removal=True)
class DartLang(umake.frameworks.baseinstaller.BaseInstaller):
def __init__(self, category):
super().__init__(name="Dart SDK", description=_("Dart SDK (default)"), is_category_default=True,
category=category, only_on_archs=_supported_archs,
download_page="https://api.dartlang.org",
dir_to_decompress_in_tarball="dart-sdk",
required_files_path=[os.path.join("bin", "dart")])
@MainLoop.in_mainloop_thread
def get_metadata_and_check_license(self, result):
"""Get latest version and append files to download"""
logger.debug("Set download metadata")
error_msg = result[self.download_page].error
if error_msg:
logger.error("An error occurred while downloading {}: {}".format(self.download_page, error_msg))
UI.return_main_screen(status_code=1)
version = ''
version_re = r'Dart SDK ([\d\.]+)'
for line in result[self.download_page].buffer:
p = re.search(version_re, line.decode())
with suppress(AttributeError):
version = p.group(1)
break
else:
logger.error("Download page changed its syntax or is not parsable")
UI.return_main_screen(status_code=1)
tag_machine = 'x64'
if platform.machine() == 'i686':
tag_machine = 'ia32'
url = "https://storage.googleapis.com/dart-archive/channels/stable/release/{}/sdk/dartsdk-linux-{}-release.zip"\
.format(version, tag_machine)
logger.debug("Found download link for {}".format(url))
self.download_requests.append(DownloadItem(url, None))
self.start_download_and_install()
def post_install(self):
"""Add go necessary env variables"""
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)))
ubuntu-make-16.11.1ubuntu1/umake/frameworks/__init__.py 0000664 0000000 0000000 00000037672 13013560574 017750 0 ustar # -*- coding: utf-8 -*-
# Copyright (C) 2014 Canonical
#
# Authors:
# Didier Roche
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Base Handling functions and base class of backends"""
import abc
from contextlib import suppress
from gettext import gettext as _
from importlib import import_module, reload
import inspect
import logging
import os
import pkgutil
import sys
import subprocess
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_ubuntu_version,\
is_completion_mode, switch_to_current_user, MainLoop, get_user_frameworks_path
from umake.ui import UI
logger = logging.getLogger(__name__)
class BaseCategory():
"""Base Category class to be inherited"""
NOT_INSTALLED, PARTIALLY_INSTALLED, FULLY_INSTALLED = range(3)
categories = NoneDict()
def __init__(self, name, description="", logo_path=None, is_main_category=False, packages_requirements=None):
self.name = name
self.description = description
self.logo_path = logo_path
self.is_main_category = is_main_category
self.default = None
self.frameworks = NoneDict()
self.packages_requirements = [] if packages_requirements is None else packages_requirements
if self.prog_name in self.categories:
logger.warning("There is already a registered category with {} as a name. Don't register the second one."
.format(name))
else:
self.categories[self.prog_name] = self
@classproperty
def main_category(self):
for category in self.categories.values():
if category.is_main_category:
return category
return None
@property
def prog_name(self):
"""Get programmatic, path and CLI compatible names"""
return self.name.lower().replace('/', '-').replace(' ', '-')
@property
def default_framework(self):
"""Get default framework"""
for framework in self.frameworks.values():
if framework.is_category_default:
return framework
return None
def register_framework(self, framework):
"""Register a new framework"""
if framework.prog_name in self.frameworks:
logger.error("There is already a registered framework with {} as a name. Don't register the second one."
.format(framework.name))
else:
self.frameworks[framework.prog_name] = framework
@property
def is_installed(self):
"""Return if the category is installed"""
installed_frameworks = [framework for framework in self.frameworks.values() if framework.is_installed]
if len(installed_frameworks) == 0:
return self.NOT_INSTALLED
if len(installed_frameworks) == len(self.frameworks):
return self.FULLY_INSTALLED
return self.PARTIALLY_INSTALLED
def install_category_parser(self, parser):
"""Install category parser and get frameworks"""
if not self.has_frameworks():
logging.debug("Skipping {} having no framework".format(self.name))
return
# framework parser is directly category parser
if self.is_main_category:
framework_parser = parser
else:
self.category_parser = parser.add_parser(self.prog_name, help=self.description)
framework_parser = self.category_parser.add_subparsers(dest="framework")
for framework in self.frameworks.values():
framework.install_framework_parser(framework_parser)
return framework_parser
def has_frameworks(self):
"""Return if a category has at least one framework"""
return len(self.frameworks) > 0
def has_one_framework(self):
"""Return if a category has one framework"""
return len(self.frameworks) == 1
def run_for(self, args):
"""Running commands from args namespace"""
# try to call default framework if any
if not args.framework:
if not self.default_framework:
message = _("A default framework for category {} was requested where there is none".format(self.name))
logger.error(message)
self.category_parser.print_usage()
UI.return_main_screen(status_code=1)
self.default_framework.run_for(args)
return
self.frameworks[args.framework].run_for(args)
class BaseFramework(metaclass=abc.ABCMeta):
def __init__(self, name, description, category, logo_path=None, is_category_default=False, install_path_dir=None,
only_on_archs=None, only_ubuntu_version=None, packages_requirements=None, only_for_removal=False,
expect_license=False, need_root_access=False):
self.name = name
self.description = description
self.logo_path = None
self.category = category
self.is_category_default = is_category_default
self.only_on_archs = [] if only_on_archs is None else only_on_archs
self.only_ubuntu_version = [] if only_ubuntu_version is None else only_ubuntu_version
self.packages_requirements = [] if packages_requirements is None else packages_requirements
self.packages_requirements.extend(self.category.packages_requirements)
self.only_for_removal = only_for_removal
self.expect_license = expect_license
# don't detect anything for completion mode (as we need to be quick), so avoid opening apt cache and detect
# if it's installed.
if is_completion_mode():
# only show it in shell completion if it was already installed
if self.only_for_removal:
config = ConfigHandler().config
try:
if not os.path.isdir(config["frameworks"][category.prog_name][self.prog_name]["path"]):
# don't show the framework in shell completion as for removal only and not installed
return
except (TypeError, KeyError, FileNotFoundError):
# don't show the framework in shell completion as for removal only and not installed
return
category.register_framework(self)
return
self.need_root_access = need_root_access
if not need_root_access:
with suppress(KeyError):
self.need_root_access = not RequirementsHandler().is_bucket_installed(self.packages_requirements)
if self.is_category_default:
if self.category == BaseCategory.main_category:
logger.error("Main category can't have default framework as {} requires".format(name))
self.is_category_default = False
elif self.category.default_framework is not None:
logger.error("Can't set {} as default for {}: this category already has a default framework ({}). "
"Don't set any as default".format(category.name, name,
self.category.default_framework.name))
self.is_category_default = False
self.category.default_framework.is_category_default = False
if not install_path_dir:
install_path_dir = os.path.join("" if category.is_main_category else category.prog_name, self.prog_name)
self.default_install_path = os.path.join(DEFAULT_INSTALL_TOOLS_PATH, install_path_dir)
self.default_binary_link_path = DEFAULT_BINARY_LINK_PATH
self.install_path = self.default_install_path
# check if we have an install path previously set
config = ConfigHandler().config
try:
self.install_path = config["frameworks"][category.prog_name][self.prog_name]["path"]
except (TypeError, KeyError, FileNotFoundError):
pass
# This requires install_path and will register need_root or not
if not self.is_installed and not self.is_installable:
logger.info("Don't register {} as it's not installable on this configuration.".format(name))
return
category.register_framework(self)
@property
def is_installable(self):
"""Return if the framework can be installed on that arch"""
if self.only_for_removal:
return False
try:
if len(self.only_on_archs) > 0:
# we have some restricted archs, check we support it
current_arch = get_current_arch()
if current_arch not in self.only_on_archs:
logger.debug("{} only supports {} archs and you are on {}.".format(self.name, self.only_on_archs,
current_arch))
return False
if len(self.only_ubuntu_version) > 0:
current_version = get_current_ubuntu_version()
if current_version not in self.only_ubuntu_version:
logger.debug("{} only supports {} and you are on {}.".format(self.name, self.only_ubuntu_version,
current_version))
return False
if not RequirementsHandler().is_bucket_available(self.packages_requirements):
return False
except:
logger.error("An error occurred when detecting platform, don't register {}".format(self.name))
return False
return True
@property
def prog_name(self):
"""Get programmatic, path and CLI compatible names"""
return self.name.lower().replace('/', '-').replace(' ', '-')
@abc.abstractmethod
def setup(self):
"""Method call to setup the Framework"""
if not self.is_installable:
logger.error(_("You can't install that framework on this machine"))
UI.return_main_screen(status_code=1)
if self.need_root_access and os.geteuid() != 0:
logger.debug("Requesting root access")
cmd = ["sudo", "-E", "env", "PATH={}".format(os.getenv("PATH"))]
cmd.extend(sys.argv)
MainLoop().quit(subprocess.call(cmd))
# be a normal, kind user as we don't want normal files to be written as root
switch_to_current_user()
@abc.abstractmethod
def remove(self):
"""Method call to remove the current framework"""
if not self.is_installed:
logger.error(_("You can't remove {} as it isn't installed".format(self.name)))
UI.return_main_screen(status_code=1)
def mark_in_config(self):
"""Mark the installation as installed in the config file"""
config = ConfigHandler().config
config.setdefault("frameworks", {})\
.setdefault(self.category.prog_name, {})\
.setdefault(self.prog_name, {})["path"] = self.install_path
ConfigHandler().config = config
def remove_from_config(self):
"""Remove current framework from config"""
config = ConfigHandler().config
del(config["frameworks"][self.category.prog_name][self.prog_name])
ConfigHandler().config = config
@property
def is_installed(self):
"""Method call to know if the framework is installed"""
if not os.path.isdir(self.install_path):
return False
if not RequirementsHandler().is_bucket_installed(self.packages_requirements):
return False
return True
def install_framework_parser(self, parser):
"""Install framework parser"""
this_framework_parser = parser.add_parser(self.prog_name, help=self.description)
this_framework_parser.add_argument('destdir', nargs='?', help=_("If the default framework name isn't provided, "
"destdir should contain a /"))
this_framework_parser.add_argument('-r', '--remove', action="store_true",
help=_("Remove framework if installed"))
if self.expect_license:
this_framework_parser.add_argument('--accept-license', dest="accept_license", action="store_true",
help=_("Accept license without prompting"))
return this_framework_parser
def run_for(self, args):
"""Running commands from args namespace"""
logger.debug("Call run_for on {}".format(self.name))
if args.remove:
if args.destdir:
message = "You can't specify a destination dir while removing a framework"
logger.error(message)
UI.return_main_screen(status_code=1)
self.remove()
else:
install_path = None
auto_accept_license = 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
self.setup(install_path=install_path, auto_accept_license=auto_accept_license)
class MainCategory(BaseCategory):
def __init__(self):
super().__init__(name="main", is_main_category=True)
def _is_categoryclass(o):
return inspect.isclass(o) and issubclass(o, BaseCategory)
def _is_frameworkclass(o):
"""Filter concrete (non-abstract) subclasses of BaseFramework."""
return inspect.isclass(o) and issubclass(o, BaseFramework) and not inspect.isabstract(o)
def load_module(module_abs_name, main_category):
logger.debug("New framework module: {}".format(module_abs_name))
if module_abs_name not in sys.modules:
import_module(module_abs_name)
else:
reload(sys.modules[module_abs_name])
module = sys.modules[module_abs_name]
current_category = main_category # if no category found -> we assign to main category
for category_name, CategoryClass in inspect.getmembers(module, _is_categoryclass):
logger.debug("Found category: {}".format(category_name))
current_category = CategoryClass()
# if we didn't register the category: escape the framework registration
if current_category not in BaseCategory.categories.values():
return
for framework_name, FrameworkClass in inspect.getmembers(module, _is_frameworkclass):
if FrameworkClass(current_category) is not None:
logger.debug("Attach framework {} to {}".format(framework_name, current_category.name))
def load_frameworks():
"""Load all modules and assign to correct category"""
main_category = MainCategory()
# Prepare local paths (1. environment path, 2. local path, 3. system paths).
# If we have duplicated categories, only consider the first loaded one.
local_paths = [get_user_frameworks_path()]
sys.path.insert(0, get_user_frameworks_path())
environment_path = os.environ.get(UMAKE_FRAMEWORKS_ENVIRON_VARIABLE)
if environment_path:
sys.path.insert(0, environment_path)
local_paths.insert(0, environment_path)
for loader, module_name, ispkg in pkgutil.iter_modules(path=local_paths):
load_module(module_name, main_category)
for loader, module_name, ispkg in pkgutil.iter_modules(path=[os.path.dirname(__file__)]):
module_name = "{}.{}".format(__package__, module_name)
load_module(module_name, main_category)
ubuntu-make-16.11.1ubuntu1/umake/frameworks/__pycache__/ 0000755 0000000 0000000 00000000000 13135606406 020026 5 ustar ubuntu-make-16.11.1ubuntu1/umake/frameworks/games.py 0000664 0000000 0000000 00000030734 13013560574 017275 0 ustar # -*- coding: utf-8 -*-
# Copyright (C) 2014-2015 Canonical
#
# Authors:
# Didier Roche
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Game IDEs module"""
from concurrent import futures
from contextlib import suppress
from gettext import gettext as _
import logging
import os
import re
import stat
import json
import umake.frameworks.baseinstaller
from umake.network.download_center import DownloadItem
from umake.tools import as_root, create_launcher, get_application_desktop_file, get_current_arch,\
ChecksumType, MainLoop, Checksum
from umake.ui import UI
logger = logging.getLogger(__name__)
class GamesCategory(umake.frameworks.BaseCategory):
def __init__(self):
super().__init__(name="Games", description=_("Games Development Environment"), logo_path=None)
class Stencyl(umake.frameworks.baseinstaller.BaseInstaller):
def __init__(self, category):
super().__init__(name="Stencyl", description=_("Stencyl game developer IDE"),
category=category, only_on_archs=['i386', 'amd64'],
download_page="http://www.stencyl.com/download/",
desktop_filename="stencyl.desktop",
required_files_path=["Stencyl"],
packages_requirements=["libxtst6:i386", "libxext6:i386", "libxi6:i386", "libncurses5:i386",
"libxt6:i386", "libxpm4:i386", "libxmu6:i386",
"libgtk2.0-0:i386", "libatk1.0-0:i386", "libc6:i386", "libcairo2:i386",
"libexpat1:i386", "libfontconfig1:i386", "libfreetype6:i386",
"libglib2.0-0:i386", "libice6:i386", "libpango1.0-0:i386",
"libpng12-0:i386", "libsm6:i386", "libxau6:i386", "libxcursor1:i386",
"libxdmcp6:i386", "libxfixes3:i386", "libx11-6:i386",
"libxinerama1:i386", "libxrandr2:i386", "libxrender1:i386",
"zlib1g:i386", "libnss3-1d:i386", "libnspr4-0d:i386", "libcurl3:i386",
"libasound2:i386"])
def parse_download_link(self, line, in_download):
"""Parse Stencyl download links"""
url, md5sum = (None, None)
if ">Linux <" in line:
in_download = True
if in_download:
regexp = r'href="(.*)"><.*64-'
if get_current_arch() == "i386":
regexp = r'href="(.*)"><.*32-'
p = re.search(regexp, line)
with suppress(AttributeError):
url = p.group(1)
if '
' in line:
in_download = False
if url is None:
return (None, in_download)
return ((url, None), in_download)
def post_install(self):
"""Create the Stencyl launcher"""
create_launcher(self.desktop_filename, get_application_desktop_file(name=_("Stencyl"),
icon_path=os.path.join(self.install_path, "data", "other", "icon-30x30.png"),
exec='"{}" %f'.format(self.exec_path),
comment=self.description,
categories="Development;IDE;",
extra="Path={}\nStartupWMClass=stencyl-sw-Launcher".format(self.install_path)))
def _chrome_sandbox_setuid(path):
"""Chown and setUID to chrome sandbox"""
# switch to root
with as_root():
try:
os.chown(path, 0, -1)
os.chmod(path, stat.S_ISUID | stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
logger.debug("Changed setUID mode {}".format(path))
return True
except Exception as e:
logger.error("Couldn't change owner and file perm to {}: {}".format(path, e))
return False
class Unity3D(umake.frameworks.baseinstaller.BaseInstaller):
def __init__(self, category):
super().__init__(name="Unity3d", description=_("Unity 3D Editor Linux experimental support"),
category=category, only_on_archs=['amd64'],
download_page="https://community.unity.com/t5/" +
"Linux-Editor/Unity-on-Linux-Release-Notes-and-Known-Issues/m-p/2323665",
match_last_link=True,
checksum_type=ChecksumType.sha1,
dir_to_decompress_in_tarball='unity-editor*',
desktop_filename="unity3d-editor.desktop",
required_files_path=[os.path.join("Editor", "Unity")],
# we need root access for chrome sandbox setUID
need_root_access=True,
# Note that some packages requirements essential to the system itself are not listed (we
# don't want to create fake packages and kill the container for medium tests)
packages_requirements=[
"gconf-service", "lib32gcc1", "lib32stdc++6", "libasound2", "libcairo2",
"libcap2", "libcups2", "libfontconfig1", "libfreetype6", "libgconf-2-4",
"libgdk-pixbuf2.0-0", "libglu1-mesa", "libgtk2.0-0",
"libgl1-mesa-glx | libgl1-mesa-glx-lts-utopic |\
libgl1-mesa-glx-lts-vivid | libgl1-mesa-glx-lts-wily",
"libnspr4", "libnss3", "libpango1.0-0", "libpq5", "libxcomposite1",
"libxcursor1", "libxdamage1", "libxext6", "libxfixes3", "libxi6",
"libxrandr2", "libxrender1", "libxtst6",
"monodevelop"]) # monodevelop is for mono deps, temporary
def parse_download_link(self, line, in_download):
"""Parse Unity3d download links"""
url, sha1 = (None, None)
if ".sh" in line:
in_download = True
p = re.search(r'.deb.*.href="(.*.sh)"', line)
with suppress(AttributeError):
url = p.group(1)
if in_download is True:
p = re.search(r'sha1sum (\w+)\)
Torrent', line)
with suppress(AttributeError):
sha1 = p.group(1)
return ((url, sha1), in_download)
def decompress_and_install(self, fds):
"""Override to strip the unwanted shell header part"""
logger.debug("Start looking at the archive inside the script")
for line in fds[0]:
if line.startswith(b"__ARCHIVE_BEGINS_HERE__"):
logger.debug("Found the archive inside the script")
break
super().decompress_and_install(fds)
def post_install(self):
"""Create the Unity 3D launcher and setuid chrome sandbox"""
with futures.ProcessPoolExecutor(max_workers=1) as executor:
# chrome sandbox requires this: https//code.google.com/p/chromium/wiki/LinuxSUIDSandbox
f = executor.submit(_chrome_sandbox_setuid, os.path.join(self.install_path, "Editor", "chrome-sandbox"))
if not f.result():
UI.return_main_screen(exit_status=1)
create_launcher(self.desktop_filename, get_application_desktop_file(name=_("Unity3D Editor"),
icon_path=os.path.join(self.install_path, "unity-editor-icon.png"),
exec=self.exec_path,
comment=self.description,
categories="Development;IDE;"))
class Twine(umake.frameworks.baseinstaller.BaseInstaller):
def __init__(self, category):
super().__init__(name="Twine", description=_("Twine tool for creating interactive and nonlinear stories"),
category=category, only_on_archs=['i386', 'amd64'],
download_page="http://twinery.org/",
dir_to_decompress_in_tarball='twine*',
desktop_filename="twine.desktop",
required_files_path=["Twine"])
# add logo download as the tar doesn't provide one
self.download_requests.append(DownloadItem("http://twinery.org/img/logo.svg", None))
def parse_download_link(self, line, in_download):
"""Parse Twine download links"""
url = None
regexp = r'href="(.*)" .*linux64'
if get_current_arch() == "i386":
regexp = r'href="(.*)" .*linux32'
p = re.search(regexp, line)
with suppress(AttributeError):
url = p.group(1)
return ((url, None), False)
def decompress_and_install(self, fds):
# if icon, we grab the icon name to reference it later on
for fd in fds:
if fd.name.endswith(".svg"):
orig_icon_name = os.path.basename(fd.name)
break
else:
logger.error("We couldn't download the Twine icon")
UI.return_main_screen(exit_status=1)
super().decompress_and_install(fds)
# rename the asset logo
self.icon_name = "logo.svg"
os.rename(os.path.join(self.install_path, orig_icon_name), os.path.join(self.install_path, self.icon_name))
def post_install(self):
"""Create the Twine launcher"""
create_launcher(self.desktop_filename, get_application_desktop_file(name=_("Twine"),
icon_path=os.path.join(self.install_path, self.icon_name),
exec='"{}" %f'.format(self.exec_path),
comment=self.description,
categories="Development;IDE;"))
class Superpowers(umake.frameworks.baseinstaller.BaseInstaller):
def __init__(self, category):
super().__init__(name="Superpowers", description=_("The HTML5 2D+3D game maker"),
category=category, only_on_archs=['i386', 'amd64'],
download_page="https://api.github.com/repos/superpowers/superpowers-app/releases/latest",
dir_to_decompress_in_tarball='superpowers*',
desktop_filename="superpowers.desktop",
required_files_path=["Superpowers"])
arch_trans = {
"amd64": "x64",
"i386": "ia32"
}
@MainLoop.in_mainloop_thread
def get_metadata_and_check_license(self, result):
logger.debug("Fetched download page, parsing.")
page = result[self.download_page]
error_msg = page.error
if error_msg:
logger.error("An error occurred while downloading {}: {}".format(self.download_page, error_msg))
UI.return_main_screen(status_code=1)
try:
assets = json.loads(page.buffer.read().decode())["assets"]
download_url = None
for asset in assets:
if "linux-{}".format(self.arch_trans[get_current_arch()]) in asset["browser_download_url"]:
download_url = asset["browser_download_url"]
if not download_url:
raise IndexError
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: " + download_url)
self.download_requests.append(DownloadItem(download_url, None))
self.start_download_and_install()
def post_install(self):
"""Create the Superpowers launcher"""
create_launcher(self.desktop_filename, get_application_desktop_file(name=_("Superpowers"),
icon_path=os.path.join(self.install_path, "resources", "app", "renderer",
"images", "superpowers-256.png"),
exec='"{}" %f'.format(self.exec_path),
comment=self.description,
categories="Development;IDE;"))
ubuntu-make-16.11.1ubuntu1/umake/frameworks/swift.py 0000664 0000000 0000000 00000014364 13013560574 017336 0 ustar # -*- coding: utf-8 -*-
# Copyright (C) 2014 Canonical
#
# Authors:
# Didier Roche
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Swift module"""
from contextlib import suppress
from gettext import gettext as _
import gnupg
import logging
import os
import re
import tempfile
import umake.frameworks.baseinstaller
from umake.interactions import DisplayMessage
from umake.tools import add_env_to_user, as_root, MainLoop, get_current_ubuntu_version
from umake.network.download_center import DownloadCenter, DownloadItem
from umake.ui import UI
logger = logging.getLogger(__name__)
class SwiftCategory(umake.frameworks.BaseCategory):
def __init__(self):
super().__init__(name="Swift", description=_("Swift language"),
logo_path=None)
class SwiftLang(umake.frameworks.baseinstaller.BaseInstaller):
def __init__(self, category):
super().__init__(name="Swift Lang", description=_("Swift compiler (default)"), is_category_default=True,
packages_requirements=["clang", "libicu-dev"],
category=category, only_on_archs=['amd64'],
download_page="https://swift.org/download/",
dir_to_decompress_in_tarball="swift*",
required_files_path=[os.path.join("usr", "bin", "swift")])
self.asc_url = "https://swift.org/keys/all-keys.asc"
def parse_download_link(self, line, in_download):
"""Parse Swift download link, expect to find a .sig file"""
sig_url = None
if '.tar.gz.sig' in line:
in_download = True
if in_download:
p = re.search(r'href="(.*)" title="PGP Signature"', line)
with suppress(AttributeError):
sig_url = "https://swift.org" + p.group(1)
logger.debug("Found signature link: {}".format(sig_url))
return (sig_url, in_download)
@MainLoop.in_mainloop_thread
def get_metadata_and_check_license(self, result):
"""Download files to download + license and check it"""
logger.debug("Parse download metadata")
error_msg = result[self.download_page].error
if error_msg:
logger.error("An error occurred while downloading {}: {}".format(self.download_page, error_msg))
UI.return_main_screen(status_code=1)
in_download = False
sig_url = None
for line in result[self.download_page].buffer:
line_content = line.decode()
(new_sig_url, in_download) = self.parse_download_link(line_content, in_download)
if str(new_sig_url) > str(sig_url):
tmp_release = re.search("ubuntu(.....).tar", new_sig_url).group(1)
if tmp_release <= get_current_ubuntu_version():
sig_url = new_sig_url
if not sig_url:
logger.error("Download page changed its syntax or is not parsable")
UI.return_main_screen(status_code=1)
DownloadCenter(urls=[DownloadItem(sig_url, None), DownloadItem(self.asc_url, None)],
on_done=self.check_gpg_and_start_download, download=False)
def _check_gpg_signature(self, gnupgdir, asc_content, sig):
"""check gpg signature (temporary stock in dir)"""
gpg = gnupg.GPG(gnupghome=gnupgdir)
imported_keys = gpg.import_keys(asc_content)
if imported_keys.count == 0:
logger.error("Keys not valid")
UI.return_main_screen(status_code=1)
verify = gpg.verify(sig)
if verify is False:
logger.error("Signature not valid")
UI.return_main_screen(status_code=1)
@MainLoop.in_mainloop_thread
def check_gpg_and_start_download(self, download_result):
asc_content = download_result.pop(self.asc_url).buffer.getvalue().decode('utf-8')
sig_url = list(download_result.keys())[0]
res = download_result[sig_url]
sig = res.buffer.getvalue().decode('utf-8').split()[0]
# When we install new packages, we are executing as root and then dropping
# as the user for extracting and such. However, for signature verification,
# we use gpg. This one doesn't like priviledge drop (if uid = 0 and
# euid = 1000) and asserts if uid != euid.
# Importing the key as root as well creates new gnupg files owned as root if
# new keys weren't imported first.
# Consequently, run gpg as root if we needed root access or as the user
# otherwise. We store the gpg public key in a temporary gnupg directory that
# will be removed under the same user rights (this directory needs to be owned
# by the same user id to not be rejected by gpg).Z
if self.need_root_access:
with as_root():
with tempfile.TemporaryDirectory() as tmpdirname:
self._check_gpg_signature(tmpdirname, asc_content, sig)
else:
with tempfile.TemporaryDirectory() as tmpdirname:
self._check_gpg_signature(tmpdirname, asc_content, sig)
# you get and store self.download_url
url = re.sub('.sig', '', sig_url)
if url is None:
logger.error("Download page changed its syntax or is not parsable (missing url)")
UI.return_main_screen(status_code=1)
logger.debug("Found download link for {}".format(url))
self.download_requests.append(DownloadItem(url, None))
self.start_download_and_install()
def post_install(self):
"""Add swift necessary env variables"""
add_env_to_user(self.name, {"PATH": {"value": os.path.join(self.install_path, "usr", "bin")}})
UI.delayed_display(DisplayMessage(self.RELOGIN_REQUIRE_MSG.format(self.name)))
ubuntu-make-16.11.1ubuntu1/umake/frameworks/baseinstaller.py 0000664 0000000 0000000 00000045262 13013560574 021033 0 ustar # -*- coding: utf-8 -*-
# Copyright (C) 2014 Canonical
#
# Authors:
# Didier Roche
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Downloader abstract module"""
from contextlib import suppress
from gettext import gettext as _
from io import StringIO
import logging
from progressbar import ProgressBar
import os
import shutil
import umake.frameworks
from umake.decompressor import Decompressor
from umake.interactions import InputText, YesNo, LicenseAgreement, DisplayMessage, UnknownProgress
from umake.network.download_center import DownloadCenter, DownloadItem
from umake.network.requirements_handler import RequirementsHandler
from umake.ui import UI
from umake.tools import MainLoop, strip_tags, launcher_exists, get_icon_path, get_launcher_path, \
Checksum, remove_framework_envs_from_user, add_exec_link
logger = logging.getLogger(__name__)
class BaseInstaller(umake.frameworks.BaseFramework):
DIRECT_COPY_EXT = ['.svg', '.png', '.ico', '.jpg', '.jpeg']
# Framework environment variables are added to `~/.profile` which may
# require logging back into your session for the changes to be picked up.
# Use `RELOGIN_REQUIRE_MSG` to alert users to this fact, in `post_install`
# function. {} in replaced with the framework name at runtime.
RELOGIN_REQUIRE_MSG = _("You may need to log back in for your {} installation to work properly")
def __new__(cls, *args, **kwargs):
"This class is not meant to be instantiated, so __new__ returns None."
if cls == BaseInstaller:
return None
return super().__new__(cls)
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.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", "")
self.required_files_path = kwargs.get("required_files_path", [])
self.desktop_filename = kwargs.get("desktop_filename", None)
self.icon_filename = kwargs.get("icon_filename", None)
self.match_last_link = kwargs.get("match_last_link", False)
for extra_arg in ["download_page", "checksum_type", "dir_to_decompress_in_tarball",
"desktop_filename", "icon_filename", "required_files_path",
"match_last_link"]:
with suppress(KeyError):
kwargs.pop(extra_arg)
super().__init__(*args, **kwargs)
self._install_done = False
self._paths_to_clean = set()
self._arg_install_path = None
self.download_requests = []
@property
def exec_link_name(self):
if self.desktop_filename:
return self.desktop_filename.split('.')[0]
return None
@property
def is_installed(self):
# check path and requirements
if not super().is_installed:
return False
for required_file_path in self.required_files_path:
if not os.path.exists(os.path.join(self.install_path, required_file_path)):
logger.debug("{} binary isn't installed".format(self.name))
return False
if self.desktop_filename:
return launcher_exists(self.desktop_filename)
logger.debug("{} is installed".format(self.name))
return True
def setup(self, install_path=None, auto_accept_license=False):
self.arg_install_path = install_path
self.auto_accept_license = auto_accept_license
super().setup()
# first step, check if installed
if 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))
else:
self.confirm_path(self.arg_install_path)
def reinstall(self):
logger.debug("Mark previous installation path for cleaning.")
self._paths_to_clean.add(self.install_path) # remove previous installation path
self.confirm_path(self.arg_install_path)
remove_framework_envs_from_user(self.name)
def remove(self):
"""Remove current framework if installed
Not that we only remove desktop file, launcher icon and dir content, we do not remove
packages as they might be in used for other framework"""
# check if it's installed and so on.
super().remove()
UI.display(DisplayMessage("Removing {}".format(self.name)))
if self.desktop_filename:
with suppress(FileNotFoundError):
os.remove(get_launcher_path(self.desktop_filename))
os.remove(os.path.join(self.default_binary_link_path, self.exec_link_name))
if self.icon_filename:
with suppress(FileNotFoundError):
os.remove(get_icon_path(self.icon_filename))
with suppress(FileNotFoundError):
shutil.rmtree(self.install_path)
remove_framework_envs_from_user(self.name)
self.remove_from_config()
UI.delayed_display(DisplayMessage("Suppression done"))
UI.return_main_screen()
def confirm_path(self, path_dir=""):
"""Confirm path dir"""
if not path_dir:
logger.debug("No installation path provided. Requesting one.")
UI.display(InputText("Choose installation path:", self.confirm_path, self.install_path))
return
logger.debug("Installation path provided. Checking if exists.")
with suppress(FileNotFoundError):
if os.listdir(path_dir):
# we already told we were ok to overwrite as it was the previous install path
if path_dir not in self._paths_to_clean:
if path_dir == "/":
logger.error("This doesn't seem wise. We won't let you shoot in your feet.")
self.confirm_path()
return
self.install_path = path_dir # we don't set it before to not repropose / as installation path
UI.display(YesNo("{} isn't an empty directory, do you want to remove its content and install "
"there?".format(path_dir), self.set_installdir_to_clean, UI.return_main_screen))
return
self.install_path = path_dir
if self.desktop_filename:
self.exec_path = os.path.join(self.install_path, self.required_files_path[0])
self.download_provider_page()
def set_installdir_to_clean(self):
logger.debug("Mark non empty new installation path for cleaning.")
self._paths_to_clean.add(self.install_path)
self.download_provider_page()
def download_provider_page(self):
logger.debug("Download application provider page")
DownloadCenter([DownloadItem(self.download_page)], self.get_metadata_and_check_license, download=False)
def parse_license(self, line, license_txt, in_license):
"""Parse license per line, eventually write to license_txt if it's in the license part.
A flag in_license that is returned by the same function helps to decide if we are in the license part"""
pass
def parse_download_link(self, line, in_download):
"""Parse download_link per line. in_download is a helper that the function return to know if it's in the
download part.
return a tuple of (None, in_download=True/False) if no parsable is found or
((url, md5sum), in_download=True/False)"""
pass
@MainLoop.in_mainloop_thread
def get_metadata_and_check_license(self, result):
"""Download files to download + license and check it"""
logger.debug("Parse download metadata")
error_msg = result[self.download_page].error
if error_msg:
logger.error("An error occurred while downloading {}: {}".format(self.download_page, error_msg))
UI.return_main_screen(status_code=1)
url, checksum = (None, None)
with StringIO() as license_txt:
in_license = False
in_download = False
for line in result[self.download_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 url is None or (self.checksum_type and not checksum) or self.match_last_link:
(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))
if url is None:
logger.error("Download page changed its syntax or is not parsable (url missing)")
UI.return_main_screen(status_code=1)
if (self.checksum_type and checksum is None):
logger.error("Download page changed its syntax or is not parsable (checksum missing)")
logger.error("URL is: {}".format(url))
UI.return_main_screen(status_code=1)
self.download_requests.append(DownloadItem(url, Checksum(self.checksum_type, checksum)))
if license_txt.getvalue() != "":
logger.debug("Check license agreement.")
UI.display(LicenseAgreement(strip_tags(license_txt.getvalue()).strip(),
self.start_download_and_install,
UI.return_main_screen))
elif self.expect_license and not self.auto_accept_license:
logger.error("We were expecting to find a license on the download page, we didn't.")
UI.return_main_screen(status_code=1)
else:
self.start_download_and_install()
def start_download_and_install(self):
self.last_progress_download = None
self.last_progress_requirement = None
self.balance_requirement_download = None
self.pkg_size_download = 0
self.result_requirement = None
self.result_download = None
self._download_done_callback_called = False
UI.display(DisplayMessage("Downloading and installing requirements"))
self.pbar = ProgressBar().start()
self.pkg_to_install = RequirementsHandler().install_bucket(self.packages_requirements,
self.get_progress_requirement,
self.requirement_done)
DownloadCenter(urls=self.download_requests, on_done=self.download_done, report=self.get_progress_download)
@MainLoop.in_mainloop_thread
def get_progress(self, progress_download, progress_requirement):
"""Global progress info. Don't use named parameters as idle_add doesn't like it"""
if progress_download is not None:
self.last_progress_download = progress_download
if progress_requirement is not None:
self.last_progress_requirement = progress_requirement
# we wait to have the file size to start getting progress proportion info
# try to compute balance requirement
if self.balance_requirement_download is None:
if not self.pkg_to_install:
self.balance_requirement_download = 0
self.last_progress_requirement = 0
if self.last_progress_download is None:
return
else:
# we only update if we got a progress from both sides
if self.last_progress_download is None or self.last_progress_requirement is None:
return
else:
# apply a minimum of 15% (no download or small download + install time)
self.balance_requirement_download = max(self.pkg_size_download /
(self.pkg_size_download + self.total_download_size),
0.15)
if not self.pbar.finished: # drawing is delayed, so ensure we are not done first
progress = self._calculate_progress()
self.pbar.update(progress)
def _calculate_progress(self):
progress = self.balance_requirement_download * self.last_progress_requirement +\
(1 - self.balance_requirement_download) * self.last_progress_download
normalized_progress = max(0, progress)
normalized_progress = min(normalized_progress, 100)
return normalized_progress
def get_progress_requirement(self, status):
"""Chain up to main get_progress, returning current value between 0 and 100"""
percentage = status["percentage"]
# 60% is download, 40% is installing
if status["step"] == RequirementsHandler.STATUS_DOWNLOADING:
self.pkg_size_download = status["pkg_size_download"]
progress = 0.6 * percentage
else:
if self.pkg_size_download == 0:
progress = percentage # no download, only install
else:
progress = 60 + 0.4 * percentage
self.get_progress(None, progress)
def get_progress_download(self, downloads):
"""Chain up to main get_progress, returning current value between 0 and 100
First call initialize the balance between requirements and download progress"""
# don't push any progress until we have the total download size
if len(downloads) != len(self.download_requests):
return
total_size = 0
total_current_size = 0
for download in downloads:
total_size += downloads[download]["size"]
total_current_size += downloads[download]["current"]
self.total_download_size = total_size
self.get_progress(total_current_size / total_size * 100, None)
def requirement_done(self, result):
# set requirement download as finished if no error
if not result.error:
self.get_progress(None, 100)
self.result_requirement = result
self.download_and_requirements_done()
def download_done(self, result):
# set download as finished if no error
for url in result:
if result[url].error:
break
else:
self.get_progress(100, None)
self.result_download = result
self.download_and_requirements_done()
@MainLoop.in_mainloop_thread
def download_and_requirements_done(self):
# wait for both side to be done
if self._download_done_callback_called or (not self.result_download or not self.result_requirement):
return
self._download_done_callback_called = True
self.pbar.finish()
# display eventual errors
error_detected = False
if self.result_requirement.error:
logger.error("Package requirements can't be met: {}".format(self.result_requirement.error))
error_detected = True
# check for any errors and collect fds
fds = []
for url in self.result_download:
if self.result_download[url].error:
logger.error(self.result_download[url].error)
error_detected = True
fds.append(self.result_download[url].fd)
if error_detected:
UI.return_main_screen(status_code=1)
# now decompress
self.decompress_and_install(fds)
def decompress_and_install(self, fds):
UI.display(DisplayMessage("Installing {}".format(self.name)))
# empty destination directory if reinstall
for dir_to_remove in self._paths_to_clean:
with suppress(FileNotFoundError):
shutil.rmtree(dir_to_remove)
# marked them as cleaned
self._paths_to_clean = []
os.makedirs(self.install_path, exist_ok=True)
decompress_fds = {}
for fd in fds:
direct_copy = False
for ext in self.DIRECT_COPY_EXT:
if fd.name.endswith(ext):
direct_copy = True
break
if direct_copy:
shutil.copy2(fd.name, os.path.join(self.install_path, os.path.basename(fd.name)))
else:
decompress_fds[fd] = Decompressor.DecompressOrder(dir=self.dir_to_decompress_in_tarball,
dest=self.install_path)
Decompressor(decompress_fds, self.decompress_and_install_done)
UI.display(UnknownProgress(self.iterate_until_install_done))
def post_install(self):
"""Call the post_install process, like creating a launcher, adding env variables…"""
pass
@MainLoop.in_mainloop_thread
def decompress_and_install_done(self, result):
self._install_done = True
error_detected = False
for fd in result:
if result[fd].error:
logger.error(result[fd].error)
error_detected = True
fd.close()
if error_detected:
UI.return_main_screen(status_code=1)
self.post_install()
if self.exec_link_name:
add_exec_link(self.exec_path, self.exec_link_name)
# Mark as installation done in configuration
self.mark_in_config()
UI.delayed_display(DisplayMessage("Installation done"))
UI.return_main_screen()
def iterate_until_install_done(self):
while not self._install_done:
yield
ubuntu-make-16.11.1ubuntu1/umake/frameworks/kotlin.py 0000664 0000000 0000000 00000005707 13013560574 017503 0 ustar # -*- coding: utf-8 -*-
# Copyright (C) 2016 Canonical
#
# Authors:
# Omer Sheikh
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Kotlin module"""
from gettext import gettext as _
import logging
import os
import json
import umake.frameworks.baseinstaller
from umake.interactions import DisplayMessage
from umake.tools import add_env_to_user, MainLoop
from umake.ui import UI
from umake.network.download_center import DownloadItem
logger = logging.getLogger(__name__)
class KotlinCategory(umake.frameworks.BaseCategory):
def __init__(self):
super().__init__(name="Kotlin", description=_("The Kotlin Programming Language"), logo_path=None)
class KotlinLang(umake.frameworks.baseinstaller.BaseInstaller):
def __init__(self, category):
super().__init__(name="Kotlin Lang", description=_("Kotlin language standalone compiler"),
is_category_default=True, category=category,
packages_requirements=["openjdk-7-jre | openjdk-8-jre"],
download_page="https://api.github.com/repos/Jetbrains/kotlin/releases/latest",
dir_to_decompress_in_tarball="kotlinc",
required_files_path=[os.path.join("bin", "kotlinc")])
@MainLoop.in_mainloop_thread
def get_metadata_and_check_license(self, result):
logger.debug("Fetched download page, parsing.")
page = result[self.download_page]
error_msg = page.error
if error_msg:
logger.error("An error occurred while downloading {}: {}".format(self.download_page, error_msg))
UI.return_main_screen(status_code=1)
try:
assets = json.loads(page.buffer.read().decode())["assets"]
download_url = assets[0]["browser_download_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: " + download_url)
self.download_requests.append(DownloadItem(download_url, None))
self.start_download_and_install()
def post_install(self):
"""Add the Kotlin binary dir to PATH"""
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)))
ubuntu-make-16.11.1ubuntu1/umake/frameworks/rust.py 0000664 0000000 0000000 00000013211 13013560574 017165 0 ustar # -*- coding: utf-8 -*-
# Copyright (C) 2015 Canonical
#
# Authors:
# Tin Tvrtković
# Jared Ravetch
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Rust module"""
from contextlib import suppress
from gettext import gettext as _
from glob import glob
import logging
import os
import re
from bs4 import BeautifulSoup
import umake.frameworks.baseinstaller
from umake.interactions import DisplayMessage
from umake.network.download_center import DownloadItem, DownloadCenter
from umake.tools import get_current_arch, add_env_to_user, ChecksumType, \
MainLoop, Checksum
from umake.ui import UI
logger = logging.getLogger(__name__)
class RustCategory(umake.frameworks.BaseCategory):
def __init__(self):
super().__init__(name="Rust", description=_("Rust language"),
logo_path=None)
class RustLang(umake.frameworks.baseinstaller.BaseInstaller):
# Button labels on the download page.
arch_trans = {
"amd64": "64-bit",
"i386": "32-bit"
}
def __init__(self, category):
super().__init__(name="Rust Lang",
description=_("The official Rust distribution"),
is_category_default=True,
category=category, only_on_archs=['i386', 'amd64'],
download_page="https://www.rust-lang.org/en-US/downloads.html",
checksum_type=ChecksumType.sha256,
dir_to_decompress_in_tarball="rust-*")
def parse_download_link(self, line, in_download):
"""Parse Rust download link, expect to find a url"""
url, sha1 = (None, None)
arch = get_current_arch()
if "{}-unknown-linux-gnu.tar.gz".format(self.arch_trans[arch]) in line:
in_download = True
if in_download:
p = re.search(r'href="(.*)">', line)
with suppress(AttributeError):
url = p.group(1)
p = re.search(r'
(\w+) | ', line)
with suppress(AttributeError):
sha1 = p.group(1)
if "" in line:
in_download = False
if url is None and sha1 is None:
return (None, in_download)
return ((url, sha1), in_download)
@MainLoop.in_mainloop_thread
def get_metadata_and_check_license(self, result):
"""Override this so we can use BS and fetch the checksum separately."""
logger.debug("Fetched download page, parsing.")
page = result[self.download_page]
error_msg = page.error
if error_msg:
logger.error("An error occurred while downloading {}: {}".format(self.download_page_url, error_msg))
UI.return_main_screen(status_code=1)
soup = BeautifulSoup(page.buffer, 'html.parser')
link = (soup.find('div', class_="install")
.find('td', class_="inst-type", text="Linux (.tar.gz)")
.parent
.find(text=self.arch_trans[get_current_arch()])
.parent
.parent)
if link is None:
logger.error("Can't parse the download URL from the download page.")
UI.return_main_screen(status_code=1)
download_url = link.attrs['href']
checksum_url = download_url + '.sha256'
logger.debug("Found download URL: " + download_url)
logger.debug("Downloading checksum first, from " + checksum_url)
def checksum_downloaded(results):
checksum_result = next(iter(results.values())) # Just get the first.
if checksum_result.error:
logger.error(checksum_result.error)
UI.return_main_screen(status_code=1)
checksum = checksum_result.buffer.getvalue().decode('utf-8').split()[0]
logger.info('Obtained SHA256 checksum: ' + checksum)
self.download_requests.append(DownloadItem(download_url,
checksum=Checksum(ChecksumType.sha256, checksum),
ignore_encoding=True))
self.start_download_and_install()
DownloadCenter([DownloadItem(checksum_url)], on_done=checksum_downloaded, download=False)
def post_install(self):
"""Add rust necessary env variables"""
add_env_to_user(self.name, {"PATH": {"value": "{}:{}".format(os.path.join(self.install_path, "rustc", "bin"),
os.path.join(self.install_path, "cargo", "bin"))},
"LD_LIBRARY_PATH": {"value": os.path.join(self.install_path, "rustc", "lib")}})
# adjust for rust 1.5 some symlinks magic to have stdlib craft available
os.chdir(os.path.join(self.install_path, "rustc", "lib"))
os.rename("rustlib", "rustlib.init")
os.symlink(glob(os.path.join('..', '..', 'rust-std-*', 'lib', 'rustlib'))[0], 'rustlib')
os.symlink(os.path.join('..', 'rustlib.init', 'etc'), os.path.join('rustlib', 'etc'))
UI.delayed_display(DisplayMessage(self.RELOGIN_REQUIRE_MSG.format(self.name)))
ubuntu-make-16.11.1ubuntu1/umake/frameworks/go.py 0000664 0000000 0000000 00000005500 13013560574 016577 0 ustar # -*- coding: utf-8 -*-
# Copyright (C) 2014 Canonical
#
# Authors:
# Didier Roche
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Go module"""
from contextlib import suppress
from gettext import gettext as _
import logging
import os
import re
import umake.frameworks.baseinstaller
from umake.interactions import DisplayMessage
from umake.tools import get_current_arch, add_env_to_user, ChecksumType
from umake.ui import UI
logger = logging.getLogger(__name__)
class GoCategory(umake.frameworks.BaseCategory):
def __init__(self):
super().__init__(name="Go", description=_("Go language"),
logo_path=None)
class GoLang(umake.frameworks.baseinstaller.BaseInstaller):
def __init__(self, category):
super().__init__(name="Go Lang", description=_("Google compiler (default)"), is_category_default=True,
category=category, only_on_archs=['i386', 'amd64'],
download_page="https://golang.org/dl/",
checksum_type=ChecksumType.sha256,
dir_to_decompress_in_tarball="go",
required_files_path=[os.path.join("bin", "go")])
def parse_download_link(self, line, in_download):
"""Parse Go download link, expect to find a sha and a url"""
url, sha = (None, None)
if "linux-{}".format(get_current_arch().replace("i386", "386")) in line:
in_download = True
if in_download:
p = re.search(r'href="(.*)">', line)
with suppress(AttributeError):
url = p.group(1)
p = re.search(r'(\w+) | ', line)
with suppress(AttributeError):
sha = p.group(1)
if "" in line:
in_download = False
if url is None and sha is None:
return (None, in_download)
return ((url, sha), in_download)
def post_install(self):
"""Add go necessary env variables"""
add_env_to_user(self.name, {"PATH": {"value": os.path.join(self.install_path, "bin")},
"GOROOT": {"value": self.install_path, "keep": False}})
UI.delayed_display(DisplayMessage(self.RELOGIN_REQUIRE_MSG.format(self.name)))
ubuntu-make-16.11.1ubuntu1/umake/frameworks/android.py 0000664 0000000 0000000 00000020432 13013560574 017613 0 ustar # -*- coding: utf-8 -*-
# Copyright (C) 2014 Canonical
#
# Authors:
# Didier Roche
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Android module"""
from contextlib import suppress
from gettext import gettext as _
import logging
import os
import platform
import re
import umake.frameworks.baseinstaller
from umake.interactions import DisplayMessage
from umake.ui import UI
from umake.tools import add_env_to_user, create_launcher, get_application_desktop_file, ChecksumType
logger = logging.getLogger(__name__)
_supported_archs = ['i386', 'amd64']
class AndroidCategory(umake.frameworks.BaseCategory):
def __init__(self):
super().__init__(name="Android", description=_("Android Development Environment"), logo_path=None)
def parse_license(self, tag, line, license_txt, in_license):
"""Parse Android download page for license"""
if line.startswith(tag):
in_license = True
if in_license:
if line.startswith(''):
in_license = False
else:
license_txt.write(line)
return in_license
def parse_download_link(self, tag, line, in_download):
"""Parse Android download links, expect to find a sha1sum and a url"""
url, sha1sum = (None, None)
if tag in line:
in_download = True
if in_download:
p = re.search(r'href="(.*)"', line)
with suppress(AttributeError):
url = p.group(1)
p = re.search(r'(\w+) | ', line)
with suppress(AttributeError):
# ensure the size can match a md5 or sha1 checksum
if len(p.group(1)) > 15:
sha1sum = p.group(1)
if "" in line:
in_download = False
if url is None and sha1sum is None:
return (None, in_download)
if url and url.startswith("//"):
url = "https:" + url
return ((url, sha1sum), in_download)
class AndroidStudio(umake.frameworks.baseinstaller.BaseInstaller):
def __init__(self, category):
super().__init__(name="Android Studio", description=_("Android Studio (default)"), is_category_default=True,
category=category, only_on_archs=_supported_archs, expect_license=True,
packages_requirements=["openjdk-7-jdk | openjdk-8-jdk",
"libncurses5:i386", "libstdc++6:i386", "zlib1g:i386"],
download_page="https://developer.android.com/sdk/index.html",
checksum_type=ChecksumType.sha1,
dir_to_decompress_in_tarball="android-studio",
desktop_filename="android-studio.desktop",
required_files_path=[os.path.join("bin", "studio.sh")])
def parse_license(self, line, license_txt, in_license):
"""Parse Android Studio download page for license"""
return self.category.parse_license('Linux ', line, in_download)
def post_install(self):
"""Add necessary environment variables"""
add_env_to_user(self.name, {"NDK_ROOT": {"value": self.install_path, "keep": False}})
# print wiki page message
UI.display(DisplayMessage("NDK installed in {}. More information on how to use it on {}".format(
self.install_path,
"https://developer.android.com/tools/sdk/ndk/index.html#GetStarted")))
class EclipseADTForRemoval(umake.frameworks.baseinstaller.BaseInstaller):
def __init__(self, category):
super().__init__(name="Eclipse ADT", description="For removal only (not supported upstream anymore)",
download_page=None, category=category, only_on_archs=_supported_archs, only_for_removal=True)
ubuntu-make-16.11.1ubuntu1/umake/frameworks/nodejs.py 0000664 0000000 0000000 00000007646 13013560574 017471 0 ustar # -*- coding: utf-8 -*-
# Copyright (C) 2014 Canonical
#
# Authors:
# Didier Roche
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Nodejs module"""
from contextlib import suppress
from gettext import gettext as _
import logging
import os
import subprocess
import umake.frameworks.baseinstaller
from umake.interactions import DisplayMessage
from umake.tools import get_current_arch, add_env_to_user, ChecksumType
from umake.ui import UI
logger = logging.getLogger(__name__)
class NodejsCategory(umake.frameworks.BaseCategory):
def __init__(self):
super().__init__(name="Nodejs", description=_("Nodejs stable"),
logo_path=None)
class NodejsLang(umake.frameworks.baseinstaller.BaseInstaller):
def __init__(self, category):
super().__init__(name="Nodejs Lang", description=_("Nodejs stable"), is_category_default=True,
category=category, only_on_archs=['i386', 'amd64'],
download_page="https://nodejs.org/dist/latest/SHASUMS256.txt",
checksum_type=ChecksumType.sha256,
dir_to_decompress_in_tarball="node*",
required_files_path=[os.path.join("bin", "node")])
arch_trans = {
"amd64": "x64",
"i386": "x86"
}
def parse_download_link(self, line, in_download):
"""Parse Nodejs download link, expect to find a sha1 and a url"""
url, shasum = (None, None)
arch = get_current_arch()
if "linux-{}.tar.xz".format(self.arch_trans[arch]) in line:
in_download = True
if in_download:
url = self.download_page.strip("SHASUMS256.txt") + line.split(' ')[2].rstrip()
shasum = line.split(' ')[0]
if url is None and shasum is None:
return (None, in_download)
return ((url, shasum), in_download)
def prefix_set(self):
with suppress(IOError):
with open('{}/.npmrc'.format(os.path.expanduser('~')), 'r') as file:
for line in file.readlines():
if line.startswith("prefix ="):
return True
return False
def post_install(self):
"""Add nodejs necessary env variables and move module folder"""
if not self.prefix_set():
with open('{}/.npmrc'.format(os.path.expanduser('~')), 'a+') as file:
file.write("prefix = ${HOME}/.npm_modules")
add_env_to_user(self.name, {"PATH": {"value": "{}:{}".format(os.path.join(self.install_path, "bin"),
os.path.join(os.path.expanduser('~'),
".npm_modules", "bin"))}})
UI.delayed_display(DisplayMessage(self.RELOGIN_REQUIRE_MSG.format(self.name)))
def install_framework_parser(self, parser):
this_framework_parser = super().install_framework_parser(parser)
this_framework_parser.add_argument('--lts', action="store_true",
help=_("Install lts version"))
return this_framework_parser
def run_for(self, args):
if args.lts:
self.download_page = "https://nodejs.org/dist/latest-argon/SHASUMS256.txt"
print('Download from {}'.format(self.download_page))
super().run_for(args)
ubuntu-make-16.11.1ubuntu1/umake/frameworks/ide.py 0000664 0000000 0000000 00000124300 13013560574 016733 0 ustar # -*- coding: utf-8 -*-
# Copyright (C) 2014-2015 Canonical
#
# Authors:
# Didier Roche
# Tin Tvrtković
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Generic IDE module."""
from abc import ABCMeta, abstractmethod
from bs4 import BeautifulSoup
from concurrent import futures
from contextlib import suppress
from gettext import gettext as _
import grp
from io import StringIO
import json
import logging
import os
from os.path import join
import pwd
import platform
import re
import subprocess
from urllib import parse
import shutil
import umake.frameworks.baseinstaller
from umake.interactions import DisplayMessage, LicenseAgreement
from umake.network.download_center import DownloadCenter, DownloadItem
from umake.tools import as_root, create_launcher, get_application_desktop_file, ChecksumType, Checksum, MainLoop,\
strip_tags, add_env_to_user, add_exec_link, get_current_arch
from umake.ui import UI
logger = logging.getLogger(__name__)
def _add_to_group(user, group):
"""Add user to group"""
# switch to root
with as_root():
try:
output = subprocess.check_output(["adduser", user, group])
logger.debug("Added {} to {}: {}".format(user, group, output))
return True
except subprocess.CalledProcessError as e:
logger.error("Couldn't add {} to {}".format(user, group))
return False
class IdeCategory(umake.frameworks.BaseCategory):
def __init__(self):
super().__init__(name="IDE", description=_("Generic IDEs"),
logo_path=None)
class BaseEclipse(umake.frameworks.baseinstaller.BaseInstaller, metaclass=ABCMeta):
"""The Eclipse Foundation distribution."""
def __init__(self, *args, **kwargs):
if self.executable:
current_required_files_path = kwargs.get("required_files_path", [])
current_required_files_path.append(os.path.join(self.executable))
kwargs["required_files_path"] = current_required_files_path
download_page = 'https://www.eclipse.org/downloads/eclipse-packages/'
kwargs["download_page"] = download_page
super().__init__(*args, **kwargs)
self.icon_url = os.path.join("https://www.eclipse.org/downloads/", "images", self.icon_filename)
self.bits = '' if platform.machine() == 'i686' else 'x86_64'
self.headers = {'User-agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu "
"Chromium/41.0.2272.76 Chrome/41.0.2272.76 Safari/537.36"}
@property
@abstractmethod
def download_keyword(self):
pass
@property
@abstractmethod
def executable(self):
pass
def download_provider_page(self):
logger.debug("Download application provider page")
DownloadCenter([DownloadItem(self.download_page, headers=self.headers)], self.get_metadata, download=False)
def parse_download_link(self, line, in_download):
"""Parse Eclipse download links"""
url_found = False
if self.download_keyword in line and self.bits in line:
in_download = True
else:
in_download = False
if in_download:
p = re.search(r'href="(.*)" title', line)
with suppress(AttributeError):
self.sha512_url = "https://www.eclipse.org/" + p.group(1) + '.sha512&r=1'
url_found = True
DownloadCenter(urls=[DownloadItem(self.sha512_url, None)],
on_done=self.get_sha_and_start_download, download=False)
return (url_found, in_download)
@MainLoop.in_mainloop_thread
def get_metadata(self, result):
"""Download files to download + license and check it"""
logger.debug("Parse download metadata")
error_msg = result[self.download_page].error
if error_msg:
logger.error("An error occurred while downloading {}: {}".format(self.download_page, error_msg))
UI.return_main_screen(status_code=1)
in_download = False
url_found = False
for line in result[self.download_page].buffer:
line_content = line.decode()
(_url_found, in_download) = self.parse_download_link(line_content, in_download)
if not url_found:
url_found = _url_found
if not url_found:
logger.error("Download page changed its syntax or is not parsable")
UI.return_main_screen(status_code=1)
@MainLoop.in_mainloop_thread
def get_sha_and_start_download(self, download_result):
res = download_result[self.sha512_url]
sha512 = res.buffer.getvalue().decode('utf-8').split()[0]
# you get and store self.download_url
url = re.sub('.sha512', '', self.sha512_url)
if url is None:
logger.error("Download page changed its syntax or is not parsable (missing url)")
UI.return_main_screen(status_code=1)
if sha512 is None:
logger.error("Download page changed its syntax or is not parsable (missing sha512)")
UI.return_main_screen(status_code=1)
logger.debug("Found download link for {}, checksum: {}".format(url, sha512))
self.download_requests.append(DownloadItem(url, Checksum(ChecksumType.sha512, sha512)))
self.start_download_and_install()
def post_install(self):
"""Create the Eclipse launcher"""
DownloadCenter(urls=[DownloadItem(self.icon_url, None)],
on_done=self.save_icon, download=True)
icon_path = join(self.install_path, self.icon_filename)
comment = self.description
categories = "Development;IDE;"
create_launcher(self.desktop_filename,
get_application_desktop_file(name=self.name,
icon_path=icon_path,
exec='"{}" %f'.format(self.exec_path),
comment=comment,
categories=categories))
def save_icon(self, download_result):
"""Save correct Eclipse icon"""
icon = download_result.pop(self.icon_url).fd.name
shutil.copy(icon, join(self.install_path, self.icon_filename))
logger.debug("Copied icon: {}".format(self.icon_url))
class EclipseJava(BaseEclipse):
"""The Eclipse Java Edition distribution."""
download_keyword = 'eclipse-java-'
executable = 'eclipse'
def __init__(self, category):
super().__init__(name="Eclipse",
description=_("Eclipse Java IDE"),
dir_to_decompress_in_tarball='eclipse',
desktop_filename='eclipse-java.desktop',
category=category, only_on_archs=['i386', 'amd64'],
packages_requirements=['openjdk-7-jdk | openjdk-8-jdk'],
icon_filename='java.png')
class EclipseJEE(BaseEclipse):
"""The Eclipse JEE Edition distribution."""
download_keyword = 'eclipse-jee-'
executable = 'eclipse'
def __init__(self, category):
super().__init__(name="Eclipse JEE",
description=_("Eclipse JEE IDE"),
dir_to_decompress_in_tarball='eclipse',
desktop_filename='eclipse-jee.desktop',
category=category, only_on_archs=['i386', 'amd64'],
packages_requirements=['openjdk-7-jdk | openjdk-8-jdk'],
icon_filename='javaee.png')
class EclipsePHP(BaseEclipse):
"""The Eclipse PHP Edition distribution."""
download_keyword = 'eclipse-php-'
executable = 'eclipse'
def __init__(self, category):
super().__init__(name="Eclipse PHP",
description=_("Eclipse PHP IDE"),
dir_to_decompress_in_tarball='eclipse',
desktop_filename='eclipse-php.desktop',
category=category, only_on_archs=['i386', 'amd64'],
packages_requirements=['openjdk-7-jdk | openjdk-8-jdk'],
icon_filename='php.png')
class EclipseCPP(BaseEclipse):
"""The Eclipse CPP Edition distribution."""
download_keyword = 'eclipse-cpp-'
executable = 'eclipse'
def __init__(self, category):
super().__init__(name="Eclipse CPP",
description=_("Eclipse C/C++ IDE"),
dir_to_decompress_in_tarball='eclipse',
desktop_filename='eclipse-cpp.desktop',
category=category, only_on_archs=['i386', 'amd64'],
packages_requirements=['openjdk-7-jdk | openjdk-8-jdk'],
icon_filename='cdt.png')
class BaseJetBrains(umake.frameworks.baseinstaller.BaseInstaller, metaclass=ABCMeta):
"""The base for all JetBrains installers."""
def __init__(self, *args, **kwargs):
"""Add executable required file path to existing list"""
if self.executable:
current_required_files_path = kwargs.get("required_files_path", [])
current_required_files_path.append(os.path.join("bin", self.executable))
kwargs["required_files_path"] = current_required_files_path
download_page = "https://data.services.jetbrains.com/products/releases?code={}".format(self.download_keyword)
kwargs["download_page"] = download_page
super().__init__(*args, **kwargs)
@property
@abstractmethod
def download_keyword(self):
pass
@property
@abstractmethod
def executable(self):
pass
@MainLoop.in_mainloop_thread
def get_metadata_and_check_license(self, result):
logger.debug("Fetched download page, parsing.")
page = result[self.download_page]
error_msg = page.error
if error_msg:
logger.error("An error occurred while downloading {}: {}".format(self.download_page, error_msg))
UI.return_main_screen(status_code=1)
try:
key, content = json.loads(page.buffer.read().decode()).popitem()
download_url = content[0]['downloads']['linux']['link']
checksum_url = content[0]['downloads']['linux']['checksumLink']
except (IndexError):
if '&type=eap' in self.download_page:
logger.error("No EAP version available.")
else:
logger.error("Can't parse the download URL from the download page.")
UI.return_main_screen(status_code=1)
except (json.JSONDecodeError):
logger.error("Can't parse the download URL from the download page.")
UI.return_main_screen(status_code=1)
logger.debug("Found download URL: " + download_url)
logger.debug("Downloading checksum first, from " + checksum_url)
def checksum_downloaded(results):
checksum_result = next(iter(results.values())) # Just get the first.
if checksum_result.error:
logger.error(checksum_result.error)
UI.return_main_screen(status_code=1)
checksum = checksum_result.buffer.getvalue().decode('utf-8').split()[0]
logger.info('Obtained SHA256 checksum: ' + checksum)
self.download_requests.append(DownloadItem(download_url,
checksum=Checksum(ChecksumType.sha256, checksum),
ignore_encoding=True))
self.start_download_and_install()
DownloadCenter([DownloadItem(checksum_url)], on_done=checksum_downloaded, download=False)
def post_install(self):
"""Create the appropriate JetBrains launcher."""
icon_path = join(self.install_path, 'bin', self.icon_filename)
comment = self.description + " (UDTC)"
categories = "Development;IDE;"
create_launcher(self.desktop_filename,
get_application_desktop_file(name=self.name,
icon_path=icon_path,
exec='"{}" %f'.format(self.exec_path),
comment=comment,
categories=categories))
def install_framework_parser(self, parser):
this_framework_parser = super().install_framework_parser(parser)
this_framework_parser.add_argument('--eap', action="store_true",
help=_("Install EAP version if available"))
return this_framework_parser
def run_for(self, args):
if args.eap:
self.download_page += '&type=eap'
self.name += " EAP"
self.description += " EAP"
self.desktop_filename = self.desktop_filename.replace(".desktop", "-eap.desktop")
self.install_path += "-eap"
super().run_for(args)
class PyCharm(BaseJetBrains):
"""The JetBrains PyCharm Community Edition distribution."""
download_keyword = 'PCC'
executable = "pycharm.sh"
def __init__(self, category):
super().__init__(name="PyCharm",
description=_("PyCharm Community Edition"),
category=category, only_on_archs=['i386', 'amd64'],
packages_requirements=['python', 'python3'],
dir_to_decompress_in_tarball='pycharm-community-*',
desktop_filename='jetbrains-pycharm-ce.desktop',
icon_filename='pycharm.png')
class PyCharmEducational(BaseJetBrains):
"""The JetBrains PyCharm Educational Edition distribution."""
download_keyword = 'PCE'
executable = "pycharm.sh"
def __init__(self, category):
super().__init__(name="PyCharm Educational",
description=_("PyCharm Educational Edition"),
category=category, only_on_archs=['i386', 'amd64'],
packages_requirements=['python', 'python3'],
dir_to_decompress_in_tarball='pycharm-edu*',
desktop_filename='jetbrains-pycharm-edu.desktop',
icon_filename='pycharm.png')
class PyCharmProfessional(BaseJetBrains):
"""The JetBrains PyCharm Professional Edition distribution."""
download_keyword = 'PCP'
executable = "pycharm.sh"
def __init__(self, category):
super().__init__(name="PyCharm Professional",
description=_("PyCharm Professional Edition"),
category=category, only_on_archs=['i386', 'amd64'],
packages_requirements=['python', 'python3'],
dir_to_decompress_in_tarball='pycharm-*',
desktop_filename='jetbrains-pycharm.desktop',
icon_filename='pycharm.png')
class Idea(BaseJetBrains):
"""The JetBrains IntelliJ Idea Community Edition distribution."""
download_keyword = 'IIC'
executable = "idea.sh"
def __init__(self, category):
super().__init__(name="Idea",
description=_("IntelliJ IDEA Community Edition"),
category=category, only_on_archs=['i386', 'amd64'],
packages_requirements=['openjdk-7-jdk | openjdk-8-jdk'],
dir_to_decompress_in_tarball='idea-IC-*',
desktop_filename='jetbrains-idea-ce.desktop',
icon_filename='idea.png')
class IdeaUltimate(BaseJetBrains):
"""The JetBrains IntelliJ Idea Ultimate Edition distribution."""
download_keyword = 'IIU'
executable = "idea.sh"
def __init__(self, category):
super().__init__(name="Idea Ultimate",
description=_("IntelliJ IDEA"),
category=category, only_on_archs=['i386', 'amd64'],
packages_requirements=['openjdk-7-jdk | openjdk-8-jdk'],
dir_to_decompress_in_tarball='idea-IU-*',
desktop_filename='jetbrains-idea.desktop',
icon_filename='idea.png')
class RubyMine(BaseJetBrains):
"""The JetBrains RubyMine IDE"""
download_keyword = 'RM'
executable = "rubymine.sh"
def __init__(self, category):
super().__init__(name="RubyMine",
description=_("Ruby on Rails IDE"),
category=category,
only_on_archs=['i386', 'amd64'],
packages_requirements=['ruby'],
dir_to_decompress_in_tarball='RubyMine-*',
desktop_filename='jetbrains-rubymine.desktop',
icon_filename='rubymine.png')
class WebStorm(BaseJetBrains):
"""The JetBrains WebStorm IDE"""
download_keyword = 'WS'
executable = "webstorm.sh"
def __init__(self, category):
super().__init__(name="WebStorm",
description=_("Complex client-side and server-side javascript IDE"),
category=category,
only_on_archs=['i386', 'amd64'],
dir_to_decompress_in_tarball='WebStorm-*',
desktop_filename='jetbrains-webstorm.desktop',
icon_filename='webstorm.svg')
class PhpStorm(BaseJetBrains):
"""The JetBrains PhpStorm IDE"""
download_keyword = 'PS'
executable = "phpstorm.sh"
def __init__(self, category):
super().__init__(name="PhpStorm",
description=_("PHP and web development IDE"),
category=category,
only_on_archs=['i386', 'amd64'],
dir_to_decompress_in_tarball='PhpStorm-*',
desktop_filename='jetbrains-phpstorm.desktop',
icon_filename='webide.png')
class CLion(BaseJetBrains):
"""The JetBrains CLion IDE"""
download_keyword = 'CL'
executable = "clion.sh"
def __init__(self, category):
super().__init__(name="CLion",
description=_("CLion integrated C/C++ IDE"),
category=category,
only_on_archs=['amd64'],
dir_to_decompress_in_tarball='clion-*',
desktop_filename='jetbrains-clion.desktop',
icon_filename='clion.svg')
class DataGrip(BaseJetBrains):
"""The JetBrains DataGrip IDE"""
download_keyword = 'DG'
executable = "datagrip.sh"
def __init__(self, category):
super().__init__(name="DataGrip",
description=_("DataGrip SQL and databases IDE"),
category=category,
only_on_archs=['i386', 'amd64'],
dir_to_decompress_in_tarball='DataGrip-*',
desktop_filename='jetbrains-datagrip.desktop',
icon_filename='product.png')
class Arduino(umake.frameworks.baseinstaller.BaseInstaller):
"""The Arduino Software distribution."""
ARDUINO_GROUP = "dialout"
def __init__(self, category):
if os.geteuid() != 0:
self._current_user = os.getenv("USER")
self._current_user = pwd.getpwuid(int(os.getenv("SUDO_UID", default=0))).pw_name
for group_name in [g.gr_name for g in grp.getgrall() if self._current_user in g.gr_mem]:
if group_name == self.ARDUINO_GROUP:
self.was_in_arduino_group = True
break
else:
self.was_in_arduino_group = False
super().__init__(name="Arduino",
description=_("The Arduino Software Distribution"),
category=category, only_on_archs=['i386', 'amd64'],
download_page='http://www.arduino.cc/en/Main/Software',
dir_to_decompress_in_tarball='arduino-*',
desktop_filename='arduino.desktop',
packages_requirements=['gcc-avr', 'avr-libc'],
need_root_access=not self.was_in_arduino_group,
required_files_path=["arduino"])
self.scraped_checksum_url = None
self.scraped_download_url = None
# This is needed later in several places.
# The framework covers other cases, in combination with self.only_on_archs
self.bits = '32' if platform.machine() == 'i686' else '64'
@MainLoop.in_mainloop_thread
def get_metadata_and_check_license(self, result):
"""We diverge from the BaseInstaller implementation a little here."""
logger.debug("Parse download metadata")
error_msg = result[self.download_page].error
if error_msg:
logger.error("An error occurred while downloading {}: {}".format(self.download_page, error_msg))
UI.return_main_screen(status_code=1)
soup = BeautifulSoup(result[self.download_page].buffer, 'html.parser')
# We need to avoid matching arduino-nightly-...
download_link_pat = r'arduino-[\d\.\-r]+-linux' + self.bits + '.tar.xz$'
# Trap no match found, then, download/checksum url will be empty and will raise the error
# instead of crashing.
with suppress(TypeError):
self.scraped_download_url = soup.find('a', href=re.compile(download_link_pat))['href']
self.scraped_checksum_url = soup.find('a', text=re.compile('Checksums'))['href']
self.scraped_download_url = 'http:' + self.scraped_download_url
self.scraped_checksum_url = 'http:' + self.scraped_checksum_url
if not self.scraped_download_url:
logger.error("Can't parse the download link from %s.", self.download_page)
UI.return_main_screen(status_code=1)
if not self.scraped_checksum_url:
logger.error("Can't parse the checksum link from %s.", self.download_page)
UI.return_main_screen(status_code=1)
DownloadCenter([DownloadItem(self.scraped_download_url), DownloadItem(self.scraped_checksum_url)],
on_done=self.prepare_to_download_archive, download=False)
@MainLoop.in_mainloop_thread
def prepare_to_download_archive(self, results):
"""Store the md5 for later and fire off the actual download."""
download_page = results[self.scraped_download_url]
checksum_page = results[self.scraped_checksum_url]
if download_page.error:
logger.error("Error fetching download page: %s", download_page.error)
UI.return_main_screen(status_code=1)
if checksum_page.error:
logger.error("Error fetching checksums: %s", checksum_page.error)
UI.return_main_screen(status_code=1)
match = re.search(r'^(\S+)\s+arduino-[\d\.\-r]+-linux' + self.bits + '.tar.xz$',
checksum_page.buffer.getvalue().decode('ascii'),
re.M)
if not match:
logger.error("Can't find a checksum.")
UI.return_main_screen(status_code=1)
checksum = match.group(1)
soup = BeautifulSoup(download_page.buffer.getvalue(), 'html.parser')
btn = soup.find('button', text=re.compile('JUST DOWNLOAD'))
if not btn:
logger.error("Can't parse download button.")
UI.return_main_screen(status_code=1)
base_url = download_page.final_url
cookies = download_page.cookies
final_download_url = parse.urljoin(base_url, btn.parent['href'])
logger.info('Final download url: %s, cookies: %s.', final_download_url, cookies)
self.download_requests = [DownloadItem(final_download_url,
checksum=Checksum(ChecksumType.md5, checksum),
cookies=cookies)]
# add the user to arduino group
if not self.was_in_arduino_group:
with futures.ProcessPoolExecutor(max_workers=1) as executor:
f = executor.submit(_add_to_group, self._current_user, self.ARDUINO_GROUP)
if not f.result():
UI.return_main_screen(status_code=1)
self.start_download_and_install()
def post_install(self):
"""Create the Arduino launcher"""
icon_path = join(self.install_path, 'lib', 'arduino_icon.ico')
comment = _("The Arduino Software IDE")
categories = "Development;IDE;"
create_launcher(self.desktop_filename,
get_application_desktop_file(name=_("Arduino"),
icon_path=icon_path,
exec='"{}" %f'.format(self.exec_path),
comment=comment,
categories=categories))
if not self.was_in_arduino_group:
UI.delayed_display(DisplayMessage(_("You need to logout and login again for your installation to work")))
class BaseNetBeans(umake.frameworks.baseinstaller.BaseInstaller):
"""The base for all Netbeans installers."""
BASE_URL = "http://download.netbeans.org/netbeans"
EXECUTABLE = "nb/netbeans"
def __init__(self, category, flavour=""):
"""The constructor.
@param category The IDE category.
@param flavour The Netbeans flavour (plugins bundled).
"""
# add a separator to the string, like -cpp
if flavour:
flavour = '-' + flavour
self.flavour = flavour
super().__init__(name="Netbeans",
description=_("Netbeans IDE"),
category=category,
only_on_archs=['i386', 'amd64'],
download_page="https://netbeans.org/downloads/zip.html",
dir_to_decompress_in_tarball="netbeans*",
desktop_filename="netbeans{}.desktop".format(flavour),
packages_requirements=['openjdk-7-jdk | openjdk-8-jdk'],
required_files_path=[os.path.join("bin", "netbeans")])
@MainLoop.in_mainloop_thread
def get_metadata_and_check_license(self, result):
"""Get the latest version and trigger the download of the download_page file.
:param result: the file downloaded by DownloadCenter, contains a web page
"""
# Processing the string to obtain metadata (version)
try:
url_version_str = result[self.download_page].buffer.read().decode('utf-8')
except AttributeError:
# The file could not be parsed or there is no network connection
logger.error("The download page changed its syntax or is not parsable")
UI.return_main_screen(status_code=1)
preg = re.compile(".*/images_www/v6/download/.*")
for line in url_version_str.split("\n"):
if preg.match(line):
line = line.replace("var PAGE_ARTIFACTS_LOCATION = \"/images"
"_www/v6/download/", "").replace("/\";", "").replace('/final', '')
self.version = line.strip()
if not self.version:
# Fallback
logger.error("Could not determine latest version")
UI.return_main_screen(status_code=1)
self.version_download_page = "https://netbeans.org/images_www/v6/download/" \
"{}/final/js/files.js".format(self.version)
DownloadCenter([DownloadItem(self.version_download_page)], self.parse_download_page_callback, download=False)
@MainLoop.in_mainloop_thread
def parse_download_page_callback(self, result):
"""Get the download_url and trigger the download and installation of the app.
:param result: the file downloaded by DownloadCenter, contains js functions with download urls
"""
logger.info("Netbeans {}".format(self.version))
# Processing the string to obtain metadata (download url)
try:
url_file = result[self.version_download_page].buffer.read().decode('utf-8')
except AttributeError:
# The file could not be parsed
logger.error("The download page changed its syntax or is not parsable")
UI.return_main_screen(status_code=1)
preg = re.compile('add_file\("zip/netbeans-{}-[0-9]{{12}}{}.zip"'.format(self.version,
self.flavour))
for line in url_file.split("\n"):
if preg.match(line):
# Clean up the string from js (it's a function call)
line = line.replace("add_file(", "").replace(");", "").replace('"', "")
url_string = line
if not url_string:
# The file could not be parsed
logger.error("The download page changed its syntax or is not parsable")
UI.return_main_screen(status_code=1)
string_array = url_string.split(", ")
try:
url_suffix = string_array[0]
sha256 = string_array[2]
except IndexError:
# The file could not be parsed
logger.error("The download page changed its syntax or is not parsable")
UI.return_main_screen(status_code=1)
download_url = "{}/{}/final/{}".format(self.BASE_URL, self.version, url_suffix)
self.download_requests.append(DownloadItem(download_url, Checksum(ChecksumType.sha256, sha256)))
self.start_download_and_install()
def post_install(self):
"""Create the Netbeans launcher"""
create_launcher(self.desktop_filename,
get_application_desktop_file(name=_("Netbeans IDE"),
icon_path=join(self.install_path, "nb", "netbeans.png"),
exec=self.exec_path,
comment=_("Netbeans IDE"),
categories="Development;IDE;"))
class VisualStudioCode(umake.frameworks.baseinstaller.BaseInstaller):
PERM_DOWNLOAD_LINKS = {
"i686": "http://go.microsoft.com/fwlink/?LinkID=620885",
"x86_64": "http://go.microsoft.com/fwlink/?LinkID=620884"
}
def __init__(self, category):
super().__init__(name="Visual Studio Code", description=_("Visual Studio focused on modern web and cloud"),
category=category, only_on_archs=['i386', 'amd64'], expect_license=True,
download_page="https://code.visualstudio.com/License",
desktop_filename="visual-studio-code.desktop",
required_files_path=["bin/code"],
dir_to_decompress_in_tarball="VSCode-linux-*",
packages_requirements=["libgtk2.0-0"])
def parse_license(self, line, license_txt, in_license):
"""Parse Android Studio download page for license"""
if 'SOFTWARE LICENSE TERMS' in line:
in_license = True
if in_license and "
" in line:
in_license = False
if in_license:
license_txt.write(line.strip() + "\n")
return in_license
def parse_download_link(self, line, in_download):
"""We have persistent links for Visual Studio Code, return it right away"""
url = None
with suppress(KeyError):
url = self.PERM_DOWNLOAD_LINKS[platform.machine()]
return ((url, None), in_download)
def post_install(self):
"""Create the Visual Studio Code launcher"""
create_launcher(self.desktop_filename, get_application_desktop_file(name=_("Visual Studio Code"),
icon_path=os.path.join(self.install_path, "resources", "app", "resources", "linux",
"code.png"),
exec=self.exec_path,
comment=_("Visual Studio focused on modern web and cloud"),
categories="Development;IDE;"))
class LightTable(umake.frameworks.baseinstaller.BaseInstaller):
def __init__(self, category):
super().__init__(name="LightTable", description=_("LightTable code editor"),
category=category, only_on_archs=['amd64'],
download_page="https://api.github.com/repos/LightTable/LightTable/releases/latest",
desktop_filename="lighttable.desktop",
required_files_path=["LightTable"],
dir_to_decompress_in_tarball="lighttable-*",
checksum_type=ChecksumType.md5)
@MainLoop.in_mainloop_thread
def get_metadata_and_check_license(self, result):
logger.debug("Fetched download page, parsing.")
page = result[self.download_page]
error_msg = page.error
if error_msg:
logger.error("An error occurred while downloading {}: {}".format(self.download_page, error_msg))
UI.return_main_screen(status_code=1)
try:
assets = json.loads(page.buffer.read().decode())["assets"]
download_url = None
for asset in assets:
if "linux" in asset["browser_download_url"]:
download_url = asset["browser_download_url"]
if not download_url:
raise IndexError
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: " + download_url)
self.download_requests.append(DownloadItem(download_url, None))
self.start_download_and_install()
def post_install(self):
"""Create the LightTable Code launcher"""
create_launcher(self.desktop_filename, get_application_desktop_file(name=_("LightTable"),
icon_path=os.path.join(self.install_path, "resources", "app", "core", "img",
"lticon.png"),
exec=self.exec_path,
comment=_("LightTable code editor"),
categories="Development;IDE;"))
class Atom(umake.frameworks.baseinstaller.BaseInstaller):
def __init__(self, category):
super().__init__(name="Atom", description=_("The hackable text editor"),
category=category, only_on_archs=['amd64'],
download_page="https://api.github.com/repos/Atom/Atom/releases/latest",
desktop_filename="atom.desktop",
required_files_path=["atom"],
dir_to_decompress_in_tarball="atom-*",
checksum_type=ChecksumType.md5)
@MainLoop.in_mainloop_thread
def get_metadata_and_check_license(self, result):
logger.debug("Fetched download page, parsing.")
page = result[self.download_page]
error_msg = page.error
if error_msg:
logger.error("An error occurred while downloading {}: {}".format(self.download_page, error_msg))
UI.return_main_screen(status_code=1)
try:
assets = json.loads(page.buffer.read().decode())["assets"]
download_url = None
for asset in assets:
if "tar.gz" in asset["browser_download_url"]:
download_url = asset["browser_download_url"]
if not download_url:
raise IndexError
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: " + download_url)
self.download_requests.append(DownloadItem(download_url, None))
self.start_download_and_install()
def post_install(self):
"""Create the Atom Code launcher"""
create_launcher(self.desktop_filename, get_application_desktop_file(name=_("Atom"),
icon_path=os.path.join(self.install_path, "atom.png"),
exec=self.exec_path,
comment=_("The hackable text editor"),
categories="Development;IDE;"))
class SublimeText(umake.frameworks.baseinstaller.BaseInstaller):
def __init__(self, category):
super().__init__(name="Sublime Text", description=_("Sophisticated text editor for code, markup and prose"),
category=category, only_on_archs=['i386', 'amd64'],
download_page="https://sublimetext.com/3",
desktop_filename="sublime-text.desktop",
required_files_path=["sublime_text"],
dir_to_decompress_in_tarball="sublime_text_*")
arch_trans = {
"amd64": "x64",
"i386": "x32"
}
def parse_download_link(self, line, in_download):
"""Parse SublimeText download links"""
url = None
if '{}.tar.bz2'.format(self.arch_trans[get_current_arch()]) in line:
p = re.search(r'also available as a