diff --git a/.taskcluster.yml b/.taskcluster.yml index 0b831b6b6..9d25b1bf6 100644 --- a/.taskcluster.yml +++ b/.taskcluster.yml @@ -34,7 +34,7 @@ tasks: $flatten: - queue:scheduler-id:${scheduler_id} - queue:create-task:highest:aws-provisioner-v1/gecko-focus - - project:mobile:fenix:releng:signing:format:autograph_fenix + - project:mobile:fenix:releng:signing:format:autograph_apk - $if: is_mozilla_mobile_repo then: - queue:create-task:highest:scriptworker-prov-v1/mobile-signing-v1 @@ -69,8 +69,7 @@ tasks: cd .. && git clone ${repository} repository && cd repository - && pip install arrow - && python tools/taskcluster/decision_task_nightly.py \ + && python automation/taskcluster/decision_task_nightly.py \ --track ${track} \ --commit \ --output /opt/repository/app/build/outputs/apk \ diff --git a/app/build.gradle b/app/build.gradle index 60d1c8990..b54b8e63f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -43,7 +43,7 @@ android.applicationVariants.all { variant -> // same version code. Therefore we need to have different version codes for our ARM and x86 // builds. - // Our generated version code now has a length of 9 (See tools/gradle/versionCode.gradle). + // Our generated version code now has a length of 9 (See automation/gradle/versionCode.gradle). // Our x86 builds need a higher version code to avoid installing ARM builds on an x86 device // with ARM compatibility mode. diff --git a/automation/__init__.py b/automation/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/automation/taskcluster/__init__.py b/automation/taskcluster/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/automation/taskcluster/decision_task_nightly.py b/automation/taskcluster/decision_task_nightly.py index 14130a865..6b839378e 100644 --- a/automation/taskcluster/decision_task_nightly.py +++ b/automation/taskcluster/decision_task_nightly.py @@ -52,7 +52,7 @@ def generate_build_task(apks): def generate_signing_task(build_task_id, apks, date, is_staging): artifacts = ["public/{}".format(os.path.basename(apk)) for apk in apks] - signing_format = 'autograph_fenix' + signing_format = 'autograph_apk' index_release = 'staging-signed-nightly' if is_staging else 'signed-nightly' routes = [ "index.project.mobile.fenix.{}.nightly.{}.{}.{}.latest".format(index_release, date.year, date.month, date.day), @@ -139,12 +139,12 @@ if __name__ == "__main__": description='Create a release pipeline (build, sign, publish) on taskcluster.') parser.add_argument('--track', dest="track", action="store", choices=['nightly', 'staging-nightly'], required=True) - parser.add_argument('--commit', dest="commit", action="store_true", help="commit the google play transaction") + parser.add_argument('--commit', action="store_true", help="commit the google play transaction") parser.add_argument('--apk', dest="apks", metavar="path", action="append", help="Path to APKs to sign and upload", required=True) - parser.add_argument('--output', dest="track", metavar="path", action="store", help="Path to the build output", + parser.add_argument('--output', metavar="path", action="store", help="Path to the build output", required=True) - parser.add_argument('--date', dest="date", action="store", help="ISO8601 timestamp for build") + parser.add_argument('--date', action="store", help="ISO8601 timestamp for build") result = parser.parse_args() apks = ["{}/{}".format(result.output, apk) for apk in result.apks] diff --git a/automation/taskcluster/lib/tasks.py b/automation/taskcluster/lib/tasks.py index d6ff793cd..06bbac53f 100644 --- a/automation/taskcluster/lib/tasks.py +++ b/automation/taskcluster/lib/tasks.py @@ -9,11 +9,8 @@ import taskcluster class TaskBuilder(object): - def __init__(self, task_id, repo_url, branch, commit, owner, source, scheduler_id): + def __init__(self, task_id, owner, source, scheduler_id): self.task_id = task_id - self.repo_url = repo_url - self.branch = branch - self.commit = commit self.owner = owner self.source = source self.scheduler_id = scheduler_id @@ -29,7 +26,7 @@ class TaskBuilder(object): }) return { - "workerType": 'github-worker', + "workerType": 'gecko-focus', "taskGroupId": self.task_id, "schedulerId": self.scheduler_id, "expires": taskcluster.stringDate(expires), diff --git a/automation/taskcluster/schedule_nightly_graph.py b/automation/taskcluster/schedule_nightly_graph.py new file mode 100644 index 000000000..ef5c527de --- /dev/null +++ b/automation/taskcluster/schedule_nightly_graph.py @@ -0,0 +1,101 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import argparse +import datetime +import jsone +import os +import slugid +import taskcluster +import yaml + +from git import Repo +from lib.tasks import schedule_task + +ROOT = os.path.join(os.path.dirname(__file__), '../..') + + +class InvalidGithubRepositoryError(Exception): + pass + + +def calculate_git_references(root): + repo = Repo(root) + remote = repo.remote() + branch = repo.head.reference + + if not remote.url.startswith('https://github.com'): + raise InvalidGithubRepositoryError('expected remote to be a GitHub repository (accessed via HTTPs)') + + url = remote.url[:-4] if remote.url.endswith('.git') else remote.url + return url, str(branch), str(branch.commit) + + +def make_decision_task(params): + """Generate a basic decision task, based on the root .taskcluster.yml""" + with open(os.path.join(ROOT, '.taskcluster.yml'), 'rb') as f: + taskcluster_yml = yaml.safe_load(f) + + slugids = {} + + def as_slugid(name): + if name not in slugids: + slugids[name] = slugid.nice() + return slugids[name] + + # provide a similar JSON-e context to what taskcluster-github provides + context = { + 'tasks_for': 'cron', + 'cron': { + 'task_id': params['cron_task_id'] + }, + 'now': datetime.datetime.utcnow().isoformat()[:23] + 'Z', + 'as_slugid': as_slugid, + 'event': { + 'repository': { + 'clone_url': params['repository_github_http_url'] + }, + 'release': { + 'tag_name': params['head_rev'], + 'target_commitish': params['branch'] + }, + 'sender': { + 'login': 'TaskclusterHook' + } + } + } + + rendered = jsone.render(taskcluster_yml, context) + if len(rendered['tasks']) != 1: + raise Exception('Expected .taskcluster.yml to only produce one cron task') + task = rendered['tasks'][0] + + task_id = task.pop('taskId') + return task_id, task + + +def schedule(is_staging): + queue = taskcluster.Queue({'baseUrl': 'http://taskcluster/queue/v1'}) + + repository_github_http_url, branch, head_rev = calculate_git_references(ROOT) + params = { + 'is_staging': is_staging, + 'repository_github_http_url': repository_github_http_url, + 'head_rev': head_rev, + 'branch': branch, + 'cron_task_id': os.environ.get('CRON_TASK_ID', '') + } + decision_task_id, decision_task = make_decision_task(params) + schedule_task(queue, decision_task_id, decision_task) + print('All scheduled!') + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Schedule a nightly release pipeline') + + parser.add_argument('--staging', action='store_true', + help="Perform a staging build (use dep workers, don't communicate with Google Play) ") + + result = parser.parse_args() + schedule(result.staging)