зеркало из https://github.com/mozilla/ff-tool.git
Initial commit.
This commit is contained in:
Родитель
7baebeec7d
Коммит
e59354f13b
|
@ -1,2 +1,4 @@
|
|||
# ff-tool
|
||||
Python CLI tool for downloading desktop Firefox version, managing profiles and test prefs
|
||||
|
||||
# Work in progress... DO NOT USE
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
"""
|
||||
Module to download OS-specific versions of Firefox:
|
||||
1. General Release (gr)
|
||||
2. Beta (beta)
|
||||
3. Developer Edition (aurora)
|
||||
4. Nightly (nightly)
|
||||
"""
|
||||
|
||||
from firefox_env_handler import IniHandler
|
||||
from mozdownload import FactoryScraper
|
||||
|
||||
|
||||
CONFIG_CHANNELS = 'configs/channels.ini'
|
||||
|
||||
try:
|
||||
import configparser # Python 3
|
||||
except:
|
||||
import ConfigParser as configparser # Python 2
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read(CONFIG_CHANNELS)
|
||||
env = IniHandler()
|
||||
env.load_os_config('configs')
|
||||
|
||||
|
||||
def set_channel(channel):
|
||||
ch_version = config.get(channel, 'version')
|
||||
ch_type = config.get(channel, 'type')
|
||||
ch_branch = config.get(channel, 'branch')
|
||||
return ch_version, ch_type, ch_branch
|
||||
|
||||
|
||||
def download(channel):
|
||||
print('USING CHANNEL: {0}'.format(channel))
|
||||
v, t, b = set_channel(channel)
|
||||
download_filename = env.get(channel, 'DOWNLOAD_FILENAME')
|
||||
scraper = FactoryScraper(
|
||||
t,
|
||||
version=v,
|
||||
branch=b,
|
||||
destination='_temp/{0}'.format(download_filename)
|
||||
)
|
||||
scraper.download()
|
||||
|
||||
|
||||
def main():
|
||||
for channel in config.sections():
|
||||
download(channel)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,174 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import configparser
|
||||
|
||||
|
||||
class FirefoxEnvHandler():
|
||||
LINUX = 'linux'
|
||||
MAC = 'darwin'
|
||||
WINDOWS = 'cygwin'
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_os():
|
||||
"""
|
||||
Do our best to determine the user's current operating system.
|
||||
"""
|
||||
system = platform.system().lower()
|
||||
return re.split('[-_]', system, maxsplit=1).pop(0)
|
||||
|
||||
@classmethod
|
||||
def is_linux(cls):
|
||||
"""
|
||||
Am I running on Linux?
|
||||
"""
|
||||
return cls.get_os() == cls.LINUX
|
||||
|
||||
@classmethod
|
||||
def is_mac(cls):
|
||||
"""
|
||||
Am I running on Mac/Darwin?
|
||||
"""
|
||||
return cls.get_os() == cls.MAC
|
||||
|
||||
@classmethod
|
||||
def is_other(cls):
|
||||
"""
|
||||
I 'literally' have no idea who you are.
|
||||
"""
|
||||
return not (cls.is_linux() or cls.is_mac() or cls.is_windows())
|
||||
|
||||
@classmethod
|
||||
def is_windows(cls):
|
||||
"""
|
||||
Am I running on Windows/Cygwin?
|
||||
"""
|
||||
return cls.get_os() == cls.WINDOWS
|
||||
|
||||
@staticmethod
|
||||
def banner(str, length=79, delimiter='='):
|
||||
"""
|
||||
Throws up a debug header to make output subjectively "easier" to read
|
||||
in stdout.
|
||||
"""
|
||||
if length <= 0:
|
||||
length = len(str)
|
||||
|
||||
divider = delimiter * length
|
||||
output = '\n'.join(['', divider, str, divider])
|
||||
print(output)
|
||||
|
||||
@staticmethod
|
||||
def clean_folder(path, foot_gun=True):
|
||||
"""
|
||||
Recursively delete the specified path.
|
||||
"""
|
||||
if os.path.isdir(path):
|
||||
print(('Deleting {0}'.format(path)))
|
||||
shutil.rmtree(path)
|
||||
|
||||
else:
|
||||
print(('{0} is not a directory'.format(path)))
|
||||
|
||||
|
||||
class IniHandler(FirefoxEnvHandler):
|
||||
def __init__(self, ini_path=None):
|
||||
"""
|
||||
Creates a new config parser object, and optionally loads a config file
|
||||
if `ini_path` was specified.
|
||||
"""
|
||||
self.config = configparser.SafeConfigParser(os.environ)
|
||||
|
||||
if ini_path is not None:
|
||||
self.load_config(ini_path)
|
||||
|
||||
def load_config(self, ini_path):
|
||||
"""
|
||||
Load an INI config based on the specified `ini_path`.
|
||||
"""
|
||||
IniHandler.banner('LOADING {0}'.format(ini_path), 79, '-')
|
||||
|
||||
# Make sure the specified config file exists, fail hard if missing.
|
||||
if not os.path.isfile(ini_path):
|
||||
sys.exit('Config file not found: {0}'.format(ini_path))
|
||||
|
||||
self.config.read(ini_path)
|
||||
|
||||
def load_os_config(self, config_path):
|
||||
"""
|
||||
Load an INI file based on the current operating system. This method
|
||||
will attempt to load a "darwin.ini", "cygwin.ini", or "linux-gnu.ini"
|
||||
file from the specified `config_path` based on the current OS.
|
||||
"""
|
||||
os_config = os.path.join(config_path, IniHandler.get_os() + '.ini')
|
||||
self.load_config(os_config)
|
||||
|
||||
def create_env_file(self, out_file='.env'):
|
||||
"""
|
||||
Generate and save the output environment file so we can source it from
|
||||
something like .bashrc or .bashprofile.
|
||||
"""
|
||||
IniHandler.banner('CREATING ENV FILE ({0})'.format(out_file))
|
||||
|
||||
env_fmt = "export %s=\"%s\""
|
||||
env_vars = []
|
||||
|
||||
# Generic paths to Sikuli and Firefox profile directories.
|
||||
for key in ['PATH_SIKULIX_BIN', 'PATH_FIREFOX_PROFILES']:
|
||||
env_key = self.get_default(key + '_ENV')
|
||||
env_vars.append(env_fmt % (key, env_key))
|
||||
|
||||
# Channel specific Firefox binary paths.
|
||||
for channel in self.sections():
|
||||
export_name = 'PATH_FIREFOX_APP_' + channel.upper()
|
||||
firefox_bin = self.get(channel, 'PATH_FIREFOX_BIN_ENV')
|
||||
env_vars.append(env_fmt % (export_name, firefox_bin))
|
||||
|
||||
output = '\n'.join(env_vars) + '\n'
|
||||
print(output)
|
||||
|
||||
with open(out_file, 'w') as env_file:
|
||||
env_file.write(output)
|
||||
env_file.close()
|
||||
|
||||
def sections(self):
|
||||
"""
|
||||
Shortcut for getting a config's `sections()` array without needing to
|
||||
do the visually horrifying `self.config.config.sections()`.
|
||||
"""
|
||||
return self.config.sections()
|
||||
|
||||
def get(self, section, option):
|
||||
"""
|
||||
Shortcut for calling a config's `get()` method without needing to
|
||||
do the visually horrifying `self.config.config.get()`.
|
||||
"""
|
||||
return self.config.get(section, option)
|
||||
|
||||
def set(self, section, option, value):
|
||||
"""
|
||||
Shortcut for calling a config's `set()` method without needing to
|
||||
do the visually horrifying `self.config.config.set()`.
|
||||
"""
|
||||
return self.config.set(section, option, str(value))
|
||||
|
||||
def get_default(self, option):
|
||||
"""
|
||||
Shortcut to get a value from the "DEFAULTS" section of a ConfigParser
|
||||
INI file. Doesn't save much time, but reads a bit easier.
|
||||
"""
|
||||
return self.get('DEFAULT', option)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
i = IniHandler()
|
||||
i.load_os_config('configs')
|
||||
i.create_env_file()
|
|
@ -0,0 +1,72 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from firefox_env_handler import IniHandler
|
||||
from fabric.api import local
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
class FirefoxInstall(object):
|
||||
def __init__(self, config, archive_dir='temp'):
|
||||
self.CACHE_FILE = 'cache.ini'
|
||||
self.out_dir = archive_dir
|
||||
self.cache_path = os.path.join(self.out_dir, self.CACHE_FILE)
|
||||
self.cache = IniHandler(self.cache_path)
|
||||
|
||||
# Do some basic type checking on the `config` attribute.
|
||||
if isinstance(config, IniHandler):
|
||||
self.config = config
|
||||
|
||||
elif isinstance(config, str):
|
||||
self.config = IniHandler()
|
||||
self.config.load_os_config(config)
|
||||
|
||||
else:
|
||||
sys.exit('FirefoxInstall: Unexpected config data type')
|
||||
|
||||
def install_all(self, force=False):
|
||||
IniHandler.banner('INSTALLING FIREFOXES')
|
||||
for channel in self.config.sections():
|
||||
self.install_channel(channel, force)
|
||||
|
||||
def install_channel(self, channel, force=False):
|
||||
was_cached = self.cache.config.getboolean('cached', channel)
|
||||
filename = self.config.get(channel, 'DOWNLOAD_FILENAME')
|
||||
install_dir = self.config.get(channel, 'PATH_FIREFOX_APP')
|
||||
installer = os.path.join('.', self.out_dir, filename)
|
||||
|
||||
if force or not was_cached:
|
||||
print(('Installing {0}'.format(channel)))
|
||||
|
||||
if IniHandler.is_linux():
|
||||
# TODO: Move to /opt/* and chmod file?
|
||||
# `tar -jxf firefox-beta.tar.gz -C ./beta --strip-components=1`?
|
||||
local('tar -jxf {0} && mv firefox {1}'.format(installer, install_dir))
|
||||
|
||||
elif IniHandler.is_windows():
|
||||
local('{0} -ms'.format(installer))
|
||||
|
||||
if channel == 'beta':
|
||||
# Since Beta and General Release channels install to the same directory,
|
||||
# install Beta first then rename the directory.
|
||||
gr_install_dir = self.config.get('gr', 'PATH_FIREFOX_APP')
|
||||
local('mv "{0}" "{1}"'.format(gr_install_dir, install_dir))
|
||||
|
||||
elif IniHandler.is_mac():
|
||||
# TODO: Mount the DMG to /Volumes and copy to /Applications?
|
||||
print('Do something...')
|
||||
|
||||
else:
|
||||
print(('[{0}] was cached, skipping install.'.format(channel)))
|
||||
|
||||
local('"{0}" --version # {1}'.format(self.config.get(channel, 'PATH_FIREFOX_BIN_ENV'), channel))
|
||||
|
||||
|
||||
def main():
|
||||
ff_install = FirefoxInstall('./configs/')
|
||||
ff_install.install_all(True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,96 @@
|
|||
"""
|
||||
This module uses Fabric API to generate a Firefox Profile by concatenating the
|
||||
following preferences files:
|
||||
- ./_utils/prefs.ini
|
||||
- ./<application>/prefs.ini
|
||||
- ./<application>/<test_type>/prefs.ini
|
||||
|
||||
The profile is then created using the specified name and saved to the ../_temp/
|
||||
directory.
|
||||
"""
|
||||
|
||||
try:
|
||||
import configparser # Python 3
|
||||
except:
|
||||
import ConfigParser as configparser # Python 2
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import configargparse
|
||||
from fabric.api import local # It looks like Fabric may only support Python 2.
|
||||
|
||||
PATH_PROJECT = os.path.abspath('../')
|
||||
PATH_TEMP = os.path.join(PATH_PROJECT, '_temp')
|
||||
FILE_PREFS = 'prefs.ini'
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
|
||||
|
||||
def _parse_args():
|
||||
"""Parses out args for CLI"""
|
||||
parser = configargparse.ArgumentParser(
|
||||
description='CLI tool for creating Firefox profiles via mozprofile CLI')
|
||||
parser.add_argument('-a', '--application',
|
||||
required=True,
|
||||
help='Application to test. Example: "loop-server"')
|
||||
parser.add_argument('-t', '--test-type',
|
||||
required=True,
|
||||
help='Application test type. Example: "stack-check"')
|
||||
parser.add_argument('-e', '--env',
|
||||
help='Test environment. Example: "dev", "stage", ...')
|
||||
parser.add_argument('-p', '--profile',
|
||||
required=True,
|
||||
help='Profile name.')
|
||||
|
||||
args = parser.parse_args()
|
||||
return args, parser
|
||||
|
||||
|
||||
def prefs_paths(application, test_type, env='stage'):
|
||||
path_global = os.path.join(PATH_PROJECT, '_utils', FILE_PREFS)
|
||||
path_app_dir = os.path.join(PATH_PROJECT, application)
|
||||
path_app = os.path.join(path_app_dir, FILE_PREFS)
|
||||
path_app_test_type = os.path.join(path_app_dir, test_type, FILE_PREFS)
|
||||
|
||||
valid_paths = [path_global]
|
||||
|
||||
if os.path.exists(path_app):
|
||||
config.read(path_app)
|
||||
# Make sure the specified INI file has the specified section.
|
||||
if config.has_section(env):
|
||||
valid_paths.append(path_app + ":" + env)
|
||||
|
||||
if os.path.exists(path_app_test_type):
|
||||
config.read(path_app_test_type)
|
||||
if config.has_section(env):
|
||||
valid_paths.append(path_app_test_type + ":" + env)
|
||||
|
||||
return valid_paths
|
||||
|
||||
|
||||
def create_mozprofile(application, test_type, env, profile_dir):
|
||||
full_profile_dir = os.path.join(PATH_TEMP, profile_dir)
|
||||
|
||||
# If temp profile already exists, kill it so it doesn't merge unexpectedly.
|
||||
if os.path.exists(full_profile_dir):
|
||||
print("Deleting existing profile... {0}".format(full_profile_dir))
|
||||
shutil.rmtree(full_profile_dir)
|
||||
|
||||
cmd = [
|
||||
'mozprofile',
|
||||
'--profile={0}'.format(full_profile_dir)
|
||||
]
|
||||
for path in prefs_paths(application, test_type, env):
|
||||
cmd.append("--preferences=" + path)
|
||||
|
||||
local(" ".join(cmd))
|
||||
|
||||
|
||||
def main():
|
||||
args, parser = _parse_args()
|
||||
create_mozprofile(args.application, args.test_type, args.env, args.profile)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from firefox_env_handler import FirefoxEnvHandler, IniHandler
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
class FirefoxProfileHandler(object):
|
||||
|
||||
def __init__(self, config):
|
||||
# Do some basic type checking on the `config` attribute.
|
||||
if isinstance(config, IniHandler):
|
||||
self.config = config
|
||||
|
||||
elif isinstance(config, str):
|
||||
self.config = IniHandler()
|
||||
self.config.load_os_config(config)
|
||||
|
||||
else:
|
||||
sys.exit('FirefoxProfileHandler: Unexpected config data type')
|
||||
|
||||
self.profile_dir = self.config.get_default('PATH_FIREFOX_PROFILES_ENV')
|
||||
|
||||
def switch_prefs(self, profile_name, user_prefs, channel='nightly'):
|
||||
channel_firefox_bin = self.config.get(channel, 'PATH_FIREFOX_BIN_ENV')
|
||||
print(('{0} -CreateProfile {1}'.format(channel_firefox_bin, profile_name)))
|
||||
print('copy prefs.js to <new profile name> dir')
|
||||
|
||||
def delete_all_profiles(self):
|
||||
FirefoxEnvHandler.clean_folder(self.profile_dir)
|
||||
|
||||
|
||||
def main():
|
||||
config_path = './configs/'
|
||||
FirefoxProfileHandler(config_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,45 @@
|
|||
"""firefox test tool helper script"""
|
||||
import glob
|
||||
import configargparse
|
||||
|
||||
|
||||
def _parse_args():
|
||||
"""Parses out args for CLI"""
|
||||
parser = configargparse.ArgumentParser(
|
||||
description='cross-platform CLI tool for installing firefox and managing profiles')
|
||||
parser.add_argument('-i', '--install',
|
||||
help='install firefox version (release, beta, aurora, nightly',
|
||||
default='nightly',
|
||||
type=str)
|
||||
parser.add_argument('-u', '--uninstall',
|
||||
help='install firefox version (release, beta, aurora, nightly, ALL',
|
||||
type=str)
|
||||
parser.add_argument('-p', '--create-profile',
|
||||
help='create new profile (indicate name)',
|
||||
type=str)
|
||||
parser.add_argument('-d', '--delete-profile',
|
||||
help='delete profile (indicate name)',
|
||||
type=str)
|
||||
parser.add_argument('-s', '--set-profile-path',
|
||||
help='-s <path to profile>',
|
||||
default='<put OS-specific default path here??>',
|
||||
type=str)
|
||||
|
||||
args = parser.parse_args()
|
||||
return args, parser
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entrypoint for CLI"""
|
||||
|
||||
args, parser = _parse_args()
|
||||
|
||||
print('INSTALL: {0}'.format(args.install))
|
||||
print('UNINSTALL: {0}'.format(args.uninstall))
|
||||
print('CREATE PROFILE: {0}'.format(args.create_profile))
|
||||
print('DELETE PROFILE: {0}'.format(args.delete_profile))
|
||||
print('SET PROFILE PATH: {0}'.format(args.set_profile_path))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,63 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from firefox_env_handler import IniHandler
|
||||
from fabric.api import local
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
class FirefoxUninstall(object):
|
||||
def __init__(self, config, archive_dir="temp"):
|
||||
self.CACHE_FILE = "cache.ini"
|
||||
self.out_dir = archive_dir
|
||||
self.cache_path = os.path.join(self.out_dir, self.CACHE_FILE)
|
||||
self.cache = IniHandler(self.cache_path)
|
||||
|
||||
# Do some basic type checking on the `config` attribute.
|
||||
if isinstance(config, IniHandler):
|
||||
self.config = config
|
||||
elif isinstance(config, str):
|
||||
self.config = IniHandler()
|
||||
self.config.load_os_config(config)
|
||||
else:
|
||||
sys.exit("FirefoxUninstall: Unexpected config data type")
|
||||
|
||||
def uninstall_all(self, force=False):
|
||||
"""
|
||||
Delete all the Firefox apps (nightly, aurora, beta, general release),
|
||||
and then delete the shared profiles directory.
|
||||
"""
|
||||
IniHandler.banner("UNINSTALLING FIREFOXES")
|
||||
|
||||
for channel in self.config.sections():
|
||||
self.uninstall_channel(channel, force)
|
||||
|
||||
def uninstall_channel(self, channel, force=False):
|
||||
was_cached = self.cache.config.getboolean("cached", channel)
|
||||
|
||||
if force or not was_cached:
|
||||
path_firefox_app = self.config.get(channel, "PATH_FIREFOX_APP")
|
||||
if not os.path.isdir(path_firefox_app):
|
||||
print(('Firefox not found: {0}'.format(path_firefox_app)))
|
||||
return
|
||||
|
||||
# If we're on Windows/Cygwin, use the uninstaller.
|
||||
if self.config.is_windows():
|
||||
local("\"{0}/uninstall/helper.exe\" -ms".format(path_firefox_app))
|
||||
|
||||
# Otherwise just rimraf the Firefox folder.
|
||||
else:
|
||||
IniHandler.clean_folder(path_firefox_app, False)
|
||||
|
||||
else:
|
||||
print(("[%s] was cached, skipping uninstall." % (channel)))
|
||||
|
||||
|
||||
def main():
|
||||
config_path = "./configs/"
|
||||
ff_uninstall = FirefoxUninstall(config_path)
|
||||
ff_uninstall.uninstall_all()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Загрузка…
Ссылка в новой задаче