Bug 1746414 - Use standalone taskgraph's parameters module in Gecko, r=taskgraph-reviewers,jmaher

Rather than defining a distinct `Parameter` class for Gecko, this now uses the
`extend_parameters_schema` utility function (which mobile repos are already
using).

As a consequence, shared parameters are now defined in standalone taskgraph.
And only Gecko-specific parameters are listed in
`gecko_taskgraph/parameters.py`

The only exception is `project` which gets redefined so we can override the
standalone taskgraph default (since it derives `project` from the repo name,
which doesn't work for Gecko).

Differential Revision: https://phabricator.services.mozilla.com/D134515
This commit is contained in:
Andrew Halberstadt 2022-01-05 16:37:43 +00:00
Родитель 7d0ca11d88
Коммит d3f1ddd6d3
22 изменённых файлов: 119 добавлений и 441 удалений

Просмотреть файл

@ -422,7 +422,8 @@ task-priority:
'mozilla-inbound': 'low'
'default': 'very-low'
taskgraph: {}
taskgraph:
register: gecko_taskgraph:register
workers:
aliases:

Просмотреть файл

@ -45,6 +45,9 @@ Push Information
the symbolic ref containing ``head_rev`` that should be pulled from
``head_repository``.
``head_tag``
The tag attached to the revision, if any.
``owner``
Email address indicating the person who made the push. Note that this
value may be forged and *must not* be relied on for authentication.
@ -70,6 +73,9 @@ Push Information
A formatted timestamp of ``build_date``. Expressed as a string with the following
format: %Y%m%d%H%M%S
``repository_type``
The type of repository, either ``hg`` or ``git``.
``tasks_for``
The ``tasks_for`` value used to generate the decision task.

Просмотреть файл

@ -17,3 +17,14 @@ MAX_DEPENDENCIES = 99
# This is normally switched on via the --fast/-F flag to `mach taskgraph`
# Currently this skips toolchain task optimizations and schema validation
fast = False
def register(graph_config):
"""Used to register Gecko specific extensions.
Args:
graph_config: The graph configuration object.
"""
from gecko_taskgraph.parameters import register_parameters
register_parameters()

Просмотреть файл

@ -2,11 +2,10 @@
# 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/.
from taskgraph.parameters import Parameters
from .registry import register_callback_action
from gecko_taskgraph.actions.registry import register_callback_action
from gecko_taskgraph.decision import taskgraph_decision
from gecko_taskgraph.parameters import Parameters
from gecko_taskgraph.util.attributes import RELEASE_PROMOTION_PROJECTS

Просмотреть файл

@ -9,13 +9,13 @@ from types import FunctionType
from collections import namedtuple
from mozbuild.util import memoize
from taskgraph.parameters import Parameters
from taskgraph.util import yaml
from gecko_taskgraph import create
from gecko_taskgraph.config import load_graph_config
from gecko_taskgraph.util import taskcluster, hash
from gecko_taskgraph.util.python_path import import_sibling_modules
from gecko_taskgraph.parameters import Parameters
actions = []

Просмотреть файл

@ -6,8 +6,9 @@
import json
import os
from .registry import register_callback_action
from taskgraph.parameters import Parameters
from gecko_taskgraph.actions.registry import register_callback_action
from gecko_taskgraph.util.taskcluster import get_artifact
from gecko_taskgraph.util.taskgraph import (
find_decision_task,
@ -22,7 +23,6 @@ from gecko_taskgraph.util.partners import (
)
from gecko_taskgraph.taskgraph import TaskGraph
from gecko_taskgraph.decision import taskgraph_decision
from gecko_taskgraph.parameters import Parameters
from gecko_taskgraph.util.attributes import RELEASE_PROMOTION_PROJECTS, release_level

Просмотреть файл

@ -2,11 +2,10 @@
# 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/.
from taskgraph.parameters import Parameters
from gecko_taskgraph.actions.registry import register_callback_action
from gecko_taskgraph.decision import taskgraph_decision
from gecko_taskgraph.parameters import Parameters
from .registry import register_callback_action
@register_callback_action(

Просмотреть файл

@ -12,6 +12,7 @@ from collections import defaultdict
import yaml
from redo import retry
from taskgraph.parameters import Parameters
from taskgraph.util.yaml import load_yaml
from voluptuous import Required, Optional, Any
@ -19,7 +20,7 @@ from . import GECKO, create
from .actions import render_actions_json
from .create import create_tasks
from .generator import TaskGraphGenerator
from .parameters import Parameters, get_version, get_app_version
from .parameters import get_version, get_app_version
from .taskgraph import TaskGraph
from .try_option_syntax import parse_message
from .util.backstop import is_backstop, BACKSTOP_INDEX
@ -274,11 +275,13 @@ def get_decision_parameters(graph_config, options):
"head_repository",
"head_rev",
"head_ref",
"head_tag",
"project",
"pushlog_id",
"pushdate",
"owner",
"level",
"repository_type",
"target_tasks_method",
"tasks_for",
]

Просмотреть файл

@ -8,9 +8,10 @@ import os
import tarfile
from io import BytesIO
from taskgraph.parameters import Parameters
from gecko_taskgraph.generator import load_tasks_for_kind
from gecko_taskgraph.optimize.strategies import IndexSearch
from gecko_taskgraph.parameters import Parameters
from gecko_taskgraph.util import docker
from gecko_taskgraph.util.taskcluster import (
get_artifact_url,

Просмотреть файл

@ -7,17 +7,17 @@ import os
import copy
import attr
from taskgraph.parameters import Parameters
from taskgraph.util.yaml import load_yaml
from . import filter_tasks
from .graph import Graph
from .taskgraph import TaskGraph
from .task import Task
from .optimize import optimize_task_graph
from .morph import morph
from .parameters import Parameters
from .util.python_path import find_object
from .optimize import optimize_task_graph
from .task import Task
from .taskgraph import TaskGraph
from .transforms.base import TransformSequence, TransformConfig
from .util.python_path import find_object
from .util.verify import (
verify_docs,
verifications,

Просмотреть файл

@ -106,7 +106,7 @@ def get_taskgraph_generator(root, parameters):
def format_taskgraph(options, parameters, logfile=None):
import gecko_taskgraph
from gecko_taskgraph.parameters import parameters_loader
from taskgraph.parameters import parameters_loader
if logfile:
oldhandler = logging.root.handlers[-1]
@ -135,7 +135,7 @@ def format_taskgraph(options, parameters, logfile=None):
def dump_output(out, path=None, params_spec=None):
from gecko_taskgraph.parameters import Parameters
from taskgraph.parameters import Parameters
params_name = Parameters.format_spec(params_spec)
fh = None
@ -157,7 +157,7 @@ def dump_output(out, path=None, params_spec=None):
def generate_taskgraph(options, parameters, logdir):
from gecko_taskgraph.parameters import Parameters
from taskgraph.parameters import Parameters
def logfile(spec):
"""Determine logfile given a parameters specification."""
@ -315,7 +315,7 @@ def generate_taskgraph(options, parameters, logdir):
)
def show_taskgraph(options):
from mozversioncontrol import get_repository_object as get_repository
from gecko_taskgraph.parameters import Parameters
from taskgraph.parameters import Parameters
if options.pop("verbose", False):
logging.root.setLevel(logging.DEBUG)

Просмотреть файл

@ -2,36 +2,47 @@
# 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 hashlib
import json
import logging
import os
from datetime import datetime
from pprint import pformat
from urllib.parse import urlparse
from mozbuild.util import ReadOnlyDict, memoize
from mozversioncontrol import get_repository_object
from gecko_taskgraph.util.schema import validate_schema
from taskgraph.parameters import extend_parameters_schema
from voluptuous import (
ALLOW_EXTRA,
Any,
Required,
Schema,
)
from . import GECKO
from gecko_taskgraph import GECKO
logger = logging.getLogger(__name__)
class ParameterMismatch(Exception):
"""Raised when a parameters.yml has extra or missing parameters."""
@memoize
def get_head_ref():
return get_repository_object(GECKO).head_ref
gecko_parameters_schema = {
Required("app_version"): str,
Required("backstop"): bool,
Required("build_number"): int,
Required("hg_branch"): str,
Required("message"): str,
Required("next_version"): Any(None, str),
Required("optimize_strategies"): Any(None, str),
Required("phabricator_diff"): Any(None, str),
Required("release_enable_emefree"): bool,
Required("release_enable_partner_repack"): bool,
Required("release_enable_partner_attribution"): bool,
Required("release_eta"): Any(None, str),
Required("release_history"): {str: dict},
Required("release_partners"): Any(None, [str]),
Required("release_partner_config"): Any(None, dict),
Required("release_partner_build_number"): int,
Required("release_type"): str,
Required("release_product"): Any(None, str),
Required("required_signoffs"): [str],
Required("signoff_urls"): dict,
Required("test_manifest_loader"): str,
Required("try_mode"): Any(None, str),
Required("try_options"): Any(None, dict),
Required("try_task_config"): dict,
Required("version"): str,
}
def get_contents(path):
@ -50,265 +61,36 @@ def get_app_version(product_dir="browser"):
return get_contents(app_version_path)
base_schema = Schema(
{
Required("app_version"): str,
Required("backstop"): bool,
Required("base_repository"): str,
Required("build_date"): int,
Required("build_number"): int,
Required("do_not_optimize"): [str],
Required("existing_tasks"): {str: str},
Required("filters"): [str],
Required("head_ref"): str,
Required("head_repository"): str,
Required("head_rev"): str,
Required("hg_branch"): str,
Required("level"): str,
Required("message"): str,
Required("moz_build_date"): str,
Required("next_version"): Any(None, str),
Required("optimize_strategies"): Any(None, str),
Required("optimize_target_tasks"): bool,
Required("owner"): str,
Required("phabricator_diff"): Any(None, str),
Required("project"): str,
Required("pushdate"): int,
Required("pushlog_id"): str,
Required("release_enable_emefree"): bool,
Required("release_enable_partner_repack"): bool,
Required("release_enable_partner_attribution"): bool,
Required("release_eta"): Any(None, str),
Required("release_history"): {str: dict},
Required("release_partners"): Any(None, [str]),
Required("release_partner_config"): Any(None, dict),
Required("release_partner_build_number"): int,
Required("release_type"): str,
Required("release_product"): Any(None, str),
Required("required_signoffs"): [str],
Required("signoff_urls"): dict,
# target-kind is not included, since it should never be
# used at run-time
Required("target_tasks_method"): str,
Required("tasks_for"): str,
Required("test_manifest_loader"): str,
Required("try_mode"): Any(None, str),
Required("try_options"): Any(None, dict),
Required("try_task_config"): dict,
Required("version"): str,
def get_defaults():
return {
"app_version": get_app_version(),
"backstop": False,
"build_number": 1,
"hg_branch": "default",
"message": "",
"next_version": None,
"optimize_strategies": None,
"phabricator_diff": None,
"project": "mozilla-central",
"release_enable_emefree": False,
"release_enable_partner_repack": False,
"release_enable_partner_attribution": False,
"release_eta": "",
"release_history": {},
"release_partners": [],
"release_partner_config": None,
"release_partner_build_number": 1,
"release_product": None,
"release_type": "nightly",
"required_signoffs": [],
"signoff_urls": {},
"test_manifest_loader": "default",
"try_mode": None,
"try_options": None,
"try_task_config": {},
"version": get_version(),
}
)
class Parameters(ReadOnlyDict):
"""An immutable dictionary with nicer KeyError messages on failure"""
def __init__(self, strict=True, **kwargs):
self.strict = strict
self.spec = kwargs.pop("spec", "defaults")
self._id = None
if not self.strict:
# apply defaults to missing parameters
kwargs = Parameters._fill_defaults(**kwargs)
ReadOnlyDict.__init__(self, **kwargs)
@property
def id(self):
if not self._id:
self._id = hashlib.sha256(
json.dumps(self, sort_keys=True).encode("utf-8")
).hexdigest()[:12]
return self._id
@staticmethod
def format_spec(spec):
"""
Get a friendly identifier from a parameters specifier.
Args:
spec (str): Parameters specifier.
Returns:
str: Name to identify parameters by.
"""
if spec is None:
return "defaults"
if any(spec.startswith(s) for s in ("task-id=", "project=")):
return spec
result = urlparse(spec)
if result.scheme in ("http", "https"):
spec = result.path
return os.path.splitext(os.path.basename(spec))[0]
@staticmethod
def _fill_defaults(**kwargs):
now = datetime.utcnow()
epoch = datetime.utcfromtimestamp(0)
seconds_from_epoch = int((now - epoch).total_seconds())
defaults = {
"app_version": get_app_version(),
"backstop": False,
"base_repository": "https://hg.mozilla.org/mozilla-unified",
"build_date": seconds_from_epoch,
"build_number": 1,
"do_not_optimize": [],
"existing_tasks": {},
"filters": ["target_tasks_method"],
"head_ref": get_head_ref(),
"head_repository": "https://hg.mozilla.org/mozilla-central",
"head_rev": get_head_ref(),
"hg_branch": "default",
"level": "3",
"message": "",
"moz_build_date": now.strftime("%Y%m%d%H%M%S"),
"next_version": None,
"optimize_strategies": None,
"optimize_target_tasks": True,
"owner": "nobody@mozilla.com",
"phabricator_diff": None,
"project": "mozilla-central",
"pushdate": seconds_from_epoch,
"pushlog_id": "0",
"release_enable_emefree": False,
"release_enable_partner_repack": False,
"release_enable_partner_attribution": False,
"release_eta": "",
"release_history": {},
"release_partners": [],
"release_partner_config": None,
"release_partner_build_number": 1,
"release_product": None,
"release_type": "nightly",
"required_signoffs": [],
"signoff_urls": {},
"target_tasks_method": "default",
"tasks_for": "hg-push",
"test_manifest_loader": "default",
"try_mode": None,
"try_options": None,
"try_task_config": {},
"version": get_version(),
}
for name, default in defaults.items():
if name not in kwargs:
kwargs[name] = default
return kwargs
def check(self):
schema = (
base_schema if self.strict else base_schema.extend({}, extra=ALLOW_EXTRA)
)
validate_schema(schema, self.copy(), "Invalid parameters:")
def __getitem__(self, k):
try:
return super().__getitem__(k)
except KeyError:
raise KeyError(f"taskgraph parameter {k!r} not found")
def file_url(self, path, pretty=False):
"""
Determine the VCS URL for viewing a file in the tree, suitable for
viewing by a human.
:param text_type path: The path, relative to the root of the repository.
:param bool pretty: Whether to return a link to a formatted version of the
file, or the raw file version.
:return text_type: The URL displaying the given path.
"""
if path.startswith("comm/"):
path = path[len("comm/") :]
repo = self["comm_head_repository"]
rev = self["comm_head_rev"]
else:
repo = self["head_repository"]
rev = self["head_rev"]
endpoint = "file" if pretty else "raw-file"
return f"{repo}/{endpoint}/{rev}/{path}"
def __str__(self):
return f"Parameters(id={self.id}) (from {self.format_spec(self.spec)})"
def __repr__(self):
return pformat(dict(self), indent=2)
def load_parameters_file(spec, strict=True, overrides=None, trust_domain=None):
"""
Load parameters from a path, url, decision task-id or project.
Examples:
task-id=fdtgsD5DQUmAQZEaGMvQ4Q
project=mozilla-central
"""
import requests
from taskgraph.util import yaml
from gecko_taskgraph.util.taskcluster import get_artifact_url, find_task_id
if overrides is None:
overrides = {}
if not spec:
return Parameters(strict=strict, **overrides)
try:
# reading parameters from a local parameters.yml file
f = open(spec)
except OSError:
# fetching parameters.yml using task task-id, project or supplied url
task_id = None
if spec.startswith("task-id="):
task_id = spec.split("=")[1]
elif spec.startswith("project="):
if trust_domain is None:
raise ValueError(
"Can't specify parameters by project "
"if trust domain isn't supplied.",
)
index = "{trust_domain}.v2.{project}.latest.taskgraph.decision".format(
trust_domain=trust_domain,
project=spec.split("=")[1],
)
task_id = find_task_id(index)
if task_id:
spec = get_artifact_url(task_id, "public/parameters.yml")
logger.info(f"Loading parameters from {spec}")
resp = requests.get(spec, stream=True)
resp.raise_for_status()
f = resp.raw
if spec.endswith(".yml"):
kwargs = yaml.load_stream(f)
elif spec.endswith(".json"):
kwargs = json.load(f)
else:
raise TypeError(f"Parameters file `{spec}` is not JSON or YAML")
kwargs.update(overrides)
return Parameters(strict=strict, **kwargs)
def parameters_loader(spec, strict=True, overrides=None):
def get_parameters(graph_config):
parameters = load_parameters_file(
spec,
strict=strict,
overrides=overrides,
trust_domain=graph_config["trust-domain"],
)
parameters.check()
return parameters
return get_parameters
def register_parameters():
extend_parameters_schema(gecko_parameters_schema, defaults_fn=get_defaults)

Просмотреть файл

@ -8,8 +8,9 @@ import os
import re
from redo import retry
from taskgraph.parameters import Parameters
from gecko_taskgraph import try_option_syntax
from gecko_taskgraph.parameters import Parameters
from gecko_taskgraph.util.attributes import (
match_run_on_projects,
match_run_on_hg_branches,

Просмотреть файл

@ -7,6 +7,8 @@ import pytest
from mach.logging import LoggingManager
from responses import RequestsMock
from taskgraph.parameters import Parameters
from gecko_taskgraph import (
GECKO,
generator,
@ -17,7 +19,6 @@ from gecko_taskgraph.actions import render_actions_json
from gecko_taskgraph.config import load_graph_config, GraphConfig
from gecko_taskgraph.generator import TaskGraphGenerator, Kind
from gecko_taskgraph.optimize import OptimizationStrategy
from gecko_taskgraph.parameters import Parameters
from gecko_taskgraph.util.templates import merge

Просмотреть файл

@ -11,7 +11,6 @@ subsuite = taskgraph
[test_morph.py]
[test_optimize.py]
[test_optimize_strategies.py]
[test_parameters.py]
[test_target_tasks.py]
[test_taskgraph.py]
[test_taskcluster_yml.py]

Просмотреть файл

@ -9,16 +9,23 @@ import shutil
import unittest
import tempfile
import pytest
from mozunit import main, MockedOpen
from taskgraph.util.yaml import load_yaml
from unittest.mock import patch
from gecko_taskgraph import decision
from gecko_taskgraph.parameters import register_parameters
FAKE_GRAPH_CONFIG = {"product-dir": "browser", "taskgraph": {}}
@pytest.fixture(scope="module", autouse=True)
def register():
register_parameters()
class TestDecision(unittest.TestCase):
def test_write_artifact_json(self):
data = [{"some": "data"}]
@ -56,11 +63,13 @@ class TestGetDecisionParameters(unittest.TestCase):
"head_repository": "https://hg.mozilla.org/mozilla-central",
"head_rev": "abcd",
"head_ref": "ef01",
"head_tag": "",
"message": "",
"project": "mozilla-central",
"pushlog_id": "143",
"pushdate": 1503691511,
"owner": "nobody@mozilla.com",
"repository_type": "hg",
"tasks_for": "hg-push",
"level": "3",
}

Просмотреть файл

@ -4,10 +4,10 @@
import pytest
from taskgraph.parameters import Parameters
from gecko_taskgraph import morph
from gecko_taskgraph.graph import Graph
from gecko_taskgraph.parameters import Parameters
from gecko_taskgraph.taskgraph import TaskGraph
from gecko_taskgraph.task import Task

Просмотреть файл

@ -1,131 +0,0 @@
# 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 unittest
from gecko_taskgraph.parameters import (
Parameters,
load_parameters_file,
)
from mozunit import main, MockedOpen
class TestParameters(unittest.TestCase):
vals = {
"app_version": "app_version",
"backstop": False,
"base_repository": "base_repository",
"build_date": 0,
"build_number": 0,
"do_not_optimize": [],
"existing_tasks": {},
"filters": [],
"head_ref": "head_ref",
"head_repository": "head_repository",
"head_rev": "head_rev",
"hg_branch": "hg_branch",
"level": "level",
"message": "message",
"moz_build_date": "moz_build_date",
"next_version": "next_version",
"optimize_strategies": None,
"optimize_target_tasks": False,
"owner": "owner",
"phabricator_diff": "phabricator_diff",
"project": "project",
"pushdate": 0,
"pushlog_id": "pushlog_id",
"release_enable_emefree": False,
"release_enable_partner_repack": False,
"release_enable_partner_attribution": False,
"release_eta": None,
"release_history": {},
"release_partners": [],
"release_partner_config": None,
"release_partner_build_number": 1,
"release_type": "release_type",
"release_product": None,
"required_signoffs": [],
"signoff_urls": {},
"target_tasks_method": "target_tasks_method",
"test_manifest_loader": "default",
"tasks_for": "tasks_for",
"try_mode": "try_mode",
"try_options": None,
"try_task_config": {},
"version": "version",
}
def test_Parameters_immutable(self):
p = Parameters(**self.vals)
def assign():
p["head_ref"] = 20
self.assertRaises(Exception, assign)
def test_Parameters_missing_KeyError(self):
p = Parameters(**self.vals)
self.assertRaises(KeyError, lambda: p["z"])
def test_Parameters_invalid_KeyError(self):
"""even if the value is present, if it's not a valid property, raise KeyError"""
p = Parameters(xyz=10, strict=True, **self.vals)
self.assertRaises(Exception, lambda: p.check())
def test_Parameters_get(self):
p = Parameters(head_ref=10, level=20)
self.assertEqual(p["head_ref"], 10)
def test_Parameters_check(self):
p = Parameters(**self.vals)
p.check() # should not raise
def test_Parameters_check_missing(self):
p = Parameters()
self.assertRaises(Exception, lambda: p.check())
p = Parameters(strict=False)
p.check() # should not raise
def test_Parameters_check_extra(self):
p = Parameters(xyz=10, **self.vals)
self.assertRaises(Exception, lambda: p.check())
p = Parameters(strict=False, xyz=10, **self.vals)
p.check() # should not raise
def test_load_parameters_file_yaml(self):
with MockedOpen({"params.yml": "some: data\n"}):
self.assertEqual(load_parameters_file("params.yml"), {"some": "data"})
def test_load_parameters_file_json(self):
with MockedOpen({"params.json": '{"some": "data"}'}):
self.assertEqual(load_parameters_file("params.json"), {"some": "data"})
def test_load_parameters_override(self):
"""
When ``load_parameters_file`` is passed overrides, they are included in
the generated parameters.
"""
self.assertEqual(
load_parameters_file("", overrides={"some": "data"}), {"some": "data"}
)
def test_load_parameters_override_file(self):
"""
When ``load_parameters_file`` is passed overrides, they overwrite data
loaded from a file.
"""
with MockedOpen({"params.json": '{"some": "data"}'}):
self.assertEqual(
load_parameters_file("params.json", overrides={"some": "other"}),
{"some": "other"},
)
if __name__ == "__main__":
main()

Просмотреть файл

@ -4,9 +4,9 @@
import attr
from taskgraph.parameters import Parameters
from ..config import GraphConfig
from ..parameters import Parameters
from ..util.schema import Schema, validate_schema

Просмотреть файл

@ -360,13 +360,13 @@ def show_actions(command_context, options):
import gecko_taskgraph
import gecko_taskgraph.actions
import gecko_taskgraph.generator
import gecko_taskgraph.parameters
from taskgraph.parameters import parameters_loader
try:
setup_logging(
command_context, quiet=options["quiet"], verbose=options["verbose"]
)
parameters = gecko_taskgraph.parameters.parameters_loader(options["parameters"])
parameters = parameters_loader(options["parameters"])
tgg = gecko_taskgraph.generator.TaskGraphGenerator(
root_dir=options.get("root"),

Просмотреть файл

@ -9,9 +9,9 @@ import os
import pytest
from responses import RequestsMock, logger as rsps_logger
from taskgraph.parameters import parameters_loader
from gecko_taskgraph.generator import TaskGraphGenerator
from gecko_taskgraph.parameters import parameters_loader
from gecko_taskgraph.util.hg import PUSHLOG_PUSHES_TMPL
from gecko_taskgraph.util.bugbug import BUGBUG_BASE_URL

Просмотреть файл

@ -13,13 +13,10 @@ from mach.util import get_state_dir
from mozbuild.base import MozbuildObject
from mozpack.files import FileFinder
from moztest.resolve import TestResolver, TestManifestLoader, get_suite_definition
from taskgraph.parameters import ParameterMismatch, parameters_loader
import gecko_taskgraph
from gecko_taskgraph.generator import TaskGraphGenerator
from gecko_taskgraph.parameters import (
ParameterMismatch,
parameters_loader,
)
from gecko_taskgraph.taskgraph import TaskGraph
here = os.path.abspath(os.path.dirname(__file__))