Bug 1759030 - Vendor `taskcluster-taskgraph` at `3.5.1`, r=ahochheiden

Differential Revision: https://phabricator.services.mozilla.com/D161056
This commit is contained in:
ahochheiden 2022-11-04 14:14:56 +00:00
Родитель 71b86860bf
Коммит 55811f8006
24 изменённых файлов: 2217 добавлений и 1374 удалений

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

@ -464,7 +464,7 @@ class PLURALS(LegacySource):
selector = ctx.evaluate(self.selector) selector = ctx.evaluate(self.selector)
keys = ctx.plural_categories keys = ctx.plural_categories
forms = [ forms = [
FTL.TextElement(part.strip()) FTL.TextElement(part)
for part in element.value.split(';') for part in element.value.split(';')
] ]

15
third_party/python/poetry.lock сгенерированный поставляемый
Просмотреть файл

@ -252,8 +252,8 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
zipp = ">=0.5" zipp = ">=0.5"
[package.extras] [package.extras]
testing = ["importlib-resources (>=1.3)", "pep517", "packaging"] docs = ["sphinx", "rst.linker"]
docs = ["rst.linker", "sphinx"] testing = ["packaging", "pep517", "importlib-resources (>=1.3)"]
[[package]] [[package]]
name = "iso8601" name = "iso8601"
@ -634,7 +634,7 @@ test = ["pytest", "pytest-cov", "pytest-mock", "httmock", "mock", "setuptools-li
[[package]] [[package]]
name = "taskcluster-taskgraph" name = "taskcluster-taskgraph"
version = "3.2.1" version = "3.5.1"
description = "Build taskcluster taskgraphs" description = "Build taskcluster taskgraphs"
category = "main" category = "main"
optional = false optional = false
@ -653,6 +653,9 @@ slugid = ">=2.0"
taskcluster-urls = ">=11.0" taskcluster-urls = ">=11.0"
voluptuous = ">=0.12.1" voluptuous = ">=0.12.1"
[package.extras]
load-image = ["zstandard"]
[[package]] [[package]]
name = "taskcluster-urls" name = "taskcluster-urls"
version = "13.0.1" version = "13.0.1"
@ -757,7 +760,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.6" python-versions = "^3.6"
content-hash = "eb26337dadb86c9a4895059df8799e1d81eb31e85150dea8931c2207a0bef168" content-hash = "a12185410cebae037b0b16a9f8a3b99e1069c4f8d55157c48e15cc3f14233228"
[metadata.files] [metadata.files]
aiohttp = [ aiohttp = [
@ -1153,8 +1156,8 @@ taskcluster = [
{file = "taskcluster-44.2.2.tar.gz", hash = "sha256:0266a6a901e1a2ec838984a7f24e7adb6d58f9f2e221a7f613388f8f23f786fc"}, {file = "taskcluster-44.2.2.tar.gz", hash = "sha256:0266a6a901e1a2ec838984a7f24e7adb6d58f9f2e221a7f613388f8f23f786fc"},
] ]
taskcluster-taskgraph = [ taskcluster-taskgraph = [
{file = "taskcluster-taskgraph-3.2.1.tar.gz", hash = "sha256:c638724f0d514a3fc2d6ba34ddd395cfe021312dcf78b01c789ec4c7bf068cf0"}, {file = "taskcluster-taskgraph-3.5.1.tar.gz", hash = "sha256:e08b935175349ef8728ff5f19c7e9866a562256180f5580b291da3217cb5016c"},
{file = "taskcluster_taskgraph-3.2.1-py3-none-any.whl", hash = "sha256:87498ce08c5d2bfe0fd0b1a860e3dc2b9eba4d7acb883e9e5c2b6f7f15281a34"}, {file = "taskcluster_taskgraph-3.5.1-py3-none-any.whl", hash = "sha256:dc56b87228fb8eb1ef611750202344817a8cf5d825c0dc7e2dcc0f8b2795cbcd"},
] ]
taskcluster-urls = [ taskcluster-urls = [
{file = "taskcluster-urls-13.0.1.tar.gz", hash = "sha256:b25e122ecec249c4299ac7b20b08db76e3e2025bdaeb699a9d444556de5fd367"}, {file = "taskcluster-urls-13.0.1.tar.gz", hash = "sha256:b25e122ecec249c4299ac7b20b08db76e3e2025bdaeb699a9d444556de5fd367"},

98
third_party/python/requirements.in поставляемый
Просмотреть файл

@ -1,49 +1,49 @@
appdirs==1.4.4 appdirs==1.4.4
attrs==19.2.0 attrs==19.2.0
blessings==1.7 blessings==1.7
cbor2==4.0.1 cbor2==4.0.1
colorama==0.4.5 colorama==0.4.5
compare-locales==8.2.1 compare-locales==8.2.1
cookies==2.2.1 cookies==2.2.1
cram==0.7 cram==0.7
distro==1.4.0 distro==1.4.0
ecdsa==0.15 ecdsa==0.15
esprima==4.0.1 esprima==4.0.1
fluent.migrate==0.11 fluent.migrate==0.11
fluent.syntax==0.18.1 fluent.syntax==0.18.1
glean_parser==6.1.2 glean_parser==6.1.2
# Pin importlib-metadata to a version compatible with poetry # Pin importlib-metadata to a version compatible with poetry
importlib-metadata==1.7.0 importlib-metadata==1.7.0
jsmin==2.1.0 jsmin==2.1.0
json-e==2.7.0 json-e==2.7.0
looseversion==1.0.1 looseversion==1.0.1
mozilla-repo-urls==0.1.0 mozilla-repo-urls==0.1.0
mozilla-version==0.3.4 mozilla-version==0.3.4
packaging==20.9 packaging==20.9
pathspec==0.9.0 pathspec==0.9.0
pip==21.2.4 pip==21.2.4
pip-tools==5.5.0 pip-tools==5.5.0
ply==3.10 ply==3.10
pyasn1==0.4.8 pyasn1==0.4.8
pyasn1-modules==0.2.8 pyasn1-modules==0.2.8
pylru==1.0.9 pylru==1.0.9
python-hglib==2.4 python-hglib==2.4
pytoml==0.1.10 pytoml==0.1.10
pyyaml==5.4.1 pyyaml==5.4.1
redo==2.0.3 redo==2.0.3
requests==2.25.1 requests==2.25.1
requests-unixsocket==0.2.0 requests-unixsocket==0.2.0
responses==0.10.6 responses==0.10.6
rsa==3.1.4 rsa==3.1.4
sentry-sdk==0.14.3 sentry-sdk==0.14.3
setuptools==51.2.0 setuptools==51.2.0
six==1.13.0 six==1.13.0
slugid==2.0.0 slugid==2.0.0
taskcluster==44.2.2 taskcluster==44.2.2
taskcluster-taskgraph==3.2.1 taskcluster-taskgraph==3.5.1
taskcluster-urls==13.0.1 taskcluster-urls==13.0.1
tqdm==4.62.3 tqdm==4.62.3
urllib3==1.26 urllib3==1.26
voluptuous==0.12.1 voluptuous==0.12.1
wheel==0.37.0 wheel==0.37.0
yamllint==1.23 yamllint==1.23

802
third_party/python/requirements.txt поставляемый
Просмотреть файл

@ -1,401 +1,401 @@
aiohttp==3.7.4.post0; python_version >= "3.6" \ aiohttp==3.7.4.post0; python_version >= "3.6" \
--hash=sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5 \ --hash=sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5 \
--hash=sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8 \ --hash=sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8 \
--hash=sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95 \ --hash=sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95 \
--hash=sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290 \ --hash=sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290 \
--hash=sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f \ --hash=sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f \
--hash=sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809 \ --hash=sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809 \
--hash=sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe \ --hash=sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe \
--hash=sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287 \ --hash=sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287 \
--hash=sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc \ --hash=sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc \
--hash=sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87 \ --hash=sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87 \
--hash=sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0 \ --hash=sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0 \
--hash=sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970 \ --hash=sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970 \
--hash=sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f \ --hash=sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f \
--hash=sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde \ --hash=sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde \
--hash=sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c \ --hash=sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c \
--hash=sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8 \ --hash=sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8 \
--hash=sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f \ --hash=sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f \
--hash=sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5 \ --hash=sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5 \
--hash=sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf \ --hash=sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf \
--hash=sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df \ --hash=sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df \
--hash=sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213 \ --hash=sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213 \
--hash=sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4 \ --hash=sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4 \
--hash=sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009 \ --hash=sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009 \
--hash=sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5 \ --hash=sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5 \
--hash=sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013 \ --hash=sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013 \
--hash=sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16 \ --hash=sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16 \
--hash=sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5 \ --hash=sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5 \
--hash=sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b \ --hash=sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b \
--hash=sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd \ --hash=sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd \
--hash=sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439 \ --hash=sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439 \
--hash=sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22 \ --hash=sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22 \
--hash=sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a \ --hash=sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a \
--hash=sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb \ --hash=sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb \
--hash=sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb \ --hash=sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb \
--hash=sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9 \ --hash=sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9 \
--hash=sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe \ --hash=sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe \
--hash=sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf --hash=sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf
appdirs==1.4.4 \ appdirs==1.4.4 \
--hash=sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128 \ --hash=sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128 \
--hash=sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41 --hash=sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41
async-timeout==3.0.1; python_full_version >= "3.5.3" and python_version >= "3.6" \ async-timeout==3.0.1; python_full_version >= "3.5.3" and python_version >= "3.6" \
--hash=sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f \ --hash=sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f \
--hash=sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3 --hash=sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3
attrs==19.2.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \ attrs==19.2.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \
--hash=sha256:ec20e7a4825331c1b5ebf261d111e16fa9612c1f7a5e1f884f12bd53a664dfd2 \ --hash=sha256:ec20e7a4825331c1b5ebf261d111e16fa9612c1f7a5e1f884f12bd53a664dfd2 \
--hash=sha256:f913492e1663d3c36f502e5e9ba6cd13cf19d7fab50aa13239e420fef95e1396 --hash=sha256:f913492e1663d3c36f502e5e9ba6cd13cf19d7fab50aa13239e420fef95e1396
blessings==1.7; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \ blessings==1.7; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \
--hash=sha256:caad5211e7ba5afe04367cdd4cfc68fa886e2e08f6f35e76b7387d2109ccea6e \ --hash=sha256:caad5211e7ba5afe04367cdd4cfc68fa886e2e08f6f35e76b7387d2109ccea6e \
--hash=sha256:b1fdd7e7a675295630f9ae71527a8ebc10bfefa236b3d6aa4932ee4462c17ba3 \ --hash=sha256:b1fdd7e7a675295630f9ae71527a8ebc10bfefa236b3d6aa4932ee4462c17ba3 \
--hash=sha256:98e5854d805f50a5b58ac2333411b0482516a8210f23f43308baeb58d77c157d --hash=sha256:98e5854d805f50a5b58ac2333411b0482516a8210f23f43308baeb58d77c157d
cbor2==4.0.1 \ cbor2==4.0.1 \
--hash=sha256:b0eb916c9ea226aa81e9091607737475d5b0e5c314fe8d5a87179fba449cd190 \ --hash=sha256:b0eb916c9ea226aa81e9091607737475d5b0e5c314fe8d5a87179fba449cd190 \
--hash=sha256:cee0d01e520563b5a73c72eace5c428bb68aefb1b3f7aee5d692d3af6a1e5172 --hash=sha256:cee0d01e520563b5a73c72eace5c428bb68aefb1b3f7aee5d692d3af6a1e5172
certifi==2018.4.16 \ certifi==2018.4.16 \
--hash=sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0 \ --hash=sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0 \
--hash=sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7 --hash=sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7
chardet==4.0.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" or python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" \ chardet==4.0.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" or python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" \
--hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 \ --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 \
--hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa
click==7.1.2; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" \ click==7.1.2; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" \
--hash=sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc \ --hash=sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc \
--hash=sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a --hash=sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a
colorama==0.4.5; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") \ colorama==0.4.5; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") \
--hash=sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da \ --hash=sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da \
--hash=sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4 --hash=sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4
compare-locales==8.2.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0" and python_version < "4") \ compare-locales==8.2.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0" and python_version < "4") \
--hash=sha256:470d50d96c68f8e147daa3d70f29a7b750adefea450c5fa07e0f666c8083d854 \ --hash=sha256:470d50d96c68f8e147daa3d70f29a7b750adefea450c5fa07e0f666c8083d854 \
--hash=sha256:e6a1610151d357e74ee6c1f5e944f1868e449f83e478c84d92f7b86132f721d7 --hash=sha256:e6a1610151d357e74ee6c1f5e944f1868e449f83e478c84d92f7b86132f721d7
cookies==2.2.1 \ cookies==2.2.1 \
--hash=sha256:15bee753002dff684987b8df8c235288eb8d45f8191ae056254812dfd42c81d3 \ --hash=sha256:15bee753002dff684987b8df8c235288eb8d45f8191ae056254812dfd42c81d3 \
--hash=sha256:d6b698788cae4cfa4e62ef8643a9ca332b79bd96cb314294b864ae8d7eb3ee8e --hash=sha256:d6b698788cae4cfa4e62ef8643a9ca332b79bd96cb314294b864ae8d7eb3ee8e
cram==0.7 \ cram==0.7 \
--hash=sha256:008e4e8b4d325cf040964b5f62460535b004a7bc816d54f8527a4d299edfe4a3 \ --hash=sha256:008e4e8b4d325cf040964b5f62460535b004a7bc816d54f8527a4d299edfe4a3 \
--hash=sha256:7da7445af2ce15b90aad5ec4792f857cef5786d71f14377e9eb994d8b8337f2f --hash=sha256:7da7445af2ce15b90aad5ec4792f857cef5786d71f14377e9eb994d8b8337f2f
diskcache==4.1.0 \ diskcache==4.1.0 \
--hash=sha256:69b253a6ffe95bb4bafb483b97c24fca3c2c6c47b82e92b36486969a7e80d47d \ --hash=sha256:69b253a6ffe95bb4bafb483b97c24fca3c2c6c47b82e92b36486969a7e80d47d \
--hash=sha256:bcee5a59f9c264e2809e58d01be6569a3bbb1e36a1e0fb83f7ef9b2075f95ce0 --hash=sha256:bcee5a59f9c264e2809e58d01be6569a3bbb1e36a1e0fb83f7ef9b2075f95ce0
distro==1.4.0 \ distro==1.4.0 \
--hash=sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4 \ --hash=sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4 \
--hash=sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57 --hash=sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57
ecdsa==0.15; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0") \ ecdsa==0.15; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0") \
--hash=sha256:867ec9cf6df0b03addc8ef66b56359643cb5d0c1dc329df76ba7ecfe256c8061 \ --hash=sha256:867ec9cf6df0b03addc8ef66b56359643cb5d0c1dc329df76ba7ecfe256c8061 \
--hash=sha256:8f12ac317f8a1318efa75757ef0a651abe12e51fc1af8838fb91079445227277 --hash=sha256:8f12ac317f8a1318efa75757ef0a651abe12e51fc1af8838fb91079445227277
esprima==4.0.1 \ esprima==4.0.1 \
--hash=sha256:08db1a876d3c2910db9cfaeb83108193af5411fc3a3a66ebefacd390d21323ee --hash=sha256:08db1a876d3c2910db9cfaeb83108193af5411fc3a3a66ebefacd390d21323ee
fluent.migrate==0.11 \ fluent.migrate==0.11 \
--hash=sha256:3b93fdba9cbc8702d160367ba3a0d5c120707fdde752af35aecf516ce80ed252 --hash=sha256:3b93fdba9cbc8702d160367ba3a0d5c120707fdde752af35aecf516ce80ed252
fluent.syntax==0.18.1 \ fluent.syntax==0.18.1 \
--hash=sha256:0e63679fa4f1b3042565220a5127b4bab842424f07d6a13c12299e3b3835486a \ --hash=sha256:0e63679fa4f1b3042565220a5127b4bab842424f07d6a13c12299e3b3835486a \
--hash=sha256:3a55f5e605d1b029a65cc8b6492c86ec4608e15447e73db1495de11fd46c104f --hash=sha256:3a55f5e605d1b029a65cc8b6492c86ec4608e15447e73db1495de11fd46c104f
giturlparse==0.10.0; python_version >= "3.6" \ giturlparse==0.10.0; python_version >= "3.6" \
--hash=sha256:04ba1a3a099c3093fa8d24a422913c6a9b2c2cd22bcffc939cf72e3e98f672d7 \ --hash=sha256:04ba1a3a099c3093fa8d24a422913c6a9b2c2cd22bcffc939cf72e3e98f672d7 \
--hash=sha256:2595ab291d30717cda8474b874c9fd509f1b9802ad7f6968c36a45e4b13eb337 --hash=sha256:2595ab291d30717cda8474b874c9fd509f1b9802ad7f6968c36a45e4b13eb337
glean-parser==6.1.2 \ glean-parser==6.1.2 \
--hash=sha256:e801af6463b7e0ba79d97ddfc0a58d9d71121c93cea601417571e33fa8142270 \ --hash=sha256:e801af6463b7e0ba79d97ddfc0a58d9d71121c93cea601417571e33fa8142270 \
--hash=sha256:12a0fecedc1144d77fa571e0422ff3fea4dbadc381d631bea800a6b2f58f4f7f --hash=sha256:12a0fecedc1144d77fa571e0422ff3fea4dbadc381d631bea800a6b2f58f4f7f
idna-ssl==1.1.0; python_version < "3.7" and python_version >= "3.6" \ idna-ssl==1.1.0; python_version < "3.7" and python_version >= "3.6" \
--hash=sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c --hash=sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c
idna==2.10; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.7" or python_version < "3.7" and python_version >= "3.6" and python_full_version >= "3.4.0" or python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" \ idna==2.10; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.7" or python_version < "3.7" and python_version >= "3.6" and python_full_version >= "3.4.0" or python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" \
--hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 \ --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 \
--hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6
importlib-metadata==1.7.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") \ importlib-metadata==1.7.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") \
--hash=sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070 \ --hash=sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070 \
--hash=sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83 --hash=sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83
iso8601==0.1.14; python_version <= "3.6" \ iso8601==0.1.14; python_version <= "3.6" \
--hash=sha256:e7e1122f064d626e17d47cd5106bed2c620cb38fe464999e0ddae2b6d2de6004 \ --hash=sha256:e7e1122f064d626e17d47cd5106bed2c620cb38fe464999e0ddae2b6d2de6004 \
--hash=sha256:8aafd56fa0290496c5edbb13c311f78fa3a241f0853540da09d9363eae3ebd79 --hash=sha256:8aafd56fa0290496c5edbb13c311f78fa3a241f0853540da09d9363eae3ebd79
jinja2==2.11.3; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" \ jinja2==2.11.3; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" \
--hash=sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419 \ --hash=sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419 \
--hash=sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6 --hash=sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6
jsmin==2.1.0 \ jsmin==2.1.0 \
--hash=sha256:5d07bf0251a4128e5e8e8eef603849b6b5741c337bff087731a248f9cc774f56 --hash=sha256:5d07bf0251a4128e5e8e8eef603849b6b5741c337bff087731a248f9cc774f56
json-e==2.7.0 \ json-e==2.7.0 \
--hash=sha256:d8c1ec3f5bbc7728c3a504ebe58829f283c64eca230871e4eefe974b4cdaae4a --hash=sha256:d8c1ec3f5bbc7728c3a504ebe58829f283c64eca230871e4eefe974b4cdaae4a
jsonschema==3.2.0 \ jsonschema==3.2.0 \
--hash=sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163 \ --hash=sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163 \
--hash=sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a --hash=sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a
looseversion==1.0.1; python_version >= "3" \ looseversion==1.0.1; python_version >= "3" \
--hash=sha256:a205beabd0ffd40488edb9ccb3a39134510fc7c0c2847a25079f559e59c004ac \ --hash=sha256:a205beabd0ffd40488edb9ccb3a39134510fc7c0c2847a25079f559e59c004ac \
--hash=sha256:b339dfde67680e9c5c2e96673e52bee9f94d2f0e1b8f4cbfd86d32311e86b952 --hash=sha256:b339dfde67680e9c5c2e96673e52bee9f94d2f0e1b8f4cbfd86d32311e86b952
markupsafe==1.1.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" or python_full_version >= "3.5.0" \ markupsafe==1.1.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" or python_full_version >= "3.5.0" \
--hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \ --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \
--hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \
--hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \ --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \
--hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \ --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \
--hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \ --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \
--hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \
--hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \ --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \
--hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \ --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \
--hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \ --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \
--hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \ --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \
--hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \ --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \
--hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \ --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \
--hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \ --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \
--hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \ --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \
--hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \ --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \
--hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \ --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \
--hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \ --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \
--hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \ --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \
--hash=sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5 \ --hash=sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5 \
--hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \
--hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \ --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \
--hash=sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f \ --hash=sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f \
--hash=sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0 \ --hash=sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0 \
--hash=sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7 \ --hash=sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7 \
--hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \ --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \
--hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \ --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \
--hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \ --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \
--hash=sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193 \ --hash=sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193 \
--hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \ --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \
--hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \
--hash=sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1 \ --hash=sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1 \
--hash=sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1 \ --hash=sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1 \
--hash=sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f \ --hash=sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f \
--hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \ --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \
--hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \ --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \
--hash=sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15 \ --hash=sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15 \
--hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \ --hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \
--hash=sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42 \ --hash=sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42 \
--hash=sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2 \ --hash=sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2 \
--hash=sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032 \ --hash=sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032 \
--hash=sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b \ --hash=sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b \
--hash=sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b \ --hash=sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b \
--hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be \ --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be \
--hash=sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c \ --hash=sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c \
--hash=sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb \ --hash=sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb \
--hash=sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014 \ --hash=sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014 \
--hash=sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850 \ --hash=sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850 \
--hash=sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85 \ --hash=sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85 \
--hash=sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621 \ --hash=sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621 \
--hash=sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39 \ --hash=sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39 \
--hash=sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8 \ --hash=sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8 \
--hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b
mohawk==0.3.4 \ mohawk==0.3.4 \
--hash=sha256:b3f85ffa93a5c7d2f9cc591246ef9f8ac4a9fa716bfd5bae0377699a2d89d78c \ --hash=sha256:b3f85ffa93a5c7d2f9cc591246ef9f8ac4a9fa716bfd5bae0377699a2d89d78c \
--hash=sha256:e98b331d9fa9ece7b8be26094cbe2d57613ae882133cc755167268a984bc0ab3 --hash=sha256:e98b331d9fa9ece7b8be26094cbe2d57613ae882133cc755167268a984bc0ab3
mozilla-repo-urls==0.1.0 \ mozilla-repo-urls==0.1.0 \
--hash=sha256:aa43ebcc4744b4cf20bf27f8dff885e82efee21a0219ba20f6bd99931cabd7b9 \ --hash=sha256:aa43ebcc4744b4cf20bf27f8dff885e82efee21a0219ba20f6bd99931cabd7b9 \
--hash=sha256:5978abd796ae2b51a66e571754f0c559050cb4a024f2bf401471fa7ac4afd54e --hash=sha256:5978abd796ae2b51a66e571754f0c559050cb4a024f2bf401471fa7ac4afd54e
mozilla-version==0.3.4 \ mozilla-version==0.3.4 \
--hash=sha256:3ed4deb7a6fb25c83a5346ef4de08ddff9b2ddc4d16dd8fafb4a84978cc71255 \ --hash=sha256:3ed4deb7a6fb25c83a5346ef4de08ddff9b2ddc4d16dd8fafb4a84978cc71255 \
--hash=sha256:ce5741c2e7d12c30b53de9f79e30d6ac2a8bd4c93be711d30c7a7a08e32a094f --hash=sha256:ce5741c2e7d12c30b53de9f79e30d6ac2a8bd4c93be711d30c7a7a08e32a094f
multidict==5.1.0; python_version >= "3.6" \ multidict==5.1.0; python_version >= "3.6" \
--hash=sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f \ --hash=sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f \
--hash=sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf \ --hash=sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf \
--hash=sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281 \ --hash=sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281 \
--hash=sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d \ --hash=sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d \
--hash=sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d \ --hash=sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d \
--hash=sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da \ --hash=sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da \
--hash=sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224 \ --hash=sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224 \
--hash=sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26 \ --hash=sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26 \
--hash=sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6 \ --hash=sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6 \
--hash=sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76 \ --hash=sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76 \
--hash=sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a \ --hash=sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a \
--hash=sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f \ --hash=sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f \
--hash=sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348 \ --hash=sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348 \
--hash=sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93 \ --hash=sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93 \
--hash=sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9 \ --hash=sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9 \
--hash=sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37 \ --hash=sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37 \
--hash=sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5 \ --hash=sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5 \
--hash=sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632 \ --hash=sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632 \
--hash=sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952 \ --hash=sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952 \
--hash=sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79 \ --hash=sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79 \
--hash=sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456 \ --hash=sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456 \
--hash=sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7 \ --hash=sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7 \
--hash=sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635 \ --hash=sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635 \
--hash=sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a \ --hash=sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a \
--hash=sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea \ --hash=sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea \
--hash=sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656 \ --hash=sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656 \
--hash=sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3 \ --hash=sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3 \
--hash=sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93 \ --hash=sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93 \
--hash=sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647 \ --hash=sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647 \
--hash=sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d \ --hash=sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d \
--hash=sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8 \ --hash=sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8 \
--hash=sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1 \ --hash=sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1 \
--hash=sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841 \ --hash=sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841 \
--hash=sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda \ --hash=sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda \
--hash=sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80 \ --hash=sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80 \
--hash=sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359 \ --hash=sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359 \
--hash=sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5 --hash=sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5
packaging==20.9; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \ packaging==20.9; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \
--hash=sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a \ --hash=sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a \
--hash=sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5 --hash=sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5
pathspec==0.9.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") \ pathspec==0.9.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") \
--hash=sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a \ --hash=sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a \
--hash=sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1 --hash=sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1
pip-tools==5.5.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") \ pip-tools==5.5.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") \
--hash=sha256:cb0108391366b3ef336185097b3c2c0f3fa115b15098dafbda5e78aef70ea114 \ --hash=sha256:cb0108391366b3ef336185097b3c2c0f3fa115b15098dafbda5e78aef70ea114 \
--hash=sha256:10841c1e56c234d610d0466447685b9ea4ee4a2c274f858c0ef3c33d9bd0d985 --hash=sha256:10841c1e56c234d610d0466447685b9ea4ee4a2c274f858c0ef3c33d9bd0d985
pip==21.2.4; python_version >= "3.6" \ pip==21.2.4; python_version >= "3.6" \
--hash=sha256:fa9ebb85d3fd607617c0c44aca302b1b45d87f9c2a1649b46c26167ca4296323 \ --hash=sha256:fa9ebb85d3fd607617c0c44aca302b1b45d87f9c2a1649b46c26167ca4296323 \
--hash=sha256:0eb8a1516c3d138ae8689c0c1a60fde7143310832f9dc77e11d8a4bc62de193b --hash=sha256:0eb8a1516c3d138ae8689c0c1a60fde7143310832f9dc77e11d8a4bc62de193b
ply==3.10 \ ply==3.10 \
--hash=sha256:96e94af7dd7031d8d6dd6e2a8e0de593b511c211a86e28a9c9621c275ac8bacb --hash=sha256:96e94af7dd7031d8d6dd6e2a8e0de593b511c211a86e28a9c9621c275ac8bacb
pyasn1-modules==0.2.8 \ pyasn1-modules==0.2.8 \
--hash=sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e \ --hash=sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e \
--hash=sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199 \ --hash=sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199 \
--hash=sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405 \ --hash=sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405 \
--hash=sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb \ --hash=sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb \
--hash=sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8 \ --hash=sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8 \
--hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74 \ --hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74 \
--hash=sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d \ --hash=sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d \
--hash=sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45 \ --hash=sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45 \
--hash=sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4 \ --hash=sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4 \
--hash=sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811 \ --hash=sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811 \
--hash=sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed \ --hash=sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed \
--hash=sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0 \ --hash=sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0 \
--hash=sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd --hash=sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd
pyasn1==0.4.8 \ pyasn1==0.4.8 \
--hash=sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3 \ --hash=sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3 \
--hash=sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf \ --hash=sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf \
--hash=sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00 \ --hash=sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00 \
--hash=sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8 \ --hash=sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8 \
--hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \ --hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \
--hash=sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86 \ --hash=sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86 \
--hash=sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7 \ --hash=sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7 \
--hash=sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576 \ --hash=sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576 \
--hash=sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12 \ --hash=sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12 \
--hash=sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2 \ --hash=sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2 \
--hash=sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359 \ --hash=sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359 \
--hash=sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776 \ --hash=sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776 \
--hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba --hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba
pylru==1.0.9 \ pylru==1.0.9 \
--hash=sha256:71376192671f0ad1690b2a7427d39a29b1df994c8469a9b46b03ed7e28c0172c --hash=sha256:71376192671f0ad1690b2a7427d39a29b1df994c8469a9b46b03ed7e28c0172c
pyparsing==2.4.7; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" \ pyparsing==2.4.7; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" \
--hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b \
--hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1
pyrsistent==0.16.0 \ pyrsistent==0.16.0 \
--hash=sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3 --hash=sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3
python-hglib==2.4 \ python-hglib==2.4 \
--hash=sha256:693d6ed92a6566e78802c7a03c256cda33d08c63ad3f00fcfa11379b184b9462 --hash=sha256:693d6ed92a6566e78802c7a03c256cda33d08c63ad3f00fcfa11379b184b9462
pytoml==0.1.10 \ pytoml==0.1.10 \
--hash=sha256:98399eabd927cd3e12457525315b6abbc5abf9a6f392ab578cbcec327f73890c --hash=sha256:98399eabd927cd3e12457525315b6abbc5abf9a6f392ab578cbcec327f73890c
pyyaml==5.4.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0") \ pyyaml==5.4.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0") \
--hash=sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922 \ --hash=sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922 \
--hash=sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393 \ --hash=sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393 \
--hash=sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8 \ --hash=sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8 \
--hash=sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185 \ --hash=sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185 \
--hash=sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253 \ --hash=sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253 \
--hash=sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc \ --hash=sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc \
--hash=sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347 \ --hash=sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347 \
--hash=sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541 \ --hash=sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541 \
--hash=sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5 \ --hash=sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5 \
--hash=sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df \ --hash=sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df \
--hash=sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018 \ --hash=sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018 \
--hash=sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63 \ --hash=sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63 \
--hash=sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa \ --hash=sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa \
--hash=sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0 \ --hash=sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0 \
--hash=sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b \ --hash=sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b \
--hash=sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf \ --hash=sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf \
--hash=sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46 \ --hash=sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46 \
--hash=sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb \ --hash=sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb \
--hash=sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247 \ --hash=sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247 \
--hash=sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc \ --hash=sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc \
--hash=sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc \ --hash=sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc \
--hash=sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696 \ --hash=sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696 \
--hash=sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77 \ --hash=sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77 \
--hash=sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183 \ --hash=sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183 \
--hash=sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122 \ --hash=sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122 \
--hash=sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6 \ --hash=sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6 \
--hash=sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10 \ --hash=sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10 \
--hash=sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db \ --hash=sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db \
--hash=sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e --hash=sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e
redo==2.0.3 \ redo==2.0.3 \
--hash=sha256:36784bf8ae766e14f9db0e377ccfa02835d648321d2007b6ae0bf4fd612c0f94 \ --hash=sha256:36784bf8ae766e14f9db0e377ccfa02835d648321d2007b6ae0bf4fd612c0f94 \
--hash=sha256:71161cb0e928d824092a5f16203939bbc0867ce4c4685db263cf22c3ae7634a8 --hash=sha256:71161cb0e928d824092a5f16203939bbc0867ce4c4685db263cf22c3ae7634a8
requests-unixsocket==0.2.0 \ requests-unixsocket==0.2.0 \
--hash=sha256:9e5c1a20afc3cf786197ae59c79bcdb0e7565f218f27df5f891307ee8817c1ea \ --hash=sha256:9e5c1a20afc3cf786197ae59c79bcdb0e7565f218f27df5f891307ee8817c1ea \
--hash=sha256:014d07bfb66dc805a011a8b4b306cf4ec96d2eddb589f6b2b5765e626f0dc0cc --hash=sha256:014d07bfb66dc805a011a8b4b306cf4ec96d2eddb589f6b2b5765e626f0dc0cc
requests==2.25.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") \ requests==2.25.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") \
--hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e \ --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e \
--hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804
responses==0.10.6; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \ responses==0.10.6; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \
--hash=sha256:97193c0183d63fba8cd3a041c75464e4b09ea0aff6328800d1546598567dde0b \ --hash=sha256:97193c0183d63fba8cd3a041c75464e4b09ea0aff6328800d1546598567dde0b \
--hash=sha256:502d9c0c8008439cfcdef7e251f507fcfdd503b56e8c0c87c3c3e3393953f790 --hash=sha256:502d9c0c8008439cfcdef7e251f507fcfdd503b56e8c0c87c3c3e3393953f790
rsa==3.1.4 \ rsa==3.1.4 \
--hash=sha256:e2b0b05936c276b1edd2e1525553233b666df9e29b5c3ba223eed738277c82a0 --hash=sha256:e2b0b05936c276b1edd2e1525553233b666df9e29b5c3ba223eed738277c82a0
sentry-sdk==0.14.3 \ sentry-sdk==0.14.3 \
--hash=sha256:bb90a4e19c7233a580715fc986cc44be2c48fc10b31e71580a2037e1c94b6950 \ --hash=sha256:bb90a4e19c7233a580715fc986cc44be2c48fc10b31e71580a2037e1c94b6950 \
--hash=sha256:23808d571d2461a4ce3784ec12bbee5bdb8c026c143fe79d36cef8a6d653e71f --hash=sha256:23808d571d2461a4ce3784ec12bbee5bdb8c026c143fe79d36cef8a6d653e71f
setuptools==51.2.0; python_version >= "3.6" \ setuptools==51.2.0; python_version >= "3.6" \
--hash=sha256:56948bf25c682e166cf2bfe7c1ad63e5745849b50d1ae7b0f8bff5decdcf34f2 \ --hash=sha256:56948bf25c682e166cf2bfe7c1ad63e5745849b50d1ae7b0f8bff5decdcf34f2 \
--hash=sha256:7ef59b1790b3491f8d321f531eccc11517a07a4d7637e498465cd834d80d4c2c --hash=sha256:7ef59b1790b3491f8d321f531eccc11517a07a4d7637e498465cd834d80d4c2c
six==1.13.0; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.2.0") \ six==1.13.0; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.2.0") \
--hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \
--hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66
slugid==2.0.0 \ slugid==2.0.0 \
--hash=sha256:aec8b0e01c4ad32e38e12d609eab3ec912fd129aaf6b2ded0199b56a5f8fd67c \ --hash=sha256:aec8b0e01c4ad32e38e12d609eab3ec912fd129aaf6b2ded0199b56a5f8fd67c \
--hash=sha256:a950d98b72691178bdd4d6c52743c4a2aa039207cf7a97d71060a111ff9ba297 --hash=sha256:a950d98b72691178bdd4d6c52743c4a2aa039207cf7a97d71060a111ff9ba297
taskcluster-taskgraph==3.2.1 \ taskcluster-taskgraph==3.5.1 \
--hash=sha256:87498ce08c5d2bfe0fd0b1a860e3dc2b9eba4d7acb883e9e5c2b6f7f15281a34 \ --hash=sha256:e08b935175349ef8728ff5f19c7e9866a562256180f5580b291da3217cb5016c \
--hash=sha256:c638724f0d514a3fc2d6ba34ddd395cfe021312dcf78b01c789ec4c7bf068cf0 --hash=sha256:dc56b87228fb8eb1ef611750202344817a8cf5d825c0dc7e2dcc0f8b2795cbcd
taskcluster-urls==13.0.1 \ taskcluster-urls==13.0.1 \
--hash=sha256:b25e122ecec249c4299ac7b20b08db76e3e2025bdaeb699a9d444556de5fd367 \ --hash=sha256:b25e122ecec249c4299ac7b20b08db76e3e2025bdaeb699a9d444556de5fd367 \
--hash=sha256:5e25e7e6818e8877178b175ff43d2e6548afad72694aa125f404a7329ece0973 \ --hash=sha256:5e25e7e6818e8877178b175ff43d2e6548afad72694aa125f404a7329ece0973 \
--hash=sha256:f66dcbd6572a6216ab65949f0fa0b91f2df647918028436c384e6af5cd12ae2b --hash=sha256:f66dcbd6572a6216ab65949f0fa0b91f2df647918028436c384e6af5cd12ae2b
taskcluster==44.2.2 \ taskcluster==44.2.2 \
--hash=sha256:c1b0e82be25b1ed17e07c90b24a382634b2bfce273fdf2682d94568abe10716c \ --hash=sha256:c1b0e82be25b1ed17e07c90b24a382634b2bfce273fdf2682d94568abe10716c \
--hash=sha256:846d73c597f0f47dd8525c85c8d9bc41111d5200b090690d3f16b2f57c56a2e1 \ --hash=sha256:846d73c597f0f47dd8525c85c8d9bc41111d5200b090690d3f16b2f57c56a2e1 \
--hash=sha256:0266a6a901e1a2ec838984a7f24e7adb6d58f9f2e221a7f613388f8f23f786fc --hash=sha256:0266a6a901e1a2ec838984a7f24e7adb6d58f9f2e221a7f613388f8f23f786fc
tqdm==4.62.3; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \ tqdm==4.62.3; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \
--hash=sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c \ --hash=sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c \
--hash=sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d --hash=sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d
typing-extensions==3.10.0.0; python_version < "3.8" and python_version >= "3.6" or python_version >= "3.6" \ typing-extensions==3.10.0.0; python_version < "3.8" and python_version >= "3.6" or python_version >= "3.6" \
--hash=sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497 \ --hash=sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497 \
--hash=sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84 \ --hash=sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84 \
--hash=sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342 --hash=sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342
urllib3==1.26.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0" and python_version < "4") \ urllib3==1.26.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0" and python_version < "4") \
--hash=sha256:bad31cb622ceee0ab46c4c884cf61957def0ff2e644de0a7a093678844c9ccac \ --hash=sha256:bad31cb622ceee0ab46c4c884cf61957def0ff2e644de0a7a093678844c9ccac \
--hash=sha256:4849f132941d68144df0a3785ccc4fe423430ba5db0108d045c8cadbc90f517a --hash=sha256:4849f132941d68144df0a3785ccc4fe423430ba5db0108d045c8cadbc90f517a
voluptuous==0.12.1 \ voluptuous==0.12.1 \
--hash=sha256:8ace33fcf9e6b1f59406bfaf6b8ec7bcc44266a9f29080b4deb4fe6ff2492386 \ --hash=sha256:8ace33fcf9e6b1f59406bfaf6b8ec7bcc44266a9f29080b4deb4fe6ff2492386 \
--hash=sha256:663572419281ddfaf4b4197fd4942d181630120fb39b333e3adad70aeb56444b --hash=sha256:663572419281ddfaf4b4197fd4942d181630120fb39b333e3adad70aeb56444b
wheel==0.37.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") \ wheel==0.37.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") \
--hash=sha256:21014b2bd93c6d0034b6ba5d35e4eb284340e09d63c59aef6fc14b0f346146fd \ --hash=sha256:21014b2bd93c6d0034b6ba5d35e4eb284340e09d63c59aef6fc14b0f346146fd \
--hash=sha256:e2ef7239991699e3355d54f8e968a21bb940a1dbf34a4d226741e64462516fad --hash=sha256:e2ef7239991699e3355d54f8e968a21bb940a1dbf34a4d226741e64462516fad
yamllint==1.23.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \ yamllint==1.23.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \
--hash=sha256:0fa69bf8a86182b7fe14918bdd3a30354c869966bbc7cbfff176af71bda9c806 \ --hash=sha256:0fa69bf8a86182b7fe14918bdd3a30354c869966bbc7cbfff176af71bda9c806 \
--hash=sha256:59f3ff77f44e7f46be6aecdb985830f73a1c51e290b7082a7d38c2ae1940f4a9 --hash=sha256:59f3ff77f44e7f46be6aecdb985830f73a1c51e290b7082a7d38c2ae1940f4a9
yarl==1.6.3; python_version >= "3.6" \ yarl==1.6.3; python_version >= "3.6" \
--hash=sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434 \ --hash=sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434 \
--hash=sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478 \ --hash=sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478 \
--hash=sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6 \ --hash=sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6 \
--hash=sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e \ --hash=sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e \
--hash=sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406 \ --hash=sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406 \
--hash=sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76 \ --hash=sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76 \
--hash=sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366 \ --hash=sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366 \
--hash=sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721 \ --hash=sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721 \
--hash=sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643 \ --hash=sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643 \
--hash=sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e \ --hash=sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e \
--hash=sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3 \ --hash=sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3 \
--hash=sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8 \ --hash=sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8 \
--hash=sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a \ --hash=sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a \
--hash=sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c \ --hash=sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c \
--hash=sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f \ --hash=sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f \
--hash=sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970 \ --hash=sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970 \
--hash=sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e \ --hash=sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e \
--hash=sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50 \ --hash=sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50 \
--hash=sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2 \ --hash=sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2 \
--hash=sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec \ --hash=sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec \
--hash=sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71 \ --hash=sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71 \
--hash=sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc \ --hash=sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc \
--hash=sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959 \ --hash=sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959 \
--hash=sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2 \ --hash=sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2 \
--hash=sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2 \ --hash=sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2 \
--hash=sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896 \ --hash=sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896 \
--hash=sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a \ --hash=sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a \
--hash=sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e \ --hash=sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e \
--hash=sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724 \ --hash=sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724 \
--hash=sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c \ --hash=sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c \
--hash=sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25 \ --hash=sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25 \
--hash=sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96 \ --hash=sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96 \
--hash=sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0 \ --hash=sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0 \
--hash=sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4 \ --hash=sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4 \
--hash=sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424 \ --hash=sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424 \
--hash=sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6 \ --hash=sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6 \
--hash=sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10 --hash=sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10
zipp==3.4.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.5.0" and python_version < "3.8" and python_version >= "3.6" \ zipp==3.4.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.5.0" and python_version < "3.8" and python_version >= "3.6" \
--hash=sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098 \ --hash=sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098 \
--hash=sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76 --hash=sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76

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

@ -1,6 +1,6 @@
Metadata-Version: 2.1 Metadata-Version: 2.1
Name: taskcluster-taskgraph Name: taskcluster-taskgraph
Version: 3.2.1 Version: 3.5.1
Summary: Build taskcluster taskgraphs Summary: Build taskcluster taskgraphs
Home-page: https://github.com/taskcluster/taskgraph Home-page: https://github.com/taskcluster/taskgraph
License: UNKNOWN License: UNKNOWN
@ -26,6 +26,8 @@ Requires-Dist: requests-unixsocket (>=0.2)
Requires-Dist: slugid (>=2.0) Requires-Dist: slugid (>=2.0)
Requires-Dist: taskcluster-urls (>=11.0) Requires-Dist: taskcluster-urls (>=11.0)
Requires-Dist: voluptuous (>=0.12.1) Requires-Dist: voluptuous (>=0.12.1)
Provides-Extra: load-image
Requires-Dist: zstandard ; extra == 'load-image'
UNKNOWN UNKNOWN

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

@ -1,15 +1,16 @@
taskgraph/__init__.py,sha256=jwOtU7TkmU317LP_IsgIswpj2T1OPUXXgMRv4sIU7nE,707 taskgraph/__init__.py,sha256=jwOtU7TkmU317LP_IsgIswpj2T1OPUXXgMRv4sIU7nE,707
taskgraph/config.py,sha256=MoFLjKPUViWYGALi_acWDVXZs7M8cy0zQpUKsJSlBMs,4411 taskgraph/config.py,sha256=MoFLjKPUViWYGALi_acWDVXZs7M8cy0zQpUKsJSlBMs,4411
taskgraph/create.py,sha256=1z2AyLvHMkZfDkmPy6um86HG9xTRhE0Sphnbpd-kuEg,5190 taskgraph/create.py,sha256=1z2AyLvHMkZfDkmPy6um86HG9xTRhE0Sphnbpd-kuEg,5190
taskgraph/decision.py,sha256=X94bfSp6LyYkO7hpi4A0ytWSfHl9YtkRLNaJR8loAWQ,12758 taskgraph/decision.py,sha256=ApfQeXumRH7uq55DLt7gjQCh_eKls6lPhnNaH2ZpR-0,12849
taskgraph/docker.py,sha256=hsMIvRVXiqC8DIGD34WwQrC1JnjaYHSvVWq_lEeNQEE,7471 taskgraph/docker.py,sha256=dB282jKjfLnHwL73YSg1Eeqj-ojHQc676vEpWt4PjVw,7835
taskgraph/files_changed.py,sha256=W3_gEgUT-mVH9DaaU_8X6gYpftrqBU3kgveGbzPLziU,2793 taskgraph/files_changed.py,sha256=W3_gEgUT-mVH9DaaU_8X6gYpftrqBU3kgveGbzPLziU,2793
taskgraph/filter_tasks.py,sha256=R7tYXiaVPGIkQ6O1c9-QJrKZ59m9pFXCloUlPraVnZU,866 taskgraph/filter_tasks.py,sha256=R7tYXiaVPGIkQ6O1c9-QJrKZ59m9pFXCloUlPraVnZU,866
taskgraph/generator.py,sha256=ZfSb8dek6tQRxfpHbvQP2KMxXFzmhqwN821tOlNcvzo,15118 taskgraph/generator.py,sha256=tonQ3UvaZYRdpWOtmdQ5Mr4en1FRCUJvbvlbzfChluM,15590
taskgraph/graph.py,sha256=9tE3bSSBRHvRLgJzK4dTieGT3RrzQZdR1YbKizEhzlw,4667 taskgraph/graph.py,sha256=9tE3bSSBRHvRLgJzK4dTieGT3RrzQZdR1YbKizEhzlw,4667
taskgraph/main.py,sha256=E7dC1q14L4psrNfUe-PMC8QH4cYjsIs91I-aVmzeBaI,23551 taskgraph/main.py,sha256=rb7cwghT5U97kSpIho0KzXo4HSXp2Iw_jaL2A2Qrf18,23581
taskgraph/morph.py,sha256=8qxYdruEQkbHGqv7dh3e1OWhH9Y5i6bFUKzDMs-Ctnw,9625 taskgraph/morph.py,sha256=8qxYdruEQkbHGqv7dh3e1OWhH9Y5i6bFUKzDMs-Ctnw,9625
taskgraph/parameters.py,sha256=8556WayG8J-3w_DZTjF--VKd7Czuaxng1Zl3Cvdz5eg,11644 taskgraph/optimize.py,sha256=NVshvkqRKr7SQvRdqz5CELmnIXeiODkDxlK0D9QMi9k,16487
taskgraph/parameters.py,sha256=CYaR9E6pFsysUcRahlFILplEy3unVwUu7scLhP03nQo,11824
taskgraph/target_tasks.py,sha256=41BIVwiATy8DCQujPduTtnFmgHlKOfw6RPGL4b20WO8,3324 taskgraph/target_tasks.py,sha256=41BIVwiATy8DCQujPduTtnFmgHlKOfw6RPGL4b20WO8,3324
taskgraph/task.py,sha256=QCrOzMaTsy5QHShKUo89XgjJVMl3cSZGZJPLuHCXItE,3132 taskgraph/task.py,sha256=QCrOzMaTsy5QHShKUo89XgjJVMl3cSZGZJPLuHCXItE,3132
taskgraph/taskgraph.py,sha256=tfj0ZMqjuwEQDET0W57EcP-_KBEbqkxJci9Z6DkeOEQ,2397 taskgraph/taskgraph.py,sha256=tfj0ZMqjuwEQDET0W57EcP-_KBEbqkxJci9Z6DkeOEQ,2397
@ -25,22 +26,22 @@ taskgraph/loader/transform.py,sha256=olUBPjxk3eEIg25sduxlcyqhjoig4ts5kPlT_zs6g9g
taskgraph/optimize/__init__.py,sha256=Oqpq1RW8QzOcu7zaMlNQ3BHT9ws9e_93FWfCqzNcQps,123 taskgraph/optimize/__init__.py,sha256=Oqpq1RW8QzOcu7zaMlNQ3BHT9ws9e_93FWfCqzNcQps,123
taskgraph/optimize/base.py,sha256=WvoDNewyHG46IQbG3th-aau9OxSKegsYNfvdOEmunbA,18341 taskgraph/optimize/base.py,sha256=WvoDNewyHG46IQbG3th-aau9OxSKegsYNfvdOEmunbA,18341
taskgraph/optimize/strategies.py,sha256=Y5fS-f_3xsQNfFjCXIwDxrwXBvyp4yZxdPVNh49c7XU,2381 taskgraph/optimize/strategies.py,sha256=Y5fS-f_3xsQNfFjCXIwDxrwXBvyp4yZxdPVNh49c7XU,2381
taskgraph/run-task/fetch-content,sha256=uUoyua3OdIgynY5Q9K6EojBwuaM2zo2OiN9bmNS646Q,24291 taskgraph/run-task/fetch-content,sha256=z3kx-vxaaaAmfqW-JW7dPKIFpjnxdZiXMdpPj1jAG8M,29915
taskgraph/run-task/hgrc,sha256=BybWLDR89bWi3pE5T05UqmDHs02CbLypE-omLZWU6Uk,896 taskgraph/run-task/hgrc,sha256=BybWLDR89bWi3pE5T05UqmDHs02CbLypE-omLZWU6Uk,896
taskgraph/run-task/robustcheckout.py,sha256=xc24zaBd6dyuoga1ace0M27jo14K4UXNwhqcbHutJ7U,28977 taskgraph/run-task/robustcheckout.py,sha256=tZi_FRGFhX27fspaUj2RGsMCmkwn8IfpRiSsPOrGfXQ,29802
taskgraph/run-task/run-task,sha256=76p0Zo19a6f4NkwTq8s9y4Emt3YW6Q-VdTInlcqjPjo,46956 taskgraph/run-task/run-task,sha256=zT83gWFaB0qBWdxCLxOVHiMdq1bmSmi90FjXjcegfpk,43584
taskgraph/transforms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 taskgraph/transforms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
taskgraph/transforms/base.py,sha256=N9ec4kw65V_J2KY4C4QRPlbIREbRDYwTlhClstYmOBU,5285 taskgraph/transforms/base.py,sha256=N9ec4kw65V_J2KY4C4QRPlbIREbRDYwTlhClstYmOBU,5285
taskgraph/transforms/cached_tasks.py,sha256=Z10VD1kEBVXJvj8qSsNTq2mYpklh0V1EN8OT6QK3v_E,2607 taskgraph/transforms/cached_tasks.py,sha256=Z10VD1kEBVXJvj8qSsNTq2mYpklh0V1EN8OT6QK3v_E,2607
taskgraph/transforms/code_review.py,sha256=eE2xrDtdD_n3HT3caQ2HGAkPm6Uutdm4hDCpCoFjEps,707 taskgraph/transforms/code_review.py,sha256=eE2xrDtdD_n3HT3caQ2HGAkPm6Uutdm4hDCpCoFjEps,707
taskgraph/transforms/docker_image.py,sha256=ADiOUB-Ngm9Y6uwzGDpQsDJ_-4w6-ZYwLCxQ-0b16E0,7567 taskgraph/transforms/docker_image.py,sha256=ADiOUB-Ngm9Y6uwzGDpQsDJ_-4w6-ZYwLCxQ-0b16E0,7567
taskgraph/transforms/fetch.py,sha256=jxJw7wlEh_WxAa1Bmy2WIHfpdvL79PDsKwC1DFymbBQ,9584 taskgraph/transforms/fetch.py,sha256=Q7Co4wdBKL6Tr3Uc-eitJ3NGgGUYmRXNLuC5m-59-M8,10443
taskgraph/transforms/release_notifications.py,sha256=jrb9CCT-z_etDf690T-AeCvdzIoVWBAeM_FGoW7FIzA,3305 taskgraph/transforms/release_notifications.py,sha256=jrb9CCT-z_etDf690T-AeCvdzIoVWBAeM_FGoW7FIzA,3305
taskgraph/transforms/task.py,sha256=kWic-qqvK8vEFxQwojRPxc42GAsdkxoV3HVcG1pdBxE,47942 taskgraph/transforms/task.py,sha256=fBiSCyC0Lzd2GDSZ_QwhQ1RRebXLmkw4ZCPte9fwEL8,48212
taskgraph/transforms/job/__init__.py,sha256=GKYODycxov7u05owF_ZWgczd7WHi2yHTd8L5Ftvxge0,16929 taskgraph/transforms/job/__init__.py,sha256=ayAytoDmlmNvJNArJc-_nBz1Xuc191rZdbobUgp9hQA,17192
taskgraph/transforms/job/common.py,sha256=onHnerPcmmvbSk0oHt8mvJmOo7AnjHQya0ombgMNLG8,7106 taskgraph/transforms/job/common.py,sha256=XtKSxUCwRYqpPgRTyLD_8JGRuJs2JYuR0RXpTarPdTE,6826
taskgraph/transforms/job/index_search.py,sha256=Ngh9FFu1bx2kHVTChW2vcrbnb3SzMneRHopXk18RfB4,1220 taskgraph/transforms/job/index_search.py,sha256=Ngh9FFu1bx2kHVTChW2vcrbnb3SzMneRHopXk18RfB4,1220
taskgraph/transforms/job/run_task.py,sha256=oRR-is7dRKRrSCY3WntmJ-pKK3wx9-BMJpY9qru2FWY,8654 taskgraph/transforms/job/run_task.py,sha256=z5DqgHmmHYEbKtnpMQqcMY6ksgCnnoB7CugH3Z41Gag,8610
taskgraph/transforms/job/toolchain.py,sha256=WWsj6L_db9rJxzo26TdEf_0jcrK4MCoHHJDzFBkSFpI,5978 taskgraph/transforms/job/toolchain.py,sha256=WWsj6L_db9rJxzo26TdEf_0jcrK4MCoHHJDzFBkSFpI,5978
taskgraph/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 taskgraph/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
taskgraph/util/archive.py,sha256=nzYn8cQ3NfLAeV-2SuTNoeQ6hg8m40f6FQcSTyVIKwQ,2855 taskgraph/util/archive.py,sha256=nzYn8cQ3NfLAeV-2SuTNoeQ6hg8m40f6FQcSTyVIKwQ,2855
@ -66,9 +67,9 @@ taskgraph/util/vcs.py,sha256=i13idS8y9ooR216mnd1gksdjSgHBNlAZEdq7Xr-ROwE,18536
taskgraph/util/verify.py,sha256=YETuZVkwnfYe57GRPx2x_vedstgqdGiH46HLWAdcks8,8827 taskgraph/util/verify.py,sha256=YETuZVkwnfYe57GRPx2x_vedstgqdGiH46HLWAdcks8,8827
taskgraph/util/workertypes.py,sha256=5g2mgIbEKMzDpZNnmPMoMNyy7Wahi-jmWcV1amDAcPo,2341 taskgraph/util/workertypes.py,sha256=5g2mgIbEKMzDpZNnmPMoMNyy7Wahi-jmWcV1amDAcPo,2341
taskgraph/util/yaml.py,sha256=hfKI_D8Q7dimq4_VvO3WEh8CJsTrsIMwN6set7HIQbY,990 taskgraph/util/yaml.py,sha256=hfKI_D8Q7dimq4_VvO3WEh8CJsTrsIMwN6set7HIQbY,990
taskcluster_taskgraph-3.2.1.dist-info/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725 taskcluster_taskgraph-3.5.1.dist-info/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
taskcluster_taskgraph-3.2.1.dist-info/METADATA,sha256=ahNDmBrUgn48sWk5gx2bq4WMRmnUlDkC_E-wXC6Yglg,1050 taskcluster_taskgraph-3.5.1.dist-info/METADATA,sha256=uy5bE9DFHpqImbRhKEVM6CSC1me3wjdCW76836B0rEc,1126
taskcluster_taskgraph-3.2.1.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92 taskcluster_taskgraph-3.5.1.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
taskcluster_taskgraph-3.2.1.dist-info/entry_points.txt,sha256=VoXNtZpN4LvyXYB1wq47AU9CO-DMYMJ0VktKxjugzbY,51 taskcluster_taskgraph-3.5.1.dist-info/entry_points.txt,sha256=VoXNtZpN4LvyXYB1wq47AU9CO-DMYMJ0VktKxjugzbY,51
taskcluster_taskgraph-3.2.1.dist-info/top_level.txt,sha256=3JNeYn_hNiNXC7DrdH_vcv-WYSE7QdgGjdvUYvSjVp0,10 taskcluster_taskgraph-3.5.1.dist-info/top_level.txt,sha256=3JNeYn_hNiNXC7DrdH_vcv-WYSE7QdgGjdvUYvSjVp0,10
taskcluster_taskgraph-3.2.1.dist-info/RECORD,, taskcluster_taskgraph-3.5.1.dist-info/RECORD,,

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

@ -188,9 +188,11 @@ def get_decision_parameters(graph_config, options):
parameters["filters"] = [ parameters["filters"] = [
"target_tasks_method", "target_tasks_method",
] ]
parameters["optimize_strategies"] = None
parameters["optimize_target_tasks"] = True parameters["optimize_target_tasks"] = True
parameters["existing_tasks"] = {} parameters["existing_tasks"] = {}
parameters["do_not_optimize"] = [] parameters["do_not_optimize"] = []
parameters["enable_always_target"] = True
parameters["build_number"] = 1 parameters["build_number"] = 1
parameters["version"] = get_version(repo_path) parameters["version"] = get_version(repo_path)
parameters["next_version"] = None parameters["next_version"] = None
@ -224,13 +226,8 @@ def get_decision_parameters(graph_config, options):
# ..but can be overridden by the commit message: if it contains the special # ..but can be overridden by the commit message: if it contains the special
# string "DONTBUILD" and this is an on-push decision task, then use the # string "DONTBUILD" and this is an on-push decision task, then use the
# special 'nothing' target task method. (except on the toolchains project, # special 'nothing' target task method.
# where we ignore "DONTBUILD"). if "DONTBUILD" in commit_message and options["tasks_for"] == "hg-push":
if (
"DONTBUILD" in commit_message
and options["tasks_for"] == "hg-push"
and project != "toolchains"
):
parameters["target_tasks_method"] = "nothing" parameters["target_tasks_method"] = "nothing"
if options.get("optimize_target_tasks") is not None: if options.get("optimize_target_tasks") is not None:

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

@ -7,6 +7,12 @@ import json
import os import os
import tarfile import tarfile
from io import BytesIO from io import BytesIO
from textwrap import dedent
try:
import zstandard as zstd
except ImportError as e:
zstd = e
from taskgraph.util import docker from taskgraph.util import docker
from taskgraph.util.taskcluster import get_artifact_url, get_session from taskgraph.util.taskcluster import get_artifact_url, get_session
@ -115,7 +121,15 @@ def load_image(url, imageName=None, imageTag=None):
Returns an object with properties 'image', 'tag' and 'layer'. Returns an object with properties 'image', 'tag' and 'layer'.
""" """
import zstandard as zstd if isinstance(zstd, ImportError):
raise ImportError(
dedent(
"""
zstandard is not installed! Use `pip install taskcluster-taskgraph[load-image]`
to use this feature.
"""
)
) from zstd
# If imageName is given and we don't have an imageTag # If imageName is given and we don't have an imageTag
# we parse out the imageTag from imageName, or default it to 'latest' # we parse out the imageTag from imageName, or default it to 'latest'

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

@ -14,7 +14,7 @@ from .config import GraphConfig, load_graph_config
from .graph import Graph from .graph import Graph
from .morph import morph from .morph import morph
from .optimize.base import optimize_task_graph from .optimize.base import optimize_task_graph
from .parameters import Parameters from .parameters import parameters_loader
from .task import Task from .task import Task
from .taskgraph import TaskGraph from .taskgraph import TaskGraph
from .transforms.base import TransformConfig, TransformSequence from .transforms.base import TransformConfig, TransformSequence
@ -249,9 +249,6 @@ class TaskGraphGenerator:
continue continue
def _run(self): def _run(self):
# Initial verifications that don't depend on any generation state.
verifications("initial")
logger.info("Loading graph configuration.") logger.info("Loading graph configuration.")
graph_config = load_graph_config(self.root_dir) graph_config = load_graph_config(self.root_dir)
@ -259,6 +256,9 @@ class TaskGraphGenerator:
graph_config.register() graph_config.register()
# Initial verifications that don't depend on any generation state.
verifications("initial")
if callable(self._parameters): if callable(self._parameters):
parameters = self._parameters(graph_config) parameters = self._parameters(graph_config)
else: else:
@ -360,11 +360,14 @@ class TaskGraphGenerator:
if t.attributes["kind"] == "docker-image" if t.attributes["kind"] == "docker-image"
} }
# include all tasks with `always_target` set # include all tasks with `always_target` set
always_target_tasks = { if parameters["enable_always_target"]:
t.label always_target_tasks = {
for t in full_task_graph.tasks.values() t.label
if t.attributes.get("always_target") for t in full_task_graph.tasks.values()
} if t.attributes.get("always_target")
}
else:
always_target_tasks = set()
logger.info( logger.info(
"Adding %d tasks with `always_target` attribute" "Adding %d tasks with `always_target` attribute"
% (len(always_target_tasks) - len(always_target_tasks & target_tasks)) % (len(always_target_tasks) - len(always_target_tasks & target_tasks))
@ -383,6 +386,14 @@ class TaskGraphGenerator:
do_not_optimize = set(parameters.get("do_not_optimize", [])) do_not_optimize = set(parameters.get("do_not_optimize", []))
if not parameters.get("optimize_target_tasks", True): if not parameters.get("optimize_target_tasks", True):
do_not_optimize = set(target_task_set.graph.nodes).union(do_not_optimize) do_not_optimize = set(target_task_set.graph.nodes).union(do_not_optimize)
# this is used for testing experimental optimization strategies
strategies = os.environ.get(
"TASKGRAPH_OPTIMIZE_STRATEGIES", parameters.get("optimize_strategies")
)
if strategies:
strategies = find_object(strategies)
optimized_task_graph, label_to_taskid = optimize_task_graph( optimized_task_graph, label_to_taskid = optimize_task_graph(
target_task_graph, target_task_graph,
requested_tasks, requested_tasks,
@ -390,6 +401,7 @@ class TaskGraphGenerator:
do_not_optimize, do_not_optimize,
self._decision_task_id, self._decision_task_id,
existing_tasks=existing_tasks, existing_tasks=existing_tasks,
strategy_override=strategies,
) )
yield self.verify( yield self.verify(
@ -428,7 +440,7 @@ def load_tasks_for_kind(parameters, kind, root_dir=None):
# make parameters read-write # make parameters read-write
parameters = dict(parameters) parameters = dict(parameters)
parameters["target-kind"] = kind parameters["target-kind"] = kind
parameters = Parameters(strict=False, **parameters) parameters = parameters_loader(spec=None, strict=False, overrides=parameters)
tgg = TaskGraphGenerator(root_dir=root_dir, parameters=parameters) tgg = TaskGraphGenerator(root_dir=root_dir, parameters=parameters)
return { return {
task.task["metadata"]["name"]: task task.task["metadata"]["name"]: task

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

@ -47,7 +47,9 @@ def argument(*args, **kwargs):
def format_taskgraph_labels(taskgraph): def format_taskgraph_labels(taskgraph):
return "\n".join( return "\n".join(
taskgraph.tasks[index].label for index in taskgraph.graph.visit_postorder() sorted(
taskgraph.tasks[index].label for index in taskgraph.graph.visit_postorder()
)
) )

471
third_party/python/taskcluster_taskgraph/taskgraph/optimize.py поставляемый Normal file
Просмотреть файл

@ -0,0 +1,471 @@
# 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/.
"""
The objective of optimization is to remove as many tasks from the graph as
possible, as efficiently as possible, thereby delivering useful results as
quickly as possible. For example, ideally if only a test script is modified in
a push, then the resulting graph contains only the corresponding test suite
task.
See ``taskcluster/docs/optimization.rst`` for more information.
"""
import logging
import os
from collections import defaultdict
from slugid import nice as slugid
from . import files_changed
from .graph import Graph
from .taskgraph import TaskGraph
from .util.parameterization import resolve_task_references
from .util.taskcluster import find_task_id
logger = logging.getLogger(__name__)
TOPSRCDIR = os.path.abspath(os.path.join(__file__, "../../../"))
def optimize_task_graph(
target_task_graph,
params,
do_not_optimize,
decision_task_id,
existing_tasks=None,
strategies=None,
):
"""
Perform task optimization, returning a taskgraph and a map from label to
assigned taskId, including replacement tasks.
"""
label_to_taskid = {}
if not existing_tasks:
existing_tasks = {}
# instantiate the strategies for this optimization process
if not strategies:
strategies = _make_default_strategies()
optimizations = _get_optimizations(target_task_graph, strategies)
removed_tasks = remove_tasks(
target_task_graph=target_task_graph,
optimizations=optimizations,
params=params,
do_not_optimize=do_not_optimize,
)
replaced_tasks = replace_tasks(
target_task_graph=target_task_graph,
optimizations=optimizations,
params=params,
do_not_optimize=do_not_optimize,
label_to_taskid=label_to_taskid,
existing_tasks=existing_tasks,
removed_tasks=removed_tasks,
)
return (
get_subgraph(
target_task_graph,
removed_tasks,
replaced_tasks,
label_to_taskid,
decision_task_id,
),
label_to_taskid,
)
def _make_default_strategies():
return {
"never": OptimizationStrategy(), # "never" is the default behavior
"index-search": IndexSearch(),
"skip-unless-changed": SkipUnlessChanged(),
}
def _get_optimizations(target_task_graph, strategies):
def optimizations(label):
task = target_task_graph.tasks[label]
if task.optimization:
opt_by, arg = list(task.optimization.items())[0]
return (opt_by, strategies[opt_by], arg)
else:
return ("never", strategies["never"], None)
return optimizations
def _log_optimization(verb, opt_counts, opt_reasons=None):
if opt_reasons:
message = "optimize: {label} {action} because of {reason}"
for label, (action, reason) in opt_reasons.items():
logger.debug(message.format(label=label, action=action, reason=reason))
if opt_counts:
logger.info(
f"{verb.title()} "
+ ", ".join(f"{c} tasks by {b}" for b, c in sorted(opt_counts.items()))
+ " during optimization."
)
else:
logger.info(f"No tasks {verb} during optimization")
def remove_tasks(target_task_graph, params, optimizations, do_not_optimize):
"""
Implement the "Removing Tasks" phase, returning a set of task labels of all removed tasks.
"""
opt_counts = defaultdict(int)
opt_reasons = {}
removed = set()
dependents_of = target_task_graph.graph.reverse_links_dict()
tasks = target_task_graph.tasks
prune_candidates = set()
# Traverse graph so dependents (child nodes) are guaranteed to be processed
# first.
for label in target_task_graph.graph.visit_preorder():
# Dependents that can be pruned away (shouldn't cause this task to run).
# Only dependents that either:
# A) Explicitly reference this task in their 'if_dependencies' list, or
# B) Don't have an 'if_dependencies' attribute (i.e are in 'prune_candidates'
# because they should be removed but have prune_deps themselves)
# should be considered.
prune_deps = {
l
for l in dependents_of[label]
if l in prune_candidates
if not tasks[l].if_dependencies or label in tasks[l].if_dependencies
}
def _keep(reason):
"""Mark a task as being kept in the graph. Also recursively removes
any dependents from `prune_candidates`, assuming they should be
kept because of this task.
"""
opt_reasons[label] = ("kept", reason)
# Removes dependents that were in 'prune_candidates' from a task
# that ended up being kept (and therefore the dependents should
# also be kept).
queue = list(prune_deps)
while queue:
l = queue.pop()
# If l is a prune_dep of multiple tasks it could be queued up
# multiple times. Guard against it being already removed.
if l not in prune_candidates:
continue
# If a task doesn't set 'if_dependencies' itself (rather it was
# added to 'prune_candidates' due to one of its depenendents),
# then we shouldn't remove it.
if not tasks[l].if_dependencies:
continue
prune_candidates.remove(l)
queue.extend([r for r in dependents_of[l] if r in prune_candidates])
def _remove(reason):
"""Potentially mark a task as being removed from the graph. If the
task has dependents that can be pruned, add this task to
`prune_candidates` rather than removing it.
"""
if prune_deps:
# If there are prune_deps, unsure if we can remove this task yet.
prune_candidates.add(label)
else:
opt_reasons[label] = ("removed", reason)
opt_counts[reason] += 1
removed.add(label)
# if we're not allowed to optimize, that's easy..
if label in do_not_optimize:
_keep("do not optimize")
continue
# If there are remaining tasks depending on this one, do not remove.
if any(
l for l in dependents_of[label] if l not in removed and l not in prune_deps
):
_keep("dependent tasks")
continue
# Call the optimization strategy.
task = tasks[label]
opt_by, opt, arg = optimizations(label)
if opt.should_remove_task(task, params, arg):
_remove(opt_by)
continue
# Some tasks should only run if their dependency was also run. Since we
# haven't processed dependencies yet, we add them to a list of
# candidate tasks for pruning.
if task.if_dependencies:
opt_reasons[label] = ("kept", opt_by)
prune_candidates.add(label)
else:
_keep(opt_by)
if prune_candidates:
reason = "if-dependencies pruning"
for label in prune_candidates:
# There's an edge case where a triangle graph can cause a
# dependency to stay in 'prune_candidates' when the dependent
# remains. Do a final check to ensure we don't create any bad
# edges.
dependents = any(
d
for d in dependents_of[label]
if d not in prune_candidates
if d not in removed
)
if dependents:
opt_reasons[label] = ("kept", "dependent tasks")
continue
removed.add(label)
opt_counts[reason] += 1
opt_reasons[label] = ("removed", reason)
_log_optimization("removed", opt_counts, opt_reasons)
return removed
def replace_tasks(
target_task_graph,
params,
optimizations,
do_not_optimize,
label_to_taskid,
removed_tasks,
existing_tasks,
):
"""
Implement the "Replacing Tasks" phase, returning a set of task labels of
all replaced tasks. The replacement taskIds are added to label_to_taskid as
a side-effect.
"""
opt_counts = defaultdict(int)
replaced = set()
links_dict = target_task_graph.graph.links_dict()
for label in target_task_graph.graph.visit_postorder():
# if we're not allowed to optimize, that's easy..
if label in do_not_optimize:
continue
# if this task depends on un-replaced, un-removed tasks, do not replace
if any(l not in replaced and l not in removed_tasks for l in links_dict[label]):
continue
# if the task already exists, that's an easy replacement
repl = existing_tasks.get(label)
if repl:
label_to_taskid[label] = repl
replaced.add(label)
opt_counts["existing_tasks"] += 1
continue
# call the optimization strategy
task = target_task_graph.tasks[label]
opt_by, opt, arg = optimizations(label)
repl = opt.should_replace_task(task, params, arg)
if repl:
if repl is True:
# True means remove this task; get_subgraph will catch any
# problems with removed tasks being depended on
removed_tasks.add(label)
else:
label_to_taskid[label] = repl
replaced.add(label)
opt_counts[opt_by] += 1
continue
_log_optimization("replaced", opt_counts)
return replaced
def get_subgraph(
target_task_graph,
removed_tasks,
replaced_tasks,
label_to_taskid,
decision_task_id,
):
"""
Return the subgraph of target_task_graph consisting only of
non-optimized tasks and edges between them.
To avoid losing track of taskIds for tasks optimized away, this method
simultaneously substitutes real taskIds for task labels in the graph, and
populates each task definition's `dependencies` key with the appropriate
taskIds. Task references are resolved in the process.
"""
# check for any dependency edges from included to removed tasks
bad_edges = [
(l, r, n)
for l, r, n in target_task_graph.graph.edges
if l not in removed_tasks and r in removed_tasks
]
if bad_edges:
probs = ", ".join(
f"{l} depends on {r} as {n} but it has been removed"
for l, r, n in bad_edges
)
raise Exception("Optimization error: " + probs)
# fill in label_to_taskid for anything not removed or replaced
assert replaced_tasks <= set(label_to_taskid)
for label in sorted(
target_task_graph.graph.nodes - removed_tasks - set(label_to_taskid)
):
label_to_taskid[label] = slugid()
# resolve labels to taskIds and populate task['dependencies']
tasks_by_taskid = {}
named_links_dict = target_task_graph.graph.named_links_dict()
omit = removed_tasks | replaced_tasks
for label, task in target_task_graph.tasks.items():
if label in omit:
continue
task.task_id = label_to_taskid[label]
named_task_dependencies = {
name: label_to_taskid[label]
for name, label in named_links_dict.get(label, {}).items()
}
# Add remaining soft dependencies
if task.soft_dependencies:
named_task_dependencies.update(
{
label: label_to_taskid[label]
for label in task.soft_dependencies
if label in label_to_taskid and label not in omit
}
)
task.task = resolve_task_references(
task.label,
task.task,
task_id=task.task_id,
decision_task_id=decision_task_id,
dependencies=named_task_dependencies,
)
deps = task.task.setdefault("dependencies", [])
deps.extend(sorted(named_task_dependencies.values()))
tasks_by_taskid[task.task_id] = task
# resolve edges to taskIds
edges_by_taskid = (
(label_to_taskid.get(left), label_to_taskid.get(right), name)
for (left, right, name) in target_task_graph.graph.edges
)
# ..and drop edges that are no longer entirely in the task graph
# (note that this omits edges to replaced tasks, but they are still in task.dependnecies)
edges_by_taskid = {
(left, right, name)
for (left, right, name) in edges_by_taskid
if left in tasks_by_taskid and right in tasks_by_taskid
}
return TaskGraph(tasks_by_taskid, Graph(set(tasks_by_taskid), edges_by_taskid))
class OptimizationStrategy:
def should_remove_task(self, task, params, arg):
"""Determine whether to optimize this task by removing it. Returns
True to remove."""
return False
def should_replace_task(self, task, params, arg):
"""Determine whether to optimize this task by replacing it. Returns a
taskId to replace this task, True to replace with nothing, or False to
keep the task."""
return False
class Either(OptimizationStrategy):
"""Given one or more optimization strategies, remove a task if any of them
says to, and replace with a task if any finds a replacement (preferring the
earliest). By default, each substrategy gets the same arg, but split_args
can return a list of args for each strategy, if desired."""
def __init__(self, *substrategies, **kwargs):
self.substrategies = substrategies
self.split_args = kwargs.pop("split_args", None)
if not self.split_args:
self.split_args = lambda arg: [arg] * len(substrategies)
if kwargs:
raise TypeError("unexpected keyword args")
def _for_substrategies(self, arg, fn):
for sub, arg in zip(self.substrategies, self.split_args(arg)):
rv = fn(sub, arg)
if rv:
return rv
return False
def should_remove_task(self, task, params, arg):
return self._for_substrategies(
arg, lambda sub, arg: sub.should_remove_task(task, params, arg)
)
def should_replace_task(self, task, params, arg):
return self._for_substrategies(
arg, lambda sub, arg: sub.should_replace_task(task, params, arg)
)
class IndexSearch(OptimizationStrategy):
# A task with no dependencies remaining after optimization will be replaced
# if artifacts exist for the corresponding index_paths.
# Otherwise, we're in one of the following cases:
# - the task has un-optimized dependencies
# - the artifacts have expired
# - some changes altered the index_paths and new artifacts need to be
# created.
# In every of those cases, we need to run the task to create or refresh
# artifacts.
def should_replace_task(self, task, params, index_paths):
"Look for a task with one of the given index paths"
for index_path in index_paths:
try:
task_id = find_task_id(
index_path, use_proxy=bool(os.environ.get("TASK_ID"))
)
return task_id
except KeyError:
# 404 will end up here and go on to the next index path
pass
return False
class SkipUnlessChanged(OptimizationStrategy):
def should_remove_task(self, task, params, file_patterns):
if params.get("repository_type") != "hg":
raise RuntimeError(
"SkipUnlessChanged optimization only works with mercurial repositories"
)
# pushlog_id == -1 - this is the case when run from a cron.yml job
if params.get("pushlog_id") == -1:
return False
changed = files_changed.check(params, file_patterns)
if not changed:
logger.debug(
'no files found matching a pattern in `skip-unless-changed` for "{}"'.format(
task.label
)
)
return True
return False

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

@ -37,6 +37,7 @@ base_schema = Schema(
Required("build_date"): int, Required("build_date"): int,
Required("build_number"): int, Required("build_number"): int,
Required("do_not_optimize"): [str], Required("do_not_optimize"): [str],
Required("enable_always_target"): bool,
Required("existing_tasks"): {str: str}, Required("existing_tasks"): {str: str},
Required("filters"): [str], Required("filters"): [str],
Required("head_ref"): str, Required("head_ref"): str,
@ -46,6 +47,7 @@ base_schema = Schema(
Required("level"): str, Required("level"): str,
Required("moz_build_date"): str, Required("moz_build_date"): str,
Required("next_version"): Any(str, None), Required("next_version"): Any(str, None),
Required("optimize_strategies"): Any(str, None),
Required("optimize_target_tasks"): bool, Required("optimize_target_tasks"): bool,
Required("owner"): str, Required("owner"): str,
Required("project"): str, Required("project"): str,
@ -93,6 +95,7 @@ def _get_defaults(repo_root=None):
"build_date": int(time.time()), "build_date": int(time.time()),
"build_number": 1, "build_number": 1,
"do_not_optimize": [], "do_not_optimize": [],
"enable_always_target": True,
"existing_tasks": {}, "existing_tasks": {},
"filters": ["target_tasks_method"], "filters": ["target_tasks_method"],
"head_ref": repo.branch or repo.head_rev, "head_ref": repo.branch or repo.head_rev,
@ -102,6 +105,7 @@ def _get_defaults(repo_root=None):
"level": "3", "level": "3",
"moz_build_date": datetime.now().strftime("%Y%m%d%H%M%S"), "moz_build_date": datetime.now().strftime("%Y%m%d%H%M%S"),
"next_version": None, "next_version": None,
"optimize_strategies": None,
"optimize_target_tasks": True, "optimize_target_tasks": True,
"owner": "nobody@mozilla.com", "owner": "nobody@mozilla.com",
"project": project, "project": project,

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

@ -16,6 +16,7 @@ import multiprocessing
import os import os
import pathlib import pathlib
import random import random
import re
import stat import stat
import subprocess import subprocess
import sys import sys
@ -31,6 +32,11 @@ try:
except ImportError: except ImportError:
zstandard = None zstandard = None
try:
import certifi
except ImportError:
certifi = None
CONCURRENCY = multiprocessing.cpu_count() CONCURRENCY = multiprocessing.cpu_count()
@ -46,13 +52,13 @@ class IntegrityError(Exception):
def ZstdCompressor(*args, **kwargs): def ZstdCompressor(*args, **kwargs):
if not zstandard: if not zstandard:
raise ValueError('zstandard Python package not available') raise ValueError("zstandard Python package not available")
return zstandard.ZstdCompressor(*args, **kwargs) return zstandard.ZstdCompressor(*args, **kwargs)
def ZstdDecompressor(*args, **kwargs): def ZstdDecompressor(*args, **kwargs):
if not zstandard: if not zstandard:
raise ValueError('zstandard Python package not available') raise ValueError("zstandard Python package not available")
return zstandard.ZstdDecompressor(*args, **kwargs) return zstandard.ZstdDecompressor(*args, **kwargs)
@ -67,7 +73,7 @@ def rename_after_close(fname, *args, **kwargs):
manager. manager.
""" """
path = pathlib.Path(fname) path = pathlib.Path(fname)
tmp = path.with_name('%s.tmp' % path.name) tmp = path.with_name("%s.tmp" % path.name)
try: try:
with tmp.open(*args, **kwargs) as fh: with tmp.open(*args, **kwargs) as fh:
yield fh yield fh
@ -127,7 +133,9 @@ def retrier(attempts=5, sleeptime=10, max_sleeptime=300, sleepscale=1.5, jitter=
jitter = jitter or 0 # py35 barfs on the next line if jitter is None jitter = jitter or 0 # py35 barfs on the next line if jitter is None
if jitter > sleeptime: if jitter > sleeptime:
# To prevent negative sleep times # To prevent negative sleep times
raise Exception('jitter ({}) must be less than sleep time ({})'.format(jitter, sleeptime)) raise Exception(
"jitter ({}) must be less than sleep time ({})".format(jitter, sleeptime)
)
sleeptime_real = sleeptime sleeptime_real = sleeptime
for _ in range(attempts): for _ in range(attempts):
@ -149,7 +157,9 @@ def retrier(attempts=5, sleeptime=10, max_sleeptime=300, sleepscale=1.5, jitter=
# Don't need to sleep the last time # Don't need to sleep the last time
if _ < attempts - 1: if _ < attempts - 1:
log("sleeping for %.2fs (attempt %i/%i)" % (sleeptime_real, _ + 1, attempts)) log(
"sleeping for %.2fs (attempt %i/%i)" % (sleeptime_real, _ + 1, attempts)
)
time.sleep(sleeptime_real) time.sleep(sleeptime_real)
@ -166,7 +176,7 @@ def stream_download(url, sha256=None, size=None, headers=None):
content, it should be streamed to a file or memory and only operated content, it should be streamed to a file or memory and only operated
on after the generator is exhausted without raising. on after the generator is exhausted without raising.
""" """
log('Downloading %s' % url) log("Downloading %s" % url)
headers = headers or [] headers = headers or []
h = hashlib.sha256() h = hashlib.sha256()
@ -179,8 +189,10 @@ def stream_download(url, sha256=None, size=None, headers=None):
req_headers[key.strip()] = val.strip() req_headers[key.strip()] = val.strip()
req = urllib.request.Request(url, None, req_headers) req = urllib.request.Request(url, None, req_headers)
with urllib.request.urlopen(req) as fh: with urllib.request.urlopen(
if not url.endswith('.gz') and fh.info().get('Content-Encoding') == 'gzip': req, cafile=certifi.where()
) if certifi else urllib.request.urlopen(req) as fh:
if not url.endswith(".gz") and fh.info().get("Content-Encoding") == "gzip":
fh = gzip.GzipFile(fileobj=fh) fh = gzip.GzipFile(fileobj=fh)
while True: while True:
@ -196,22 +208,26 @@ def stream_download(url, sha256=None, size=None, headers=None):
duration = time.time() - t0 duration = time.time() - t0
digest = h.hexdigest() digest = h.hexdigest()
log('%s resolved to %d bytes with sha256 %s in %.3fs' % ( log(
url, length, digest, duration)) "%s resolved to %d bytes with sha256 %s in %.3fs"
% (url, length, digest, duration)
)
if size: if size:
if size == length: if size == length:
log('Verified size of %s' % url) log("Verified size of %s" % url)
else: else:
raise IntegrityError('size mismatch on %s: wanted %d; got %d' % ( raise IntegrityError(
url, size, length)) "size mismatch on %s: wanted %d; got %d" % (url, size, length)
)
if sha256: if sha256:
if digest == sha256: if digest == sha256:
log('Verified sha256 integrity of %s' % url) log("Verified sha256 integrity of %s" % url)
else: else:
raise IntegrityError('sha256 mismatch on %s: wanted %s; got %s' % ( raise IntegrityError(
url, sha256, digest)) "sha256 mismatch on %s: wanted %s; got %s" % (url, sha256, digest)
)
def download_to_path(url, path, sha256=None, size=None, headers=None): def download_to_path(url, path, sha256=None, size=None, headers=None):
@ -227,10 +243,12 @@ def download_to_path(url, path, sha256=None, size=None, headers=None):
for _ in retrier(attempts=5, sleeptime=60): for _ in retrier(attempts=5, sleeptime=60):
try: try:
log('Downloading %s to %s' % (url, path)) log("Downloading %s to %s" % (url, path))
with rename_after_close(path, 'wb') as fh: with rename_after_close(path, "wb") as fh:
for chunk in stream_download(url, sha256=sha256, size=size, headers=headers): for chunk in stream_download(
url, sha256=sha256, size=size, headers=headers
):
fh.write(chunk) fh.write(chunk)
return return
@ -243,65 +261,85 @@ def download_to_path(url, path, sha256=None, size=None, headers=None):
raise Exception("Download failed, no more retries!") raise Exception("Download failed, no more retries!")
def gpg_verify_path(path: pathlib.Path, public_key_data: bytes, def download_to_memory(url, sha256=None, size=None):
signature_data: bytes): """Download a URL to memory, possibly with verification."""
data = b""
for _ in retrier(attempts=5, sleeptime=60):
try:
log("Downloading %s" % (url))
for chunk in stream_download(url, sha256=sha256, size=size):
data += chunk
return data
except IntegrityError:
raise
except Exception as e:
log("Download failed: {}".format(e))
continue
raise Exception("Download failed, no more retries!")
def gpg_verify_path(path: pathlib.Path, public_key_data: bytes, signature_data: bytes):
"""Verify that a filesystem path verifies using GPG. """Verify that a filesystem path verifies using GPG.
Takes a Path defining a file to verify. ``public_key_data`` contains Takes a Path defining a file to verify. ``public_key_data`` contains
bytes with GPG public key data. ``signature_data`` contains a signed bytes with GPG public key data. ``signature_data`` contains a signed
GPG document to use with ``gpg --verify``. GPG document to use with ``gpg --verify``.
""" """
log('Validating GPG signature of %s' % path) log("Validating GPG signature of %s" % path)
log('GPG key data:\n%s' % public_key_data.decode('ascii')) log("GPG key data:\n%s" % public_key_data.decode("ascii"))
with tempfile.TemporaryDirectory() as td: with tempfile.TemporaryDirectory() as td:
try: try:
# --batch since we're running unattended. # --batch since we're running unattended.
gpg_args = ['gpg', '--homedir', td, '--batch'] gpg_args = ["gpg", "--homedir", td, "--batch"]
log('Importing GPG key...') log("Importing GPG key...")
subprocess.run(gpg_args + ['--import'], subprocess.run(gpg_args + ["--import"], input=public_key_data, check=True)
input=public_key_data,
check=True)
log('Verifying GPG signature...') log("Verifying GPG signature...")
subprocess.run(gpg_args + ['--verify', '-', '%s' % path], subprocess.run(
input=signature_data, gpg_args + ["--verify", "-", "%s" % path],
check=True) input=signature_data,
check=True,
)
log('GPG signature verified!') log("GPG signature verified!")
finally: finally:
# There is a race between the agent self-terminating and # There is a race between the agent self-terminating and
# shutil.rmtree() from the temporary directory cleanup that can # shutil.rmtree() from the temporary directory cleanup that can
# lead to exceptions. Kill the agent before cleanup to prevent this. # lead to exceptions. Kill the agent before cleanup to prevent this.
env = dict(os.environ) env = dict(os.environ)
env['GNUPGHOME'] = td env["GNUPGHOME"] = td
subprocess.run(['gpgconf', '--kill', 'gpg-agent'], env=env) subprocess.run(["gpgconf", "--kill", "gpg-agent"], env=env)
def open_tar_stream(path: pathlib.Path): def open_tar_stream(path: pathlib.Path):
"""""" """"""
if path.suffix == '.bz2': if path.suffix == ".bz2":
return bz2.open(str(path), 'rb') return bz2.open(str(path), "rb")
elif path.suffix == '.gz': elif path.suffix == ".gz":
return gzip.open(str(path), 'rb') return gzip.open(str(path), "rb")
elif path.suffix == '.xz': elif path.suffix == ".xz":
return lzma.open(str(path), 'rb') return lzma.open(str(path), "rb")
elif path.suffix == '.zst': elif path.suffix == ".zst":
dctx = ZstdDecompressor() dctx = ZstdDecompressor()
return dctx.stream_reader(path.open('rb')) return dctx.stream_reader(path.open("rb"))
elif path.suffix == '.tar': elif path.suffix == ".tar":
return path.open('rb') return path.open("rb")
else: else:
raise ValueError('unknown archive format for tar file: %s' % path) raise ValueError("unknown archive format for tar file: %s" % path)
def archive_type(path: pathlib.Path): def archive_type(path: pathlib.Path):
"""Attempt to identify a path as an extractable archive.""" """Attempt to identify a path as an extractable archive."""
if path.suffixes[-2:-1] == ['.tar']: if path.suffixes[-2:-1] == [".tar"]:
return 'tar' return "tar"
elif path.suffix == '.zip': elif path.suffix == ".zip":
return 'zip' return "zip"
else: else:
return None return None
@ -313,12 +351,12 @@ def extract_archive(path, dest_dir, typ):
path = path.resolve() path = path.resolve()
dest_dir = dest_dir.resolve() dest_dir = dest_dir.resolve()
log('Extracting %s to %s' % (path, dest_dir)) log("Extracting %s to %s" % (path, dest_dir))
t0 = time.time() t0 = time.time()
# We pipe input to the decompressor program so that we can apply # We pipe input to the decompressor program so that we can apply
# custom decompressors that the program may not know about. # custom decompressors that the program may not know about.
if typ == 'tar': if typ == "tar":
ifh = open_tar_stream(path) ifh = open_tar_stream(path)
# On Windows, the tar program doesn't support things like symbolic # On Windows, the tar program doesn't support things like symbolic
# links, while Windows actually support them. The tarfile module in # links, while Windows actually support them. The tarfile module in
@ -326,24 +364,25 @@ def extract_archive(path, dest_dir, typ):
# the tar program on Linux, only use tarfile on Windows (tarfile is # the tar program on Linux, only use tarfile on Windows (tarfile is
# also not much slower on Windows, presumably because of the # also not much slower on Windows, presumably because of the
# notoriously bad I/O). # notoriously bad I/O).
if sys.platform == 'win32': if sys.platform == "win32":
tar = tarfile.open(fileobj=ifh, mode='r|') tar = tarfile.open(fileobj=ifh, mode="r|")
tar.extractall(str(dest_dir)) tar.extractall(str(dest_dir))
args = [] args = []
else: else:
args = ['tar', 'xf', '-'] args = ["tar", "xf", "-"]
pipe_stdin = True pipe_stdin = True
elif typ == 'zip': elif typ == "zip":
# unzip from stdin has wonky behavior. We don't use a pipe for it. # unzip from stdin has wonky behavior. We don't use a pipe for it.
ifh = open(os.devnull, 'rb') ifh = open(os.devnull, "rb")
args = ['unzip', '-o', str(path)] args = ["unzip", "-o", str(path)]
pipe_stdin = False pipe_stdin = False
else: else:
raise ValueError('unknown archive format: %s' % path) raise ValueError("unknown archive format: %s" % path)
if args: if args:
with ifh, subprocess.Popen(args, cwd=str(dest_dir), bufsize=0, with ifh, subprocess.Popen(
stdin=subprocess.PIPE) as p: args, cwd=str(dest_dir), bufsize=0, stdin=subprocess.PIPE
) as p:
while True: while True:
if not pipe_stdin: if not pipe_stdin:
break break
@ -355,46 +394,50 @@ def extract_archive(path, dest_dir, typ):
p.stdin.write(chunk) p.stdin.write(chunk)
if p.returncode: if p.returncode:
raise Exception('%r exited %d' % (args, p.returncode)) raise Exception("%r exited %d" % (args, p.returncode))
log('%s extracted in %.3fs' % (path, time.time() - t0)) log("%s extracted in %.3fs" % (path, time.time() - t0))
def repack_archive(orig: pathlib.Path, dest: pathlib.Path, def repack_archive(
strip_components=0, prefix=''): orig: pathlib.Path, dest: pathlib.Path, strip_components=0, prefix=""
):
assert orig != dest assert orig != dest
log('Repacking as %s' % dest) log("Repacking as %s" % dest)
orig_typ = archive_type(orig) orig_typ = archive_type(orig)
typ = archive_type(dest) typ = archive_type(dest)
if not orig_typ: if not orig_typ:
raise Exception('Archive type not supported for %s' % orig.name) raise Exception("Archive type not supported for %s" % orig.name)
if not typ: if not typ:
raise Exception('Archive type not supported for %s' % dest.name) raise Exception("Archive type not supported for %s" % dest.name)
if dest.suffixes[-2:] != ['.tar', '.zst']: if dest.suffixes[-2:] != [".tar", ".zst"]:
raise Exception('Only producing .tar.zst archives is supported.') raise Exception("Only producing .tar.zst archives is supported.")
if strip_components or prefix: if strip_components or prefix:
def filter(name): def filter(name):
if strip_components: if strip_components:
stripped = '/'.join(name.split('/')[strip_components:]) stripped = "/".join(name.split("/")[strip_components:])
if not stripped: if not stripped:
raise Exception( raise Exception(
'Stripping %d components would remove files' "Stripping %d components would remove files" % strip_components
% strip_components) )
name = stripped name = stripped
return prefix + name return prefix + name
else: else:
filter = None filter = None
with rename_after_close(dest, 'wb') as fh: with rename_after_close(dest, "wb") as fh:
ctx = ZstdCompressor() ctx = ZstdCompressor()
if orig_typ == 'zip': if orig_typ == "zip":
assert typ == 'tar' assert typ == "tar"
zip = zipfile.ZipFile(orig) zip = zipfile.ZipFile(orig)
# Convert the zip stream to a tar on the fly. # Convert the zip stream to a tar on the fly.
with ctx.stream_writer(fh) as compressor, \ with ctx.stream_writer(fh) as compressor, tarfile.open(
tarfile.open(fileobj=compressor, mode='w:') as tar: fileobj=compressor, mode="w:"
) as tar:
for zipinfo in zip.infolist(): for zipinfo in zip.infolist():
if zipinfo.is_dir(): if zipinfo.is_dir():
continue continue
@ -408,7 +451,8 @@ def repack_archive(orig: pathlib.Path, dest: pathlib.Path,
# care about accuracy, but rather about reproducibility, # care about accuracy, but rather about reproducibility,
# so we pick UTC. # so we pick UTC.
time = datetime.datetime( time = datetime.datetime(
*zipinfo.date_time, tzinfo=datetime.timezone.utc) *zipinfo.date_time, tzinfo=datetime.timezone.utc
)
tarinfo.mtime = time.timestamp() tarinfo.mtime = time.timestamp()
# 0 is MS-DOS, 3 is UNIX. Only in the latter case do we # 0 is MS-DOS, 3 is UNIX. Only in the latter case do we
# get anything useful for the tar file mode. # get anything useful for the tar file mode.
@ -424,26 +468,35 @@ def repack_archive(orig: pathlib.Path, dest: pathlib.Path,
elif stat.S_ISREG(mode) or stat.S_IFMT(mode) == 0: elif stat.S_ISREG(mode) or stat.S_IFMT(mode) == 0:
tar.addfile(tarinfo, zip.open(filename)) tar.addfile(tarinfo, zip.open(filename))
else: else:
raise Exception('Unsupported file mode %o' raise Exception("Unsupported file mode %o" % stat.S_IFMT(mode))
% stat.S_IFMT(mode))
elif orig_typ == 'tar': elif orig_typ == "tar":
if typ == 'zip': if typ == "zip":
raise Exception('Repacking a tar to zip is not supported') raise Exception("Repacking a tar to zip is not supported")
assert typ == 'tar' assert typ == "tar"
ifh = open_tar_stream(orig) ifh = open_tar_stream(orig)
if filter: if filter:
# To apply the filter, we need to open the tar stream and # To apply the filter, we need to open the tar stream and
# tweak it. # tweak it.
origtar = tarfile.open(fileobj=ifh, mode='r|') origtar = tarfile.open(fileobj=ifh, mode="r|")
with ctx.stream_writer(fh) as compressor, \ with ctx.stream_writer(fh) as compressor, tarfile.open(
tarfile.open(fileobj=compressor, mode='w:') as tar: fileobj=compressor,
mode="w:",
format=origtar.format,
) as tar:
for tarinfo in origtar: for tarinfo in origtar:
if tarinfo.isdir(): if tarinfo.isdir():
continue continue
tarinfo.name = filter(tarinfo.name) tarinfo.name = filter(tarinfo.name)
tar.addfile(tarinfo, origtar.extractfile(tarinfo)) if "path" in tarinfo.pax_headers:
tarinfo.pax_headers["path"] = filter(
tarinfo.pax_headers["path"]
)
if tarinfo.isfile():
tar.addfile(tarinfo, origtar.extractfile(tarinfo))
else:
tar.addfile(tarinfo)
else: else:
# We only change compression here. The tar stream is unchanged. # We only change compression here. The tar stream is unchanged.
ctx.copy_stream(ifh, fh) ctx.copy_stream(ifh, fh)
@ -457,7 +510,7 @@ def fetch_and_extract(url, dest_dir, extract=True, sha256=None, size=None):
the destination directory. the destination directory.
""" """
basename = urllib.parse.urlparse(url).path.split('/')[-1] basename = urllib.parse.urlparse(url).path.split("/")[-1]
dest_path = dest_dir / basename dest_path = dest_dir / basename
download_to_path(url, dest_path, sha256=sha256, size=size) download_to_path(url, dest_path, sha256=sha256, size=size)
@ -468,7 +521,7 @@ def fetch_and_extract(url, dest_dir, extract=True, sha256=None, size=None):
typ = archive_type(dest_path) typ = archive_type(dest_path)
if typ: if typ:
extract_archive(dest_path, dest_dir, typ) extract_archive(dest_path, dest_dir, typ)
log('Removing %s' % dest_path) log("Removing %s" % dest_path)
dest_path.unlink() dest_path.unlink()
@ -484,37 +537,152 @@ def fetch_urls(downloads):
f.result() f.result()
def git_checkout_archive(dest_path: pathlib.Path, repo: str, commit: str, def _git_checkout_github_archive(
prefix=None): dest_path: pathlib.Path, repo: str, commit: str, prefix: str
):
"Use github archive generator to speed up github git repo cloning"
repo = repo.rstrip("/")
github_url = "{repo}/archive/{commit}.tar.gz".format(**locals())
with tempfile.TemporaryDirectory() as td:
temp_dir = pathlib.Path(td)
dl_dest = temp_dir / "archive.tar.gz"
download_to_path(github_url, dl_dest)
repack_archive(dl_dest, dest_path, strip_components=1, prefix=prefix + "/")
def _github_submodule_required(repo: str, commit: str):
"Use github API to check if submodules are used"
url = "{repo}/blob/{commit}/.gitmodules".format(**locals())
try:
status_code = urllib.request.urlopen(url).getcode()
return status_code == 200
except:
return False
def git_checkout_archive(
dest_path: pathlib.Path,
repo: str,
commit: str,
prefix=None,
ssh_key=None,
include_dot_git=False,
):
"""Produce an archive of the files comprising a Git checkout.""" """Produce an archive of the files comprising a Git checkout."""
dest_path.parent.mkdir(parents=True, exist_ok=True) dest_path.parent.mkdir(parents=True, exist_ok=True)
if dest_path.suffixes[-2:] != ['.tar', '.zst']: if not prefix:
raise Exception('Only producing .tar.zst archives is supported.') prefix = repo.rstrip("/").rsplit("/", 1)[-1]
if dest_path.suffixes[-2:] != [".tar", ".zst"]:
raise Exception("Only producing .tar.zst archives is supported.")
if repo.startswith("https://github.com/"):
if not include_dot_git and not _github_submodule_required(repo, commit):
log("Using github archive service to speedup archive creation")
# Always log sha1 info, either from commit or resolved from repo.
if re.match(r"^[a-fA-F0-9]{40}$", commit):
revision = commit
else:
ref_output = subprocess.check_output(["git", "ls-remote", repo,
'refs/heads/' + commit])
revision, _ = ref_output.decode().split(maxsplit=1)
log("Fetching revision {}".format(revision))
return _git_checkout_github_archive(dest_path, repo, commit, prefix)
with tempfile.TemporaryDirectory() as td: with tempfile.TemporaryDirectory() as td:
temp_dir = pathlib.Path(td) temp_dir = pathlib.Path(td)
if not prefix:
prefix = repo.rstrip('/').rsplit('/', 1)[-1]
git_dir = temp_dir / prefix git_dir = temp_dir / prefix
# This could be faster with a shallow clone. However, Git requires a ref # This could be faster with a shallow clone. However, Git requires a ref
# to initiate a clone. Since the commit-ish may not refer to a ref, we # to initiate a clone. Since the commit-ish may not refer to a ref, we
# simply perform a full clone followed by a checkout. # simply perform a full clone followed by a checkout.
print('cloning %s to %s' % (repo, git_dir)) print("cloning %s to %s" % (repo, git_dir))
subprocess.run(['git', 'clone', '--recurse-submodules', repo, str(git_dir)],
check=True)
subprocess.run(['git', 'checkout', '--recurse-submodules', commit], env = os.environ.copy()
cwd=str(git_dir), check=True) keypath = ""
if ssh_key:
taskcluster_secret_url = api(
os.environ.get("TASKCLUSTER_PROXY_URL"),
"secrets",
"v1",
"secret/{keypath}".format(keypath=ssh_key),
)
taskcluster_secret = b"".join(stream_download(taskcluster_secret_url))
taskcluster_secret = json.loads(taskcluster_secret)
sshkey = taskcluster_secret["secret"]["ssh_privkey"]
print('creating archive %s of commit %s' % (dest_path, commit)) keypath = temp_dir.joinpath("ssh-key")
proc = subprocess.Popen([ keypath.write_text(sshkey)
'tar', 'cf', '-', '--exclude=.git', '-C', str(temp_dir), prefix, keypath.chmod(0o600)
], stdout=subprocess.PIPE)
with rename_after_close(dest_path, 'wb') as out: env = {
"GIT_SSH_COMMAND": "ssh -o 'StrictHostKeyChecking no' -i {keypath}".format(
keypath=keypath
)
}
subprocess.run(["git", "clone", "-n", repo, str(git_dir)], check=True, env=env)
# Always use a detached head so that git prints out what it checked out.
subprocess.run(
["git", "checkout", "--detach", commit], cwd=str(git_dir), check=True
)
# When including the .git, we want --depth 1, but a direct clone would not
# necessarily be able to give us the right commit.
if include_dot_git:
initial_clone = git_dir.with_name(git_dir.name + ".orig")
git_dir.rename(initial_clone)
subprocess.run(
[
"git",
"clone",
"file://" + str(initial_clone),
str(git_dir),
"--depth",
"1",
],
check=True,
)
subprocess.run(
["git", "remote", "set-url", "origin", repo],
cwd=str(git_dir),
check=True,
)
# --depth 1 can induce more work on the server side, so only use it for
# submodule initialization when we want to keep the .git directory.
depth = ["--depth", "1"] if include_dot_git else []
subprocess.run(
["git", "submodule", "update", "--init"] + depth,
cwd=str(git_dir),
check=True,
)
if keypath:
os.remove(keypath)
print("creating archive %s of commit %s" % (dest_path, commit))
exclude_dot_git = [] if include_dot_git else ["--exclude=.git"]
proc = subprocess.Popen(
[
"tar",
"cf",
"-",
]
+ exclude_dot_git
+ [
"-C",
str(temp_dir),
prefix,
],
stdout=subprocess.PIPE,
)
with rename_after_close(dest_path, "wb") as out:
ctx = ZstdCompressor() ctx = ZstdCompressor()
ctx.copy_stream(proc.stdout, out) ctx.copy_stream(proc.stdout, out)
@ -525,8 +693,14 @@ def command_git_checkout_archive(args):
dest = pathlib.Path(args.dest) dest = pathlib.Path(args.dest)
try: try:
git_checkout_archive(dest, args.repo, args.commit, git_checkout_archive(
prefix=args.path_prefix) dest,
args.repo,
args.commit,
prefix=args.path_prefix,
ssh_key=args.ssh_key_secret,
include_dot_git=args.include_dot_git,
)
except Exception: except Exception:
try: try:
dest.unlink() dest.unlink()
@ -541,25 +715,26 @@ def command_static_url(args):
gpg_env_key = args.gpg_key_env gpg_env_key = args.gpg_key_env
if bool(gpg_sig_url) != bool(gpg_env_key): if bool(gpg_sig_url) != bool(gpg_env_key):
print('--gpg-sig-url and --gpg-key-env must both be defined') print("--gpg-sig-url and --gpg-key-env must both be defined")
return 1 return 1
if gpg_sig_url: if gpg_sig_url:
gpg_signature = b''.join(stream_download(gpg_sig_url)) gpg_signature = b"".join(stream_download(gpg_sig_url))
gpg_key = os.environb[gpg_env_key.encode('ascii')] gpg_key = os.environb[gpg_env_key.encode("ascii")]
dest = pathlib.Path(args.dest) dest = pathlib.Path(args.dest)
dest.parent.mkdir(parents=True, exist_ok=True) dest.parent.mkdir(parents=True, exist_ok=True)
basename = urllib.parse.urlparse(args.url).path.split('/')[-1] basename = urllib.parse.urlparse(args.url).path.split("/")[-1]
if basename.endswith(''.join(dest.suffixes)): if basename.endswith("".join(dest.suffixes)):
dl_dest = dest dl_dest = dest
else: else:
dl_dest = dest.parent / basename dl_dest = dest.parent / basename
try: try:
download_to_path(args.url, dl_dest, sha256=args.sha256, size=args.size, download_to_path(
headers=args.headers) args.url, dl_dest, sha256=args.sha256, size=args.size, headers=args.headers
)
if gpg_sig_url: if gpg_sig_url:
gpg_verify_path(dl_dest, gpg_key, gpg_signature) gpg_verify_path(dl_dest, gpg_key, gpg_signature)
@ -575,112 +750,150 @@ def command_static_url(args):
raise raise
if dl_dest != dest: if dl_dest != dest:
log('Removing %s' % dl_dest) log("Removing %s" % dl_dest)
dl_dest.unlink() dl_dest.unlink()
def api(root_url, service, version, path): def api(root_url, service, version, path):
# taskcluster-lib-urls is not available when this script runs, so # taskcluster-lib-urls is not available when this script runs, so
# simulate its behavior: # simulate its behavior:
if root_url == 'https://taskcluster.net': return "{root_url}/api/{service}/{version}/{path}".format(
return 'https://{service}.taskcluster.net/{version}/{path}'.format( root_url=root_url, service=service, version=version, path=path
service=service, version=version, path=path) )
return '{root_url}/api/{service}/{version}/{path}'.format(
root_url=root_url, service=service, version=version, path=path)
def get_hash(fetch, root_url):
path = "task/{task}/artifacts/{artifact}".format(
task=fetch["task"], artifact="public/chain-of-trust.json"
)
url = api(root_url, "queue", "v1", path)
cot = json.loads(download_to_memory(url))
return cot["artifacts"][fetch["artifact"]]["sha256"]
def command_task_artifacts(args): def command_task_artifacts(args):
start = time.monotonic() start = time.monotonic()
fetches = json.loads(os.environ['MOZ_FETCHES']) fetches = json.loads(os.environ["MOZ_FETCHES"])
downloads = [] downloads = []
for fetch in fetches: for fetch in fetches:
extdir = pathlib.Path(args.dest) extdir = pathlib.Path(args.dest)
if 'dest' in fetch: if "dest" in fetch:
extdir = extdir.joinpath(fetch['dest']) # Note: normpath doesn't like pathlib.Path in python 3.5
extdir = pathlib.Path(os.path.normpath(str(extdir.joinpath(fetch["dest"]))))
extdir.mkdir(parents=True, exist_ok=True) extdir.mkdir(parents=True, exist_ok=True)
root_url = os.environ['TASKCLUSTER_ROOT_URL'] root_url = os.environ["TASKCLUSTER_ROOT_URL"]
if fetch['artifact'].startswith('public/'): sha256 = None
path = 'task/{task}/artifacts/{artifact}'.format( if fetch.get("verify-hash"):
task=fetch['task'], artifact=fetch['artifact']) sha256 = get_hash(fetch, root_url)
url = api(root_url, 'queue', 'v1', path) if fetch["artifact"].startswith("public/"):
path = "task/{task}/artifacts/{artifact}".format(
task=fetch["task"], artifact=fetch["artifact"]
)
url = api(root_url, "queue", "v1", path)
else: else:
url = ('{proxy_url}/api/queue/v1/task/{task}/artifacts/{artifact}').format( url = ("{proxy_url}/api/queue/v1/task/{task}/artifacts/{artifact}").format(
proxy_url=os.environ['TASKCLUSTER_PROXY_URL'], proxy_url=os.environ["TASKCLUSTER_PROXY_URL"],
task=fetch['task'], task=fetch["task"],
artifact=fetch['artifact']) artifact=fetch["artifact"],
downloads.append((url, extdir, fetch['extract'])) )
downloads.append((url, extdir, fetch["extract"], sha256))
fetch_urls(downloads) fetch_urls(downloads)
end = time.monotonic() end = time.monotonic()
perfherder_data = { perfherder_data = {
'framework': {'name': 'build_metrics'}, "framework": {"name": "build_metrics"},
'suites': [{ "suites": [
'name': 'fetch_content', {
'value': end - start, "name": "fetch_content",
'lowerIsBetter': True, "value": end - start,
'shouldAlert': False, "lowerIsBetter": True,
'subtests': [], "shouldAlert": False,
}], "subtests": [],
}
],
} }
print('PERFHERDER_DATA: {}'.format(json.dumps(perfherder_data)), file=sys.stderr) print("PERFHERDER_DATA: {}".format(json.dumps(perfherder_data)), file=sys.stderr)
def main(): def main():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title='sub commands') subparsers = parser.add_subparsers(title="sub commands")
git_checkout = subparsers.add_parser( git_checkout = subparsers.add_parser(
'git-checkout-archive', "git-checkout-archive",
help='Obtain an archive of files from a Git repository checkout') help="Obtain an archive of files from a Git repository checkout",
)
git_checkout.set_defaults(func=command_git_checkout_archive) git_checkout.set_defaults(func=command_git_checkout_archive)
git_checkout.add_argument('--path-prefix', git_checkout.add_argument(
help='Prefix for paths in produced archive') "--path-prefix", help="Prefix for paths in produced archive"
git_checkout.add_argument('repo', )
help='URL to Git repository to be cloned') git_checkout.add_argument("repo", help="URL to Git repository to be cloned")
git_checkout.add_argument('commit', git_checkout.add_argument("commit", help="Git commit to check out")
help='Git commit to check out') git_checkout.add_argument("dest", help="Destination path of archive")
git_checkout.add_argument('dest', git_checkout.add_argument(
help='Destination path of archive') "--ssh-key-secret", help="The scope path of the ssh key to used for checkout"
)
git_checkout.add_argument(
"--include-dot-git", action="store_true", help="Include the .git directory"
)
url = subparsers.add_parser('static-url', help='Download a static URL') url = subparsers.add_parser("static-url", help="Download a static URL")
url.set_defaults(func=command_static_url) url.set_defaults(func=command_static_url)
url.add_argument('--sha256', required=True, url.add_argument("--sha256", required=True, help="SHA-256 of downloaded content")
help='SHA-256 of downloaded content') url.add_argument(
url.add_argument('--size', required=True, type=int, "--size", required=True, type=int, help="Size of downloaded content, in bytes"
help='Size of downloaded content, in bytes') )
url.add_argument('--gpg-sig-url', url.add_argument(
help='URL containing signed GPG document validating ' "--gpg-sig-url",
'URL to fetch') help="URL containing signed GPG document validating " "URL to fetch",
url.add_argument('--gpg-key-env', )
help='Environment variable containing GPG key to validate') url.add_argument(
url.add_argument('--strip-components', type=int, default=0, "--gpg-key-env", help="Environment variable containing GPG key to validate"
help='Number of leading components to strip from file ' )
'names in the downloaded archive') url.add_argument(
url.add_argument('--add-prefix', default='', "--strip-components",
help='Prefix to add to file names in the downloaded ' type=int,
'archive') default=0,
url.add_argument('-H', '--header', default=[], action='append', dest='headers', help="Number of leading components to strip from file "
help='Header to send as part of the request, can be passed ' "names in the downloaded archive",
'multiple times') )
url.add_argument('url', help='URL to fetch') url.add_argument(
url.add_argument('dest', help='Destination path') "--add-prefix",
default="",
help="Prefix to add to file names in the downloaded " "archive",
)
url.add_argument(
"-H",
"--header",
default=[],
action="append",
dest="headers",
help="Header to send as part of the request, can be passed " "multiple times",
)
url.add_argument("url", help="URL to fetch")
url.add_argument("dest", help="Destination path")
artifacts = subparsers.add_parser('task-artifacts', artifacts = subparsers.add_parser("task-artifacts", help="Fetch task artifacts")
help='Fetch task artifacts')
artifacts.set_defaults(func=command_task_artifacts) artifacts.set_defaults(func=command_task_artifacts)
artifacts.add_argument('-d', '--dest', default=os.environ.get('MOZ_FETCHES_DIR'), artifacts.add_argument(
help='Destination directory which will contain all ' "-d",
'artifacts (defaults to $MOZ_FETCHES_DIR)') "--dest",
default=os.environ.get("MOZ_FETCHES_DIR"),
help="Destination directory which will contain all "
"artifacts (defaults to $MOZ_FETCHES_DIR)",
)
args = parser.parse_args() args = parser.parse_args()
if not args.dest: if not args.dest:
parser.error('no destination directory specified, either pass in --dest ' parser.error(
'or set $MOZ_FETCHES_DIR') "no destination directory specified, either pass in --dest "
"or set $MOZ_FETCHES_DIR"
)
return args.func(args) return args.func(args)
if __name__ == '__main__': if __name__ == "__main__":
sys.exit(main()) sys.exit(main())

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

@ -9,6 +9,7 @@ from a source repo using best practices to ensure optimal clone
times and storage efficiency. times and storage efficiency.
""" """
from __future__ import absolute_import
import contextlib import contextlib
import json import json
@ -40,8 +41,8 @@ from mercurial import (
# Causes worker to purge caches on process exit and for task to retry. # Causes worker to purge caches on process exit and for task to retry.
EXIT_PURGE_CACHE = 72 EXIT_PURGE_CACHE = 72
testedwith = b'4.5 4.6 4.7 4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5' testedwith = b"4.5 4.6 4.7 4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9"
minimumhgversion = b'4.5' minimumhgversion = b"4.5"
cmdtable = {} cmdtable = {}
command = registrar.command(cmdtable) command = registrar.command(cmdtable)
@ -49,41 +50,60 @@ command = registrar.command(cmdtable)
configtable = {} configtable = {}
configitem = registrar.configitem(configtable) configitem = registrar.configitem(configtable)
configitem(b'robustcheckout', b'retryjittermin', default=configitems.dynamicdefault) configitem(b"robustcheckout", b"retryjittermin", default=configitems.dynamicdefault)
configitem(b'robustcheckout', b'retryjittermax', default=configitems.dynamicdefault) configitem(b"robustcheckout", b"retryjittermax", default=configitems.dynamicdefault)
def getsparse(): def getsparse():
from mercurial import sparse from mercurial import sparse
return sparse return sparse
def peerlookup(remote, v): def peerlookup(remote, v):
# TRACKING hg46 4.6 added commandexecutor API. with remote.commandexecutor() as e:
if util.safehasattr(remote, 'commandexecutor'): return e.callcommand(b"lookup", {b"key": v}).result()
with remote.commandexecutor() as e:
return e.callcommand(b'lookup', {b'key': v}).result()
else:
return remote.lookup(v)
@command(b'robustcheckout', [ @command(
(b'', b'upstream', b'', b'URL of upstream repo to clone from'), b"robustcheckout",
(b'r', b'revision', b'', b'Revision to check out'), [
(b'b', b'branch', b'', b'Branch to check out'), (b"", b"upstream", b"", b"URL of upstream repo to clone from"),
(b'', b'purge', False, b'Whether to purge the working directory'), (b"r", b"revision", b"", b"Revision to check out"),
(b'', b'sharebase', b'', b'Directory where shared repos should be placed'), (b"b", b"branch", b"", b"Branch to check out"),
(b'', b'networkattempts', 3, b'Maximum number of attempts for network ' (b"", b"purge", False, b"Whether to purge the working directory"),
b'operations'), (b"", b"sharebase", b"", b"Directory where shared repos should be placed"),
(b'', b'sparseprofile', b'', b'Sparse checkout profile to use (path in repo)'), (
(b'U', b'noupdate', False, b'the clone will include an empty working directory\n' b"",
b'(only a repository)'), b"networkattempts",
3,
b"Maximum number of attempts for network " b"operations",
),
(b"", b"sparseprofile", b"", b"Sparse checkout profile to use (path in repo)"),
(
b"U",
b"noupdate",
False,
b"the clone will include an empty working directory\n"
b"(only a repository)",
),
], ],
b'[OPTION]... URL DEST', b"[OPTION]... URL DEST",
norepo=True) norepo=True,
def robustcheckout(ui, url, dest, upstream=None, revision=None, branch=None, )
purge=False, sharebase=None, networkattempts=None, def robustcheckout(
sparseprofile=None, noupdate=False): ui,
url,
dest,
upstream=None,
revision=None,
branch=None,
purge=False,
sharebase=None,
networkattempts=None,
sparseprofile=None,
noupdate=False,
):
"""Ensure a working copy has the specified revision checked out. """Ensure a working copy has the specified revision checked out.
Repository data is automatically pooled into the common directory Repository data is automatically pooled into the common directory
@ -115,21 +135,28 @@ def robustcheckout(ui, url, dest, upstream=None, revision=None, branch=None,
4.3 or newer and the ``sparse`` extension must be enabled. 4.3 or newer and the ``sparse`` extension must be enabled.
""" """
if not revision and not branch: if not revision and not branch:
raise error.Abort(b'must specify one of --revision or --branch') raise error.Abort(b"must specify one of --revision or --branch")
if revision and branch: if revision and branch:
raise error.Abort(b'cannot specify both --revision and --branch') raise error.Abort(b"cannot specify both --revision and --branch")
# Require revision to look like a SHA-1. # Require revision to look like a SHA-1.
if revision: if revision:
if len(revision) < 12 or len(revision) > 40 or not re.match(b'^[a-f0-9]+$', revision): if (
raise error.Abort(b'--revision must be a SHA-1 fragment 12-40 ' len(revision) < 12
b'characters long') or len(revision) > 40
or not re.match(b"^[a-f0-9]+$", revision)
):
raise error.Abort(
b"--revision must be a SHA-1 fragment 12-40 " b"characters long"
)
sharebase = sharebase or ui.config(b'share', b'pool') sharebase = sharebase or ui.config(b"share", b"pool")
if not sharebase: if not sharebase:
raise error.Abort(b'share base directory not defined; refusing to operate', raise error.Abort(
hint=b'define share.pool config option or pass --sharebase') b"share base directory not defined; refusing to operate",
hint=b"define share.pool config option or pass --sharebase",
)
# Sparse profile support was added in Mercurial 4.3, where it was highly # Sparse profile support was added in Mercurial 4.3, where it was highly
# experimental. Because of the fragility of it, we only support sparse # experimental. Because of the fragility of it, we only support sparse
@ -139,16 +166,17 @@ def robustcheckout(ui, url, dest, upstream=None, revision=None, branch=None,
# fast if we can't satisfy the desired checkout request. # fast if we can't satisfy the desired checkout request.
if sparseprofile: if sparseprofile:
try: try:
extensions.find(b'sparse') extensions.find(b"sparse")
except KeyError: except KeyError:
raise error.Abort(b'sparse extension must be enabled to use ' raise error.Abort(
b'--sparseprofile') b"sparse extension must be enabled to use " b"--sparseprofile"
)
ui.warn(b'(using Mercurial %s)\n' % util.version()) ui.warn(b"(using Mercurial %s)\n" % util.version())
# worker.backgroundclose only makes things faster if running anti-virus, # worker.backgroundclose only makes things faster if running anti-virus,
# which our automation doesn't. Disable it. # which our automation doesn't. Disable it.
ui.setconfig(b'worker', b'backgroundclose', False) ui.setconfig(b"worker", b"backgroundclose", False)
# By default the progress bar starts after 3s and updates every 0.1s. We # By default the progress bar starts after 3s and updates every 0.1s. We
# change this so it shows and updates every 1.0s. # change this so it shows and updates every 1.0s.
@ -156,9 +184,9 @@ def robustcheckout(ui, url, dest, upstream=None, revision=None, branch=None,
# even if there is no known TTY. # even if there is no known TTY.
# We make the config change here instead of in a config file because # We make the config change here instead of in a config file because
# otherwise we're at the whim of whatever configs are used in automation. # otherwise we're at the whim of whatever configs are used in automation.
ui.setconfig(b'progress', b'delay', 1.0) ui.setconfig(b"progress", b"delay", 1.0)
ui.setconfig(b'progress', b'refresh', 1.0) ui.setconfig(b"progress", b"refresh", 1.0)
ui.setconfig(b'progress', b'assume-tty', True) ui.setconfig(b"progress", b"assume-tty", True)
sharebase = os.path.realpath(sharebase) sharebase = os.path.realpath(sharebase)
@ -167,9 +195,21 @@ def robustcheckout(ui, url, dest, upstream=None, revision=None, branch=None,
start = time.time() start = time.time()
try: try:
return _docheckout(ui, url, dest, upstream, revision, branch, purge, return _docheckout(
sharebase, optimes, behaviors, networkattempts, ui,
sparse_profile=sparseprofile, noupdate=noupdate) url,
dest,
upstream,
revision,
branch,
purge,
sharebase,
optimes,
behaviors,
networkattempts,
sparse_profile=sparseprofile,
noupdate=noupdate,
)
finally: finally:
overall = time.time() - start overall = time.time() - start
@ -177,89 +217,118 @@ def robustcheckout(ui, url, dest, upstream=None, revision=None, branch=None,
# the various "flavors" of operations. # the various "flavors" of operations.
# ``overall`` is always the total operation time. # ``overall`` is always the total operation time.
optimes.append(('overall', overall)) optimes.append(("overall", overall))
def record_op(name): def record_op(name):
# If special behaviors due to "corrupt" storage occur, we vary the # If special behaviors due to "corrupt" storage occur, we vary the
# name to convey that. # name to convey that.
if 'remove-store' in behaviors: if "remove-store" in behaviors:
name += '_rmstore' name += "_rmstore"
if 'remove-wdir' in behaviors: if "remove-wdir" in behaviors:
name += '_rmwdir' name += "_rmwdir"
optimes.append((name, overall)) optimes.append((name, overall))
# We break out overall operations primarily by their network interaction # We break out overall operations primarily by their network interaction
# We have variants within for working directory operations. # We have variants within for working directory operations.
if 'clone' in behaviors and 'create-store' in behaviors: if "clone" in behaviors and "create-store" in behaviors:
record_op('overall_clone') record_op("overall_clone")
if 'sparse-update' in behaviors: if "sparse-update" in behaviors:
record_op('overall_clone_sparsecheckout') record_op("overall_clone_sparsecheckout")
else: else:
record_op('overall_clone_fullcheckout') record_op("overall_clone_fullcheckout")
elif 'pull' in behaviors or 'clone' in behaviors: elif "pull" in behaviors or "clone" in behaviors:
record_op('overall_pull') record_op("overall_pull")
if 'sparse-update' in behaviors: if "sparse-update" in behaviors:
record_op('overall_pull_sparsecheckout') record_op("overall_pull_sparsecheckout")
else: else:
record_op('overall_pull_fullcheckout') record_op("overall_pull_fullcheckout")
if 'empty-wdir' in behaviors: if "empty-wdir" in behaviors:
record_op('overall_pull_emptywdir') record_op("overall_pull_emptywdir")
else: else:
record_op('overall_pull_populatedwdir') record_op("overall_pull_populatedwdir")
else: else:
record_op('overall_nopull') record_op("overall_nopull")
if 'sparse-update' in behaviors: if "sparse-update" in behaviors:
record_op('overall_nopull_sparsecheckout') record_op("overall_nopull_sparsecheckout")
else: else:
record_op('overall_nopull_fullcheckout') record_op("overall_nopull_fullcheckout")
if 'empty-wdir' in behaviors: if "empty-wdir" in behaviors:
record_op('overall_nopull_emptywdir') record_op("overall_nopull_emptywdir")
else: else:
record_op('overall_nopull_populatedwdir') record_op("overall_nopull_populatedwdir")
server_url = urllibcompat.urlreq.urlparse(url).netloc server_url = urllibcompat.urlreq.urlparse(url).netloc
if 'TASKCLUSTER_INSTANCE_TYPE' in os.environ: if "TASKCLUSTER_INSTANCE_TYPE" in os.environ:
perfherder = { perfherder = {
'framework': { "framework": {
'name': 'vcs', "name": "vcs",
}, },
'suites': [], "suites": [],
} }
for op, duration in optimes: for op, duration in optimes:
perfherder['suites'].append({ perfherder["suites"].append(
'name': op, {
'value': duration, "name": op,
'lowerIsBetter': True, "value": duration,
'shouldAlert': False, "lowerIsBetter": True,
'serverUrl': server_url.decode('utf-8'), "shouldAlert": False,
'hgVersion': util.version().decode('utf-8'), "serverUrl": server_url.decode("utf-8"),
'extraOptions': [os.environ['TASKCLUSTER_INSTANCE_TYPE']], "hgVersion": util.version().decode("utf-8"),
'subtests': [], "extraOptions": [os.environ["TASKCLUSTER_INSTANCE_TYPE"]],
}) "subtests": [],
ui.write(b'PERFHERDER_DATA: %s\n' % }
pycompat.bytestr(json.dumps(perfherder, sort_keys=True))) )
ui.write(
b"PERFHERDER_DATA: %s\n"
% pycompat.bytestr(json.dumps(perfherder, sort_keys=True))
)
def _docheckout(ui, url, dest, upstream, revision, branch, purge, sharebase,
optimes, behaviors, networkattemptlimit, networkattempts=None, def _docheckout(
sparse_profile=None, noupdate=False): ui,
url,
dest,
upstream,
revision,
branch,
purge,
sharebase,
optimes,
behaviors,
networkattemptlimit,
networkattempts=None,
sparse_profile=None,
noupdate=False,
):
if not networkattempts: if not networkattempts:
networkattempts = [1] networkattempts = [1]
def callself(): def callself():
return _docheckout(ui, url, dest, upstream, revision, branch, purge, return _docheckout(
sharebase, optimes, behaviors, networkattemptlimit, ui,
networkattempts=networkattempts, url,
sparse_profile=sparse_profile, dest,
noupdate=noupdate) upstream,
revision,
branch,
purge,
sharebase,
optimes,
behaviors,
networkattemptlimit,
networkattempts=networkattempts,
sparse_profile=sparse_profile,
noupdate=noupdate,
)
@contextlib.contextmanager @contextlib.contextmanager
def timeit(op, behavior): def timeit(op, behavior):
@ -275,12 +344,11 @@ def _docheckout(ui, url, dest, upstream, revision, branch, purge, sharebase,
elapsed = time.time() - start elapsed = time.time() - start
if errored: if errored:
op += '_errored' op += "_errored"
optimes.append((op, elapsed)) optimes.append((op, elapsed))
ui.write(b'ensuring %s@%s is available at %s\n' % (url, revision or branch, ui.write(b"ensuring %s@%s is available at %s\n" % (url, revision or branch, dest))
dest))
# We assume that we're the only process on the machine touching the # We assume that we're the only process on the machine touching the
# repository paths that we were told to use. This means our recovery # repository paths that we were told to use. This means our recovery
@ -293,70 +361,75 @@ def _docheckout(ui, url, dest, upstream, revision, branch, purge, sharebase,
destvfs = vfs.vfs(dest, audit=False, realpath=True) destvfs = vfs.vfs(dest, audit=False, realpath=True)
def deletesharedstore(path=None): def deletesharedstore(path=None):
storepath = path or destvfs.read(b'.hg/sharedpath').strip() storepath = path or destvfs.read(b".hg/sharedpath").strip()
if storepath.endswith(b'.hg'): if storepath.endswith(b".hg"):
storepath = os.path.dirname(storepath) storepath = os.path.dirname(storepath)
storevfs = vfs.vfs(storepath, audit=False) storevfs = vfs.vfs(storepath, audit=False)
storevfs.rmtree(forcibly=True) storevfs.rmtree(forcibly=True)
if destvfs.exists() and not destvfs.exists(b'.hg'): if destvfs.exists() and not destvfs.exists(b".hg"):
raise error.Abort(b'destination exists but no .hg directory') raise error.Abort(b"destination exists but no .hg directory")
# Refuse to enable sparse checkouts on existing checkouts. The reasoning # Refuse to enable sparse checkouts on existing checkouts. The reasoning
# here is that another consumer of this repo may not be sparse aware. If we # here is that another consumer of this repo may not be sparse aware. If we
# enabled sparse, we would lock them out. # enabled sparse, we would lock them out.
if destvfs.exists() and sparse_profile and not destvfs.exists(b'.hg/sparse'): if destvfs.exists() and sparse_profile and not destvfs.exists(b".hg/sparse"):
raise error.Abort(b'cannot enable sparse profile on existing ' raise error.Abort(
b'non-sparse checkout', b"cannot enable sparse profile on existing " b"non-sparse checkout",
hint=b'use a separate working directory to use sparse') hint=b"use a separate working directory to use sparse",
)
# And the other direction for symmetry. # And the other direction for symmetry.
if not sparse_profile and destvfs.exists(b'.hg/sparse'): if not sparse_profile and destvfs.exists(b".hg/sparse"):
raise error.Abort(b'cannot use non-sparse checkout on existing sparse ' raise error.Abort(
b'checkout', b"cannot use non-sparse checkout on existing sparse " b"checkout",
hint=b'use a separate working directory to use sparse') hint=b"use a separate working directory to use sparse",
)
# Require checkouts to be tied to shared storage because efficiency. # Require checkouts to be tied to shared storage because efficiency.
if destvfs.exists(b'.hg') and not destvfs.exists(b'.hg/sharedpath'): if destvfs.exists(b".hg") and not destvfs.exists(b".hg/sharedpath"):
ui.warn(b'(destination is not shared; deleting)\n') ui.warn(b"(destination is not shared; deleting)\n")
with timeit('remove_unshared_dest', 'remove-wdir'): with timeit("remove_unshared_dest", "remove-wdir"):
destvfs.rmtree(forcibly=True) destvfs.rmtree(forcibly=True)
# Verify the shared path exists and is using modern pooled storage. # Verify the shared path exists and is using modern pooled storage.
if destvfs.exists(b'.hg/sharedpath'): if destvfs.exists(b".hg/sharedpath"):
storepath = destvfs.read(b'.hg/sharedpath').strip() storepath = destvfs.read(b".hg/sharedpath").strip()
ui.write(b'(existing repository shared store: %s)\n' % storepath) ui.write(b"(existing repository shared store: %s)\n" % storepath)
if not os.path.exists(storepath): if not os.path.exists(storepath):
ui.warn(b'(shared store does not exist; deleting destination)\n') ui.warn(b"(shared store does not exist; deleting destination)\n")
with timeit('removed_missing_shared_store', 'remove-wdir'): with timeit("removed_missing_shared_store", "remove-wdir"):
destvfs.rmtree(forcibly=True) destvfs.rmtree(forcibly=True)
elif not re.search(br'[a-f0-9]{40}/\.hg$', storepath.replace(b'\\', b'/')): elif not re.search(b"[a-f0-9]{40}/\.hg$", storepath.replace(b"\\", b"/")):
ui.warn(b'(shared store does not belong to pooled storage; ' ui.warn(
b'deleting destination to improve efficiency)\n') b"(shared store does not belong to pooled storage; "
with timeit('remove_unpooled_store', 'remove-wdir'): b"deleting destination to improve efficiency)\n"
)
with timeit("remove_unpooled_store", "remove-wdir"):
destvfs.rmtree(forcibly=True) destvfs.rmtree(forcibly=True)
if destvfs.isfileorlink(b'.hg/wlock'): if destvfs.isfileorlink(b".hg/wlock"):
ui.warn(b'(dest has an active working directory lock; assuming it is ' ui.warn(
b'left over from a previous process and that the destination ' b"(dest has an active working directory lock; assuming it is "
b'is corrupt; deleting it just to be sure)\n') b"left over from a previous process and that the destination "
with timeit('remove_locked_wdir', 'remove-wdir'): b"is corrupt; deleting it just to be sure)\n"
)
with timeit("remove_locked_wdir", "remove-wdir"):
destvfs.rmtree(forcibly=True) destvfs.rmtree(forcibly=True)
def handlerepoerror(e): def handlerepoerror(e):
if pycompat.bytestr(e) == _(b'abandoned transaction found'): if pycompat.bytestr(e) == _(b"abandoned transaction found"):
ui.warn(b'(abandoned transaction found; trying to recover)\n') ui.warn(b"(abandoned transaction found; trying to recover)\n")
repo = hg.repository(ui, dest) repo = hg.repository(ui, dest)
if not repo.recover(): if not repo.recover():
ui.warn(b'(could not recover repo state; ' ui.warn(b"(could not recover repo state; " b"deleting shared store)\n")
b'deleting shared store)\n') with timeit("remove_unrecovered_shared_store", "remove-store"):
with timeit('remove_unrecovered_shared_store', 'remove-store'):
deletesharedstore() deletesharedstore()
ui.warn(b'(attempting checkout from beginning)\n') ui.warn(b"(attempting checkout from beginning)\n")
return callself() return callself()
raise raise
@ -366,11 +439,14 @@ def _docheckout(ui, url, dest, upstream, revision, branch, purge, sharebase,
def handlenetworkfailure(): def handlenetworkfailure():
if networkattempts[0] >= networkattemptlimit: if networkattempts[0] >= networkattemptlimit:
raise error.Abort(b'reached maximum number of network attempts; ' raise error.Abort(
b'giving up\n') b"reached maximum number of network attempts; " b"giving up\n"
)
ui.warn(b'(retrying after network failure on attempt %d of %d)\n' % ui.warn(
(networkattempts[0], networkattemptlimit)) b"(retrying after network failure on attempt %d of %d)\n"
% (networkattempts[0], networkattemptlimit)
)
# Do a backoff on retries to mitigate the thundering herd # Do a backoff on retries to mitigate the thundering herd
# problem. This is an exponential backoff with a multipler # problem. This is an exponential backoff with a multipler
@ -380,10 +456,10 @@ def _docheckout(ui, url, dest, upstream, revision, branch, purge, sharebase,
# 2) 5.5 - 9.5 # 2) 5.5 - 9.5
# 3) 11.5 - 15.5 # 3) 11.5 - 15.5
backoff = (2 ** networkattempts[0] - 1) * 1.5 backoff = (2 ** networkattempts[0] - 1) * 1.5
jittermin = ui.configint(b'robustcheckout', b'retryjittermin', 1000) jittermin = ui.configint(b"robustcheckout", b"retryjittermin", 1000)
jittermax = ui.configint(b'robustcheckout', b'retryjittermax', 5000) jittermax = ui.configint(b"robustcheckout", b"retryjittermax", 5000)
backoff += float(random.randint(jittermin, jittermax)) / 1000.0 backoff += float(random.randint(jittermin, jittermax)) / 1000.0
ui.warn(b'(waiting %.2fs before retry)\n' % backoff) ui.warn(b"(waiting %.2fs before retry)\n" % backoff)
time.sleep(backoff) time.sleep(backoff)
networkattempts[0] += 1 networkattempts[0] += 1
@ -394,19 +470,19 @@ def _docheckout(ui, url, dest, upstream, revision, branch, purge, sharebase,
Returns True if caller should call ``callself()`` to retry. Returns True if caller should call ``callself()`` to retry.
""" """
if isinstance(e, error.Abort): if isinstance(e, error.Abort):
if e.args[0] == _(b'repository is unrelated'): if e.args[0] == _(b"repository is unrelated"):
ui.warn(b'(repository is unrelated; deleting)\n') ui.warn(b"(repository is unrelated; deleting)\n")
destvfs.rmtree(forcibly=True) destvfs.rmtree(forcibly=True)
return True return True
elif e.args[0].startswith(_(b'stream ended unexpectedly')): elif e.args[0].startswith(_(b"stream ended unexpectedly")):
ui.warn(b'%s\n' % e.args[0]) ui.warn(b"%s\n" % e.args[0])
# Will raise if failure limit reached. # Will raise if failure limit reached.
handlenetworkfailure() handlenetworkfailure()
return True return True
# TODO test this branch # TODO test this branch
elif isinstance(e, error.ResponseError): elif isinstance(e, error.ResponseError):
if e.args[0].startswith(_(b'unexpected response from remote server:')): if e.args[0].startswith(_(b"unexpected response from remote server:")):
ui.warn(b'(unexpected response from remote server; retrying)\n') ui.warn(b"(unexpected response from remote server; retrying)\n")
destvfs.rmtree(forcibly=True) destvfs.rmtree(forcibly=True)
# Will raise if failure limit reached. # Will raise if failure limit reached.
handlenetworkfailure() handlenetworkfailure()
@ -415,20 +491,28 @@ def _docheckout(ui, url, dest, upstream, revision, branch, purge, sharebase,
# Assume all SSL errors are due to the network, as Mercurial # Assume all SSL errors are due to the network, as Mercurial
# should convert non-transport errors like cert validation failures # should convert non-transport errors like cert validation failures
# to error.Abort. # to error.Abort.
ui.warn(b'ssl error: %s\n' % e) ui.warn(b"ssl error: %s\n" % pycompat.bytestr(str(e)))
handlenetworkfailure() handlenetworkfailure()
return True return True
elif isinstance(e, urllibcompat.urlerr.urlerror): elif isinstance(e, urllibcompat.urlerr.urlerror):
if isinstance(e.reason, socket.error): if isinstance(e.reason, socket.error):
ui.warn(b'socket error: %s\n' % pycompat.bytestr(e.reason)) ui.warn(b"socket error: %s\n" % pycompat.bytestr(str(e.reason)))
handlenetworkfailure() handlenetworkfailure()
return True return True
else: else:
ui.warn(b'unhandled URLError; reason type: %s; value: %s\n' % ( ui.warn(
e.reason.__class__.__name__, e.reason)) b"unhandled URLError; reason type: %s; value: %s\n"
% (
pycompat.bytestr(e.reason.__class__.__name__),
pycompat.bytestr(str(e.reason)),
)
)
else: else:
ui.warn(b'unhandled exception during network operation; type: %s; ' ui.warn(
b'value: %s\n' % (e.__class__.__name__, e)) b"unhandled exception during network operation; type: %s; "
b"value: %s\n"
% (pycompat.bytestr(e.__class__.__name__), pycompat.bytestr(str(e)))
)
return False return False
@ -440,59 +524,69 @@ def _docheckout(ui, url, dest, upstream, revision, branch, purge, sharebase,
try: try:
clonepeer = hg.peer(ui, {}, cloneurl) clonepeer = hg.peer(ui, {}, cloneurl)
rootnode = peerlookup(clonepeer, b'0') rootnode = peerlookup(clonepeer, b"0")
except error.RepoLookupError: except error.RepoLookupError:
raise error.Abort(b'unable to resolve root revision from clone ' raise error.Abort(b"unable to resolve root revision from clone " b"source")
b'source')
except (error.Abort, ssl.SSLError, urllibcompat.urlerr.urlerror) as e: except (error.Abort, ssl.SSLError, urllibcompat.urlerr.urlerror) as e:
if handlepullerror(e): if handlepullerror(e):
return callself() return callself()
raise raise
if rootnode == nullid: if rootnode == nullid:
raise error.Abort(b'source repo appears to be empty') raise error.Abort(b"source repo appears to be empty")
storepath = os.path.join(sharebase, hex(rootnode)) storepath = os.path.join(sharebase, hex(rootnode))
storevfs = vfs.vfs(storepath, audit=False) storevfs = vfs.vfs(storepath, audit=False)
if storevfs.isfileorlink(b'.hg/store/lock'): if storevfs.isfileorlink(b".hg/store/lock"):
ui.warn(b'(shared store has an active lock; assuming it is left ' ui.warn(
b'over from a previous process and that the store is ' b"(shared store has an active lock; assuming it is left "
b'corrupt; deleting store and destination just to be ' b"over from a previous process and that the store is "
b'sure)\n') b"corrupt; deleting store and destination just to be "
b"sure)\n"
)
if destvfs.exists(): if destvfs.exists():
with timeit('remove_dest_active_lock', 'remove-wdir'): with timeit("remove_dest_active_lock", "remove-wdir"):
destvfs.rmtree(forcibly=True) destvfs.rmtree(forcibly=True)
with timeit('remove_shared_store_active_lock', 'remove-store'): with timeit("remove_shared_store_active_lock", "remove-store"):
storevfs.rmtree(forcibly=True) storevfs.rmtree(forcibly=True)
if storevfs.exists() and not storevfs.exists(b'.hg/requires'): if storevfs.exists() and not storevfs.exists(b".hg/requires"):
ui.warn(b'(shared store missing requires file; this is a really ' ui.warn(
b'odd failure; deleting store and destination)\n') b"(shared store missing requires file; this is a really "
b"odd failure; deleting store and destination)\n"
)
if destvfs.exists(): if destvfs.exists():
with timeit('remove_dest_no_requires', 'remove-wdir'): with timeit("remove_dest_no_requires", "remove-wdir"):
destvfs.rmtree(forcibly=True) destvfs.rmtree(forcibly=True)
with timeit('remove_shared_store_no_requires', 'remove-store'): with timeit("remove_shared_store_no_requires", "remove-store"):
storevfs.rmtree(forcibly=True) storevfs.rmtree(forcibly=True)
if storevfs.exists(b'.hg/requires'): if storevfs.exists(b".hg/requires"):
requires = set(storevfs.read(b'.hg/requires').splitlines()) requires = set(storevfs.read(b".hg/requires").splitlines())
# "share-safe" (enabled by default as of hg 6.1) moved most
# requirements to a new file, so we need to look there as well to avoid
# deleting and re-cloning each time
if b"share-safe" in requires:
requires |= set(storevfs.read(b".hg/store/requires").splitlines())
# FUTURE when we require generaldelta, this is where we can check # FUTURE when we require generaldelta, this is where we can check
# for that. # for that.
required = {b'dotencode', b'fncache'} required = {b"dotencode", b"fncache"}
missing = required - requires missing = required - requires
if missing: if missing:
ui.warn(b'(shared store missing requirements: %s; deleting ' ui.warn(
b'store and destination to ensure optimal behavior)\n' % b"(shared store missing requirements: %s; deleting "
b', '.join(sorted(missing))) b"store and destination to ensure optimal behavior)\n"
% b", ".join(sorted(missing))
)
if destvfs.exists(): if destvfs.exists():
with timeit('remove_dest_missing_requires', 'remove-wdir'): with timeit("remove_dest_missing_requires", "remove-wdir"):
destvfs.rmtree(forcibly=True) destvfs.rmtree(forcibly=True)
with timeit('remove_shared_store_missing_requires', 'remove-store'): with timeit("remove_shared_store_missing_requires", "remove-store"):
storevfs.rmtree(forcibly=True) storevfs.rmtree(forcibly=True)
created = False created = False
@ -500,7 +594,7 @@ def _docheckout(ui, url, dest, upstream, revision, branch, purge, sharebase,
if not destvfs.exists(): if not destvfs.exists():
# Ensure parent directories of destination exist. # Ensure parent directories of destination exist.
# Mercurial 3.8 removed ensuredirs and made makedirs race safe. # Mercurial 3.8 removed ensuredirs and made makedirs race safe.
if util.safehasattr(util, 'ensuredirs'): if util.safehasattr(util, "ensuredirs"):
makedirs = util.ensuredirs makedirs = util.ensuredirs
else: else:
makedirs = util.makedirs makedirs = util.makedirs
@ -509,17 +603,23 @@ def _docheckout(ui, url, dest, upstream, revision, branch, purge, sharebase,
makedirs(sharebase, notindexed=True) makedirs(sharebase, notindexed=True)
if upstream: if upstream:
ui.write(b'(cloning from upstream repo %s)\n' % upstream) ui.write(b"(cloning from upstream repo %s)\n" % upstream)
if not storevfs.exists(): if not storevfs.exists():
behaviors.add(b'create-store') behaviors.add(b"create-store")
try: try:
with timeit('clone', 'clone'): with timeit("clone", "clone"):
shareopts = {b'pool': sharebase, b'mode': b'identity'} shareopts = {b"pool": sharebase, b"mode": b"identity"}
res = hg.clone(ui, {}, clonepeer, dest=dest, update=False, res = hg.clone(
shareopts=shareopts, ui,
stream=True) {},
clonepeer,
dest=dest,
update=False,
shareopts=shareopts,
stream=True,
)
except (error.Abort, ssl.SSLError, urllibcompat.urlerr.urlerror) as e: except (error.Abort, ssl.SSLError, urllibcompat.urlerr.urlerror) as e:
if handlepullerror(e): if handlepullerror(e):
return callself() return callself()
@ -527,18 +627,18 @@ def _docheckout(ui, url, dest, upstream, revision, branch, purge, sharebase,
except error.RepoError as e: except error.RepoError as e:
return handlerepoerror(e) return handlerepoerror(e)
except error.RevlogError as e: except error.RevlogError as e:
ui.warn(b'(repo corruption: %s; deleting shared store)\n' % e) ui.warn(b"(repo corruption: %s; deleting shared store)\n" % e)
with timeit('remove_shared_store_revlogerror', 'remote-store'): with timeit("remove_shared_store_revlogerror", "remote-store"):
deletesharedstore() deletesharedstore()
return callself() return callself()
# TODO retry here. # TODO retry here.
if res is None: if res is None:
raise error.Abort(b'clone failed') raise error.Abort(b"clone failed")
# Verify it is using shared pool storage. # Verify it is using shared pool storage.
if not destvfs.exists(b'.hg/sharedpath'): if not destvfs.exists(b".hg/sharedpath"):
raise error.Abort(b'clone did not create a shared repo') raise error.Abort(b"clone did not create a shared repo")
created = True created = True
@ -559,15 +659,16 @@ def _docheckout(ui, url, dest, upstream, revision, branch, purge, sharebase,
if ctx: if ctx:
if not ctx.hex().startswith(revision): if not ctx.hex().startswith(revision):
raise error.Abort(b'--revision argument is ambiguous', raise error.Abort(
hint=b'must be the first 12+ characters of a ' b"--revision argument is ambiguous",
b'SHA-1 fragment') hint=b"must be the first 12+ characters of a " b"SHA-1 fragment",
)
checkoutrevision = ctx.hex() checkoutrevision = ctx.hex()
havewantedrev = True havewantedrev = True
if not havewantedrev: if not havewantedrev:
ui.write(b'(pulling to obtain %s)\n' % (revision or branch,)) ui.write(b"(pulling to obtain %s)\n" % (revision or branch,))
remote = None remote = None
try: try:
@ -575,17 +676,18 @@ def _docheckout(ui, url, dest, upstream, revision, branch, purge, sharebase,
pullrevs = [peerlookup(remote, revision or branch)] pullrevs = [peerlookup(remote, revision or branch)]
checkoutrevision = hex(pullrevs[0]) checkoutrevision = hex(pullrevs[0])
if branch: if branch:
ui.warn(b'(remote resolved %s to %s; ' ui.warn(
b'result is not deterministic)\n' % b"(remote resolved %s to %s; "
(branch, checkoutrevision)) b"result is not deterministic)\n" % (branch, checkoutrevision)
)
if checkoutrevision in repo: if checkoutrevision in repo:
ui.warn(b'(revision already present locally; not pulling)\n') ui.warn(b"(revision already present locally; not pulling)\n")
else: else:
with timeit('pull', 'pull'): with timeit("pull", "pull"):
pullop = exchange.pull(repo, remote, heads=pullrevs) pullop = exchange.pull(repo, remote, heads=pullrevs)
if not pullop.rheads: if not pullop.rheads:
raise error.Abort(b'unable to pull requested revision') raise error.Abort(b"unable to pull requested revision")
except (error.Abort, ssl.SSLError, urllibcompat.urlerr.urlerror) as e: except (error.Abort, ssl.SSLError, urllibcompat.urlerr.urlerror) as e:
if handlepullerror(e): if handlepullerror(e):
return callself() return callself()
@ -593,7 +695,7 @@ def _docheckout(ui, url, dest, upstream, revision, branch, purge, sharebase,
except error.RepoError as e: except error.RepoError as e:
return handlerepoerror(e) return handlerepoerror(e)
except error.RevlogError as e: except error.RevlogError as e:
ui.warn(b'(repo corruption: %s; deleting shared store)\n' % e) ui.warn(b"(repo corruption: %s; deleting shared store)\n" % e)
deletesharedstore() deletesharedstore()
return callself() return callself()
finally: finally:
@ -605,47 +707,46 @@ def _docheckout(ui, url, dest, upstream, revision, branch, purge, sharebase,
# Avoid any working directory manipulations if `-U`/`--noupdate` was passed # Avoid any working directory manipulations if `-U`/`--noupdate` was passed
if noupdate: if noupdate:
ui.write(b'(skipping update since `-U` was passed)\n') ui.write(b"(skipping update since `-U` was passed)\n")
return None return None
# Purge if requested. We purge before update because this way we're # Purge if requested. We purge before update because this way we're
# guaranteed to not have conflicts on `hg update`. # guaranteed to not have conflicts on `hg update`.
if purge and not created: if purge and not created:
ui.write(b'(purging working directory)\n') ui.write(b"(purging working directory)\n")
purgeext = extensions.find(b'purge') purge = getattr(commands, "purge", None)
if not purge:
purge = extensions.find(b"purge").purge
# Mercurial 4.3 doesn't purge files outside the sparse checkout. # Mercurial 4.3 doesn't purge files outside the sparse checkout.
# See https://bz.mercurial-scm.org/show_bug.cgi?id=5626. Force # See https://bz.mercurial-scm.org/show_bug.cgi?id=5626. Force
# purging by monkeypatching the sparse matcher. # purging by monkeypatching the sparse matcher.
try: try:
old_sparse_fn = getattr(repo.dirstate, '_sparsematchfn', None) old_sparse_fn = getattr(repo.dirstate, "_sparsematchfn", None)
if old_sparse_fn is not None: if old_sparse_fn is not None:
# TRACKING hg50 repo.dirstate._sparsematchfn = lambda: matchmod.always()
# Arguments passed to `matchmod.always` were unused and have been removed
if util.versiontuple(n=2) >= (5, 0):
repo.dirstate._sparsematchfn = lambda: matchmod.always()
else:
repo.dirstate._sparsematchfn = lambda: matchmod.always(repo.root, '')
with timeit('purge', 'purge'): with timeit("purge", "purge"):
if purgeext.purge(ui, repo, all=True, abort_on_err=True, if purge(
# The function expects all arguments to be ui,
# defined. repo,
**{'print': None, all=True,
'print0': None, abort_on_err=True,
'dirs': None, # The function expects all arguments to be
'files': None}): # defined.
raise error.Abort(b'error purging') **{"print": None, "print0": None, "dirs": None, "files": None}
):
raise error.Abort(b"error purging")
finally: finally:
if old_sparse_fn is not None: if old_sparse_fn is not None:
repo.dirstate._sparsematchfn = old_sparse_fn repo.dirstate._sparsematchfn = old_sparse_fn
# Update the working directory. # Update the working directory.
if repo[b'.'].node() == nullid: if repo[b"."].node() == nullid:
behaviors.add('empty-wdir') behaviors.add("empty-wdir")
else: else:
behaviors.add('populated-wdir') behaviors.add("populated-wdir")
if sparse_profile: if sparse_profile:
sparsemod = getsparse() sparsemod = getsparse()
@ -655,58 +756,70 @@ def _docheckout(ui, url, dest, upstream, revision, branch, purge, sharebase,
try: try:
repo.filectx(sparse_profile, changeid=checkoutrevision).data() repo.filectx(sparse_profile, changeid=checkoutrevision).data()
except error.ManifestLookupError: except error.ManifestLookupError:
raise error.Abort(b'sparse profile %s does not exist at revision ' raise error.Abort(
b'%s' % (sparse_profile, checkoutrevision)) b"sparse profile %s does not exist at revision "
b"%s" % (sparse_profile, checkoutrevision)
)
# TRACKING hg48 - parseconfig takes `action` param old_config = sparsemod.parseconfig(
if util.versiontuple(n=2) >= (4, 8): repo.ui, repo.vfs.tryread(b"sparse"), b"sparse"
old_config = sparsemod.parseconfig(repo.ui, repo.vfs.tryread(b'sparse'), b'sparse') )
else:
old_config = sparsemod.parseconfig(repo.ui, repo.vfs.tryread(b'sparse'))
old_includes, old_excludes, old_profiles = old_config old_includes, old_excludes, old_profiles = old_config
if old_profiles == {sparse_profile} and not old_includes and not \ if old_profiles == {sparse_profile} and not old_includes and not old_excludes:
old_excludes: ui.write(
ui.write(b'(sparse profile %s already set; no need to update ' b"(sparse profile %s already set; no need to update "
b'sparse config)\n' % sparse_profile) b"sparse config)\n" % sparse_profile
)
else: else:
if old_includes or old_excludes or old_profiles: if old_includes or old_excludes or old_profiles:
ui.write(b'(replacing existing sparse config with profile ' ui.write(
b'%s)\n' % sparse_profile) b"(replacing existing sparse config with profile "
b"%s)\n" % sparse_profile
)
else: else:
ui.write(b'(setting sparse config to profile %s)\n' % ui.write(b"(setting sparse config to profile %s)\n" % sparse_profile)
sparse_profile)
# If doing an incremental update, this will perform two updates: # If doing an incremental update, this will perform two updates:
# one to change the sparse profile and another to update to the new # one to change the sparse profile and another to update to the new
# revision. This is not desired. But there's not a good API in # revision. This is not desired. But there's not a good API in
# Mercurial to do this as one operation. # Mercurial to do this as one operation.
with repo.wlock(), timeit('sparse_update_config', with repo.wlock(), repo.dirstate.parentchange(), timeit(
'sparse-update-config'): "sparse_update_config", "sparse-update-config"
fcounts = map(len, sparsemod._updateconfigandrefreshwdir( ):
repo, [], [], [sparse_profile], force=True)) # pylint --py3k: W1636
fcounts = list(
map(
len,
sparsemod._updateconfigandrefreshwdir(
repo, [], [], [sparse_profile], force=True
),
)
)
repo.ui.status(b'%d files added, %d files dropped, ' repo.ui.status(
b'%d files conflicting\n' % tuple(fcounts)) b"%d files added, %d files dropped, "
b"%d files conflicting\n" % tuple(fcounts)
)
ui.write(b'(sparse refresh complete)\n') ui.write(b"(sparse refresh complete)\n")
op = 'update_sparse' if sparse_profile else 'update' op = "update_sparse" if sparse_profile else "update"
behavior = 'update-sparse' if sparse_profile else 'update' behavior = "update-sparse" if sparse_profile else "update"
with timeit(op, behavior): with timeit(op, behavior):
if commands.update(ui, repo, rev=checkoutrevision, clean=True): if commands.update(ui, repo, rev=checkoutrevision, clean=True):
raise error.Abort(b'error updating') raise error.Abort(b"error updating")
ui.write(b'updated to %s\n' % checkoutrevision) ui.write(b"updated to %s\n" % checkoutrevision)
return None return None
def extsetup(ui): def extsetup(ui):
# Ensure required extensions are loaded. # Ensure required extensions are loaded.
for ext in (b'purge', b'share'): for ext in (b"purge", b"share"):
try: try:
extensions.find(ext) extensions.find(ext)
except KeyError: except KeyError:

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -157,6 +157,10 @@ def make_task(config, jobs):
"tier": 1, "tier": 1,
} }
if job.get("secret", None):
task["scopes"] = ["secrets:get:" + job.get("secret")]
task["worker"]["taskcluster-proxy"] = True
if not taskgraph.fast: if not taskgraph.fast:
cache_name = task["label"].replace(f"{config.kind}-", "", 1) cache_name = task["label"].replace(f"{config.kind}-", "", 1)
@ -282,8 +286,14 @@ def create_fetch_url_task(config, name, fetch):
schema={ schema={
Required("repo"): str, Required("repo"): str,
Required("revision"): str, Required("revision"): str,
Optional("include-dot-git"): bool,
Optional("artifact-name"): str, Optional("artifact-name"): str,
Optional("path-prefix"): str, Optional("path-prefix"): str,
# ssh-key is a taskcluster secret path (e.g. project/civet/github-deploy-key)
# In the secret dictionary, the key should be specified as
# "ssh_privkey": "-----BEGIN OPENSSH PRIVATE KEY-----\nkfksnb3jc..."
# n.b. The OpenSSH private key file format requires a newline at the end of the file.
Optional("ssh-key"): str,
}, },
) )
def create_git_fetch_task(config, name, fetch): def create_git_fetch_task(config, name, fetch):
@ -307,8 +317,19 @@ def create_git_fetch_task(config, name, fetch):
"/builds/worker/artifacts/%s" % artifact_name, "/builds/worker/artifacts/%s" % artifact_name,
] ]
ssh_key = fetch.get("ssh-key")
if ssh_key:
args.append("--ssh-key-secret")
args.append(ssh_key)
digest_data = [fetch["revision"], path_prefix, artifact_name]
if fetch.get("include-dot-git", False):
args.append("--include-dot-git")
digest_data.append(".git")
return { return {
"command": args, "command": args,
"artifact_name": artifact_name, "artifact_name": artifact_name,
"digest_data": [fetch["revision"], path_prefix, artifact_name], "digest_data": digest_data,
"secret": ssh_key,
} }

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

@ -79,6 +79,7 @@ job_description_schema = Schema(
Required("artifact"): str, Required("artifact"): str,
Optional("dest"): str, Optional("dest"): str,
Optional("extract"): bool, Optional("extract"): bool,
Optional("verify-hash"): bool,
}, },
], ],
}, },
@ -298,10 +299,12 @@ def use_fetches(config, jobs):
path = artifact path = artifact
dest = None dest = None
extract = True extract = True
verify_hash = False
else: else:
path = artifact["artifact"] path = artifact["artifact"]
dest = artifact.get("dest") dest = artifact.get("dest")
extract = artifact.get("extract", True) extract = artifact.get("extract", True)
verify_hash = artifact.get("verify-hash", False)
fetch = { fetch = {
"artifact": f"{prefix}/{path}", "artifact": f"{prefix}/{path}",
@ -310,6 +313,8 @@ def use_fetches(config, jobs):
} }
if dest is not None: if dest is not None:
fetch["dest"] = dest fetch["dest"] = dest
if verify_hash:
fetch["verify-hash"] = verify_hash
job_fetches.append(fetch) job_fetches.append(fetch)
job_artifact_prefixes = { job_artifact_prefixes = {

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

@ -191,11 +191,6 @@ def support_vcs_checkout(config, job, taskdesc, repo_configs, sparse=False):
if repo_config.ssh_secret_name: if repo_config.ssh_secret_name:
taskdesc["scopes"].append(f"secrets:get:{repo_config.ssh_secret_name}") taskdesc["scopes"].append(f"secrets:get:{repo_config.ssh_secret_name}")
if any(repo_config.type == "hg" for repo_config in repo_configs.values()):
# Give task access to hgfingerprint secret so it can pin the certificate
# for hg.mozilla.org.
taskdesc["scopes"].append("secrets:get:project/taskcluster/gecko/hgfingerprint")
# only some worker platforms have taskcluster-proxy enabled # only some worker platforms have taskcluster-proxy enabled
if job["worker"]["implementation"] in ("docker-worker",): if job["worker"]["implementation"] in ("docker-worker",):
taskdesc["worker"]["taskcluster-proxy"] = True taskdesc["worker"]["taskcluster-proxy"] = True

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

@ -157,7 +157,6 @@ def docker_worker_run_task(config, job, taskdesc):
if isinstance(run_command, str) or isinstance(run_command, dict): if isinstance(run_command, str) or isinstance(run_command, dict):
exec_cmd = EXEC_COMMANDS[run.pop("exec-with", "bash")] exec_cmd = EXEC_COMMANDS[run.pop("exec-with", "bash")]
run_command = exec_cmd + [run_command] run_command = exec_cmd + [run_command]
command.append("--fetch-hgfingerprint")
if run["run-as-root"]: if run["run-as-root"]:
command.extend(("--user", "root", "--group", "root")) command.extend(("--user", "root", "--group", "root"))
command.append("--") command.append("--")

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

@ -268,6 +268,7 @@ def verify_index(config, index):
Required("loopback-audio"): bool, Required("loopback-audio"): bool,
Required("docker-in-docker"): bool, # (aka 'dind') Required("docker-in-docker"): bool, # (aka 'dind')
Required("privileged"): bool, Required("privileged"): bool,
Required("disable-seccomp"): bool,
# Paths to Docker volumes. # Paths to Docker volumes.
# #
# For in-tree Docker images, volumes can be parsed from Dockerfile. # For in-tree Docker images, volumes can be parsed from Dockerfile.
@ -406,6 +407,10 @@ def build_docker_worker_payload(config, task, task_def):
capabilities["privileged"] = True capabilities["privileged"] = True
task_def["scopes"].append("docker-worker:capability:privileged") task_def["scopes"].append("docker-worker:capability:privileged")
if worker.get("disable-seccomp"):
capabilities["disableSeccomp"] = True
task_def["scopes"].append("docker-worker:capability:disableSeccomp")
task_def["payload"] = payload = { task_def["payload"] = payload = {
"image": image, "image": image,
"env": worker["env"], "env": worker["env"],
@ -831,6 +836,7 @@ def set_defaults(config, tasks):
worker.setdefault("loopback-audio", False) worker.setdefault("loopback-audio", False)
worker.setdefault("docker-in-docker", False) worker.setdefault("docker-in-docker", False)
worker.setdefault("privileged", False) worker.setdefault("privileged", False)
worker.setdefault("disable-seccomp", False)
worker.setdefault("volumes", []) worker.setdefault("volumes", [])
worker.setdefault("env", {}) worker.setdefault("env", {})
if "caches" in worker: if "caches" in worker:
@ -992,7 +998,7 @@ def build_task(config, tasks):
branch_rev = get_branch_rev(config) branch_rev = get_branch_rev(config)
if config.params["tasks_for"] == "github-pull-request": if config.params["tasks_for"].startswith("github-pull-request"):
# In the past we used `project` for this, but that ends up being # In the past we used `project` for this, but that ends up being
# set to the repository name of the _head_ repo, which is not correct # set to the repository name of the _head_ repo, which is not correct
# (and causes scope issues) if it doesn't match the name of the # (and causes scope issues) if it doesn't match the name of the