Skip to content
Snippets Groups Projects
Commit 9f9494dc authored by Eliot Berriot's avatar Eliot Berriot
Browse files

Merge branch '5-binaries' into 'master'

Resolve "Provide precompiled binaries for easier install"

Closes #5

See merge request funkwhale/cli!5
parents df59ca94 2b723217
No related branches found
No related tags found
1 merge request!5Resolve "Provide precompiled binaries for easier install"
Pipeline #4200 passed
dist
build
stages: stages:
- test - test
- build
variables: variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
...@@ -17,3 +18,47 @@ test: ...@@ -17,3 +18,47 @@ test:
- pytest - pytest
tags: tags:
- docker - docker
build-linux:
stage: build
image: python:3.6
before_script:
- pip install .[dev]
script:
- pyinstaller --clean -y cli.spec --distpath .
- echo "Testing the generated CLI works…" && ./funkwhale --help && echo "funkwhale CLI working \o/"
artifacts:
name: "linux_${CI_COMMIT_REF_NAME}"
paths:
- funkwhale
only:
- tags@funkwhale/cli
- master@funkwhale/cli
tags:
- docker
build-windows:
# there is a weird Gitlab / windows interaction
# cf https://github.com/cdrx/docker-pyinstaller/issues/38
# so we cannot use the regular docker executor
stage: build
image: docker:stable
tags:
- docker-build
variables:
# CI_DEBUG_TRACE: "true"
script:
- docker run --rm -v "$(pwd):/src/" cdrx/pyinstaller-windows:python3 "pip install -r requirements-dev.txt && pyinstaller --clean -y cli.spec --distpath ."
- docker run --rm -v "$(pwd):/src/" cdrx/pyinstaller-windows:python3 "echo 'Testing the generated CLI works…' && wine ./funkwhale.exe --help && echo 'funkwhale CLI working \o/'"
artifacts:
name: "linux_${CI_COMMIT_REF_NAME}"
paths:
- funkwhale.exe
only:
- tags@funkwhale/cli
- master@funkwhale/cli
tags:
- docker-build
...@@ -2,12 +2,39 @@ A command line interface to interact with Funkwhale servers. ...@@ -2,12 +2,39 @@ A command line interface to interact with Funkwhale servers.
# Installation # Installation
We provide some prebuilt binaries for Windows and Linux.
On Linux:
```
curl -L "https://dev.funkwhale.audio/funkwhale/cli/-/jobs/artifacts/master/raw/funkwhale?job=build-linux" -o /usr/local/bin/funkwhale
chmod +x /usr/local/bin/funkwhale
```
On Windows:
```
curl -L "https://dev.funkwhale.audio/funkwhale/cli/-/jobs/artifacts/master/raw/funkwhale.exe?job=build-windows" -o funkwhale.exe
```
# Usage
``funkwhale --help``
# Installation (from source)
This cli requires python 3.6 or greater: This cli requires python 3.6 or greater:
git clone https://dev.funkwhale.audio/funkwhale/cli.git git clone https://dev.funkwhale.audio/funkwhale/cli.git
cd cli cd cli
pip install . pip install .
# Usage
``funkwhale --help`` # Build the binary
You can build the binarie for you platform using the following commands:
pip install .[dev]
pyinstaller cli.spec
This will output a binary in `./dist/funkwhale`.
cli.spec 0 → 100644
# -*- mode: python -*-
block_cipher = None
a = Analysis(
["funkwhale_cli/cli.py"],
pathex=["/home/eliotberriot/projects/funkwhale/cli"],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name="funkwhale",
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
runtime_tmpdir=None,
console=True,
)
...@@ -7,6 +7,14 @@ import datetime ...@@ -7,6 +7,14 @@ import datetime
import dotenv import dotenv
import functools import functools
import keyring import keyring
# importing the backends explicitely is required for PyInstaller to work
import keyring.backends.kwallet
import keyring.backends.Windows
import keyring.backends.OS_X
import keyring.backends.SecretService
import keyring.backends.chainer
import logging import logging
import math import math
import urllib.parse import urllib.parse
...@@ -16,11 +24,12 @@ import pathvalidate ...@@ -16,11 +24,12 @@ import pathvalidate
import pathlib import pathlib
import urllib.parse import urllib.parse
import tqdm import tqdm
from . import api
from . import config from funkwhale_cli import api
from . import exceptions from funkwhale_cli import config
from . import logs from funkwhale_cli import exceptions
from . import output from funkwhale_cli import logs
from funkwhale_cli import output
click_log.basic_config(logs.logger) click_log.basic_config(logs.logger)
...@@ -60,10 +69,18 @@ def async_command(f): ...@@ -60,10 +69,18 @@ def async_command(f):
_async_reraise = kwargs.pop("_async_reraise", False) _async_reraise = kwargs.pop("_async_reraise", False)
try: try:
return loop.run_until_complete(f(*args, **kwargs)) return loop.run_until_complete(f(*args, **kwargs))
except (exceptions.FunkwhaleError, aiohttp.client_exceptions.ClientError) as e: except (aiohttp.client_exceptions.ClientError) as e:
if _async_reraise:
raise
message = str(e)
if hasattr(e, 'status') and e.status == 401:
message = "Remote answered with {}, ensure your are logged in first".format(e.status)
raise click.ClickException(message)
except (exceptions.FunkwhaleError) as e:
if _async_reraise: if _async_reraise:
raise raise
raise click.ClickException(str(e)) message = str(e)
raise click.ClickException(message)
else: else:
raise raise
...@@ -118,14 +135,14 @@ class lazy_credential(): ...@@ -118,14 +135,14 @@ class lazy_credential():
return bool(self.value) return bool(self.value)
def set_server(ctx, url, token): def set_server(ctx, url, token, use_auth=True):
ctx.ensure_object(dict) ctx.ensure_object(dict)
ctx.obj["SERVER_URL"] = url ctx.obj["SERVER_URL"] = url
parsed = urllib.parse.urlparse(url) parsed = urllib.parse.urlparse(url)
ctx.obj["SERVER_NETLOC"] = parsed.netloc ctx.obj["SERVER_NETLOC"] = parsed.netloc
ctx.obj["SERVER_PROTOCOL"] = parsed.scheme ctx.obj["SERVER_PROTOCOL"] = parsed.scheme
try: try:
token = token or lazy_credential(url, "_") token = (token or lazy_credential(url, "_")) if use_auth else None
except ValueError as e: except ValueError as e:
raise click.ClickException("Error while retrieving password from keyring: {}. Your password may be incorrect.".format(e.args[0])) raise click.ClickException("Error while retrieving password from keyring: {}. Your password may be incorrect.".format(e.args[0]))
except Exception as e: except Exception as e:
...@@ -154,14 +171,26 @@ def set_server(ctx, url, token): ...@@ -154,14 +171,26 @@ def set_server(ctx, url, token):
default=False, default=False,
help="Disable logging", help="Disable logging",
) )
@click.option(
"--no-login",
envvar="FUNKWHALE_NO_LOGIN",
is_flag=True,
default=False,
help="Disable authentication/keyring",
)
@SERVER_DECORATOR @SERVER_DECORATOR
@TOKEN_DECORATOR @TOKEN_DECORATOR
@click_log.simple_verbosity_option(logs.logger, expose_value=True) @click_log.simple_verbosity_option(logs.logger, expose_value=True)
@click.pass_context @click.pass_context
def cli(ctx, env_file, url, verbosity, token, quiet): def cli(ctx, env_file, url, verbosity, token, quiet, no_login):
# small hack to fix some weird issues with pyinstaller and keyring
# there seems to be a cache issue somewhere
del keyring.backend.get_all_keyring.__wrapped__.always_returns
keyring.core.init_backend()
# /end of hack
ctx.ensure_object(dict) ctx.ensure_object(dict)
logs.logger.disabled = quiet logs.logger.disabled = quiet
set_server(ctx, url, token) set_server(ctx, url, token, use_auth=not no_login)
@cli.command() @cli.command()
......
aiofiles
aiohttp
appdirs
click
click-log
keyring
marshmallow
python-dotenv
semver
tabulate
tqdm
pathvalidate
aioresponses
asynctest
ipdb
pytest
pytest-mock
pytest-env
pyinstaller
...@@ -44,7 +44,7 @@ dev = ...@@ -44,7 +44,7 @@ dev =
pytest pytest
pytest-mock pytest-mock
pytest-env pytest-env
pyinstaller
[options.packages.find] [options.packages.find]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment