# 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 os
import tarfile
from datetime
import datetime, timedelta
import mozversioncontrol
import requests
import six
try:
from cStringIO
import StringIO
as BytesIO
except ImportError:
from io
import BytesIO
HEADERS = {
"User-Agent":
"wpt manifest download"}
def get(logger, url, **kwargs):
logger.debug(url)
if "headers" not in kwargs:
kwargs[
"headers"] = HEADERS
return requests.get(url, **kwargs)
def abs_path(path):
return os.path.abspath(os.path.expanduser(path))
def get_commits(logger, repo_root):
try:
repo = mozversioncontrol.get_repository_object(repo_root)
except mozversioncontrol.InvalidRepoPath:
logger.warning(
"No VCS found for path %s" % repo_root)
return []
# The base_ref doesn't actually return a ref, sadly
base_rev = repo.base_ref
if repo.name ==
"git":
logger.debug(
"Found git repo")
logger.debug(
"Base rev is %s" % base_rev)
if not repo.has_git_cinnabar:
logger.error(
"git cinnabar not found")
return []
changeset_iter = (
repo._run(
"cinnabar",
"git2hg", rev).strip()
for rev
in repo._run(
"log",
"--format=%H",
"-n50",
base_rev,
"testing/web-platform/tests",
"testing/web-platform/mozilla/tests",
).splitlines()
)
else:
logger.debug(
"Found hg repo")
logger.debug(
"Base rev is %s" % base_rev)
changeset_iter = repo._run(
"log",
"-fl50",
"--template={node}\n",
"-r",
base_rev,
"testing/web-platform/tests",
"testing/web-platform/mozilla/tests",
).splitlines()
return changeset_iter
def should_download(logger, manifest_paths, rebuild_time=timedelta(days=5)):
# TODO: Improve logic for when to download. Maybe if x revisions behind?
for manifest_path
in manifest_paths:
if not os.path.exists(manifest_path):
return True
mtime = datetime.fromtimestamp(os.path.getmtime(manifest_path))
if mtime < datetime.now() - rebuild_time:
return True
if os.path.getsize(manifest_path) == 0:
return True
logger.info(
"Skipping manifest download because existing file is recent")
return False
def taskcluster_url(logger, commits):
artifact_path =
"/artifacts/public/manifests.tar.gz"
repos = {
"mozilla-central":
"mozilla-central",
"integration/autoland":
"autoland",
"releases/mozilla-esr115":
"mozilla-esr115",
"releases/mozilla-esr128":
"mozilla-esr128",
}
cset_url = (
"https://hg.mozilla.org/{repo}/json-pushes?"
"changeset={changeset}&version=2&tipsonly=1"
)
tc_url = (
"https://firefox-ci-tc.services.mozilla.com/api/index/v1/"
"task/gecko.v2.{name}."
"revision.{changeset}.source.manifest-upload"
)
default = (
"https://firefox-ci-tc.services.mozilla.com/api/index/v1/"
"task/gecko.v2.mozilla-central.latest.source.manifest-upload" + artifact_path
)
for revision
in commits:
req =
None
if revision == 40 *
"0":
continue
for repo_path, index_name
in six.iteritems(repos):
try:
req_headers = HEADERS.copy()
req_headers.update({
"Accept":
"application/json"})
req = get(
logger,
cset_url.format(changeset=revision, repo=repo_path),
headers=req_headers,
)
req.raise_for_status()
except requests.exceptions.RequestException:
if req
is not None and req.status_code == 404:
# The API returns a 404 if it can't find a changeset for the revision.
logger.debug(
"%s not found in %s" % (revision, repo_path))
continue
else:
return default
result = req.json()
pushes = result[
"pushes"]
if not pushes:
logger.debug(
"Error reading response; 'pushes' key not found")
continue
[cset] = next(iter(pushes.values()))[
"changesets"]
tc_index_url = tc_url.format(changeset=cset, name=index_name)
try:
req = get(logger, tc_index_url)
except requests.exceptions.RequestException:
return default
if req.status_code == 200:
return tc_index_url + artifact_path
logger.info(
"Can't find a commit-specific manifest so just using the most " "recent one"
)
return default
def download_manifest(logger, test_paths, commits_func, url_func, force=
False):
manifest_paths = [
(item[
"manifest_path"]
if isinstance(item, dict)
else item.manifest_path)
for item
in test_paths.values()
]
if not force
and not should_download(logger, manifest_paths):
return True
commits = commits_func()
url = url_func(logger, commits)
if not url:
logger.warning(
"No generated manifest found")
return False
logger.info(
"Downloading manifest from %s" % url)
try:
req = get(logger, url)
except Exception:
logger.warning(
"Downloading pregenerated manifest failed")
return False
if req.status_code != 200:
logger.warning(
"Downloading pregenerated manifest failed; got "
"HTTP status %d" % req.status_code
)
return False
tar = tarfile.open(mode=
"r:gz", fileobj=BytesIO(req.content))
for paths
in six.itervalues(test_paths):
manifest_rel_path = (
paths[
"manifest_rel_path"]
if isinstance(paths, dict)
else paths.manifest_rel_path
)
manifest_path = (
paths[
"manifest_path"]
if isinstance(paths, dict)
else paths.manifest_path
)
try:
member = tar.getmember(manifest_rel_path.replace(os.path.sep,
"/"))
except KeyError:
logger.warning(
"Failed to find downloaded manifest %s" % manifest_rel_path)
else:
try:
logger.debug(
"Unpacking %s to %s" % (member.name, manifest_path))
src = tar.extractfile(member)
with open(manifest_path,
"wb")
as dest:
dest.write(src.read())
src.close()
except IOError:
import traceback
logger.warning(
"Failed to decompress %s:\n%s"
% (manifest_rel_path, traceback.format_exc())
)
return False
os.utime(manifest_path,
None)
return True
def download_from_taskcluster(logger, repo_root, test_paths, force=
False):
return download_manifest(
logger,
test_paths,
lambda: get_commits(logger, repo_root),
taskcluster_url,
force,
)