blob: 225eca7b7e2bca80a7d6ae06f15f87ac411f227d [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2022 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import json
import subprocess
import tempfile
from pathlib import Path
from shutil import rmtree
from fuchsia_task_lib import *
def run(*command):
try:
return subprocess.check_output(
command,
text=True,
).strip()
except subprocess.CalledProcessError as e:
print(e.stdout)
raise TaskExecutionException(f'Command {command} failed.')
class FuchsiaTaskPublish(FuchsiaTask):
def parse_args(self, parser: ScopedArgumentParser) -> argparse.Namespace:
'''Parses arguments.'''
parser.add_argument(
'--ffx',
type=parser.path_arg(),
help='A path to the ffx tool.',
required=True,
)
parser.add_argument(
'--pm',
type=parser.path_arg(),
help='A path to the pm tool.',
required=True,
)
parser.add_argument(
'--packages',
help='Paths to far files to package.',
nargs='+',
type=parser.path_arg(),
required=True,
)
parser.add_argument(
'--repo_name',
help='Optionally specify the repository name.',
scope=ArgumentScope.GLOBAL,
)
parser.add_argument(
'--target',
help='Optionally specify the target fuchsia device.',
required=False,
scope=ArgumentScope.GLOBAL,
)
# Private arguments.
# Whether to make the repo default.
parser.add_argument(
'--make_repo_default',
action='store_true',
help=argparse.SUPPRESS,
required=False
)
# The effective repo path to publish to.
parser.add_argument(
'--repo_path',
help=argparse.SUPPRESS,
required=False,
)
return parser.parse_args()
def enable_ffx_repository(self, args):
if run(args.ffx, 'config', 'get', 'repository.server.enabled') != 'true':
print('The ffx repository server is not enabled, starting it now...')
run(args.ffx, 'repository', 'server', 'start')
def ensure_target_device(self, args):
args.target = args.target or run(args.ffx, 'target', 'default', 'get')
print(f'Waiting for {args.target} to come online (60s)')
run(args.ffx, '--target', args.target, 'target', 'wait', '-t', '60')
def resolve_repo(self, args):
# Determine the repo name we want to use, in this order:
# 1. User specified argument.
def user_specified_repo_name():
if args.repo_name:
print(f'Using manually specified --repo_name: {args.repo_name}')
# We shouldn't ask to delete a user specified --repo_name.
self.prompt_repo_cleanup = False
return args.repo_name
# 2. ffx default repository.
def ffx_default_repo():
default = run(args.ffx, '-c', 'ffx_repository=true', 'repository', 'default', 'get')
if default:
print(f'Using ffx default repository: {default}')
# We shouldn't ask to delete the ffx default repo.
self.prompt_repo_cleanup = False
return default
# 3. A user prompt.
def prompt_repo():
print('--repo_name was not specified and there is no default ffx repository set.')
repo_name = input('Please specify a repo name to publish to: ')
if args.make_repo_default is None:
args.make_repo_default = input('Would you make this repo your default ffx repo? (y/n): ').lower() == 'y'
# We shouldn't ask to delete a repo that we're going to make default.
self.prompt_repo_cleanup = not args.make_repo_default
return repo_name
args.repo_name = user_specified_repo_name() or ffx_default_repo() or prompt_repo()
# Determine the pm repo path (use the existing one from ffx, or create a new one).
existing_repos = json.loads(run(
args.ffx,
'--machine',
'json',
'repository',
'list',
))
existing_repo = ([
repo
for repo in existing_repos
if repo['name'] == args.repo_name
] or [None])[0]
existing_repo_path = existing_repo and Path(
existing_repo['spec']['path']
)
args.repo_path = existing_repo_path or Path(tempfile.mkdtemp())
def ensure_repo(self, args):
# Ensure pm repo.
if (args.repo_path / 'repository').is_dir():
print(f'Using existing pm repo: {args.repo_path}')
else:
print(f'Creating a new pm repository: {args.repo_path}')
run(args.pm, 'newrepo', '-vt', '-repo', args.repo_path)
# Ensure ffx repository.
print(f'Associating {args.repo_name} to {args.repo_path}')
run(
args.ffx,
'repository',
'add-from-pm',
'--repository',
args.repo_name,
args.repo_path,
)
# Ensure ffx target repository.
print(f'Registering {args.repo_name} to target device {args.target}')
run(
args.ffx,
'--target',
args.target,
'target',
'repository',
'register',
'-r',
args.repo_name,
)
# Optionally make the ffx repository default.
if args.make_repo_default:
old_default = run(args.ffx, 'repository', 'default', 'get')
print(f'Setting default ffx repository "{old_default}" => "{args.repo_name}"')
run(
args.ffx,
'repository',
'default',
'set',
args.repo_name,
)
def publish_packages(self, args):
# TODO(fxbug.dev/110617): Publish all packages with 1 command invocation.
print(f'Publishing packages: {args.packages}')
for package in args.packages:
run(
args.pm,
'publish',
'-vt',
'-a',
'-f',
package,
'-repo',
args.repo_path,
)
print(f'Published {len(args.packages)} packages')
def teardown(self, args):
if self.prompt_repo_cleanup:
input('Press enter to delete this repository, or ^C to quit.')
# Delete the pm repository.
rmtree(args.repo_path)
print(f'Deleted {args.repo_path}')
# Remove the ffx repository.
run('ffx', 'repository', 'remove', args.repo_name)
print(f'Removed the ffx repository {args.repo_name}')
def run(self, parser: ScopedArgumentParser) -> None:
# Parse arguments.
args = self.parse_args(parser)
# Check environment and gather information for publishing.
self.enable_ffx_repository(args)
self.ensure_target_device(args)
self.resolve_repo(args)
# Perform the publishing.
self.ensure_repo(args)
self.publish_packages(args)
self.workflow_state['environment_variables']['FUCHSIA_REPO_NAME'] = args.repo_name
# Optionally cleanup the repo.
self.teardown(args)
if __name__ == '__main__':
FuchsiaTaskPublish.main()