зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1759030 - Vendor `taskcluster-taskgraph` at `3.5.1`, r=ahochheiden
Differential Revision: https://phabricator.services.mozilla.com/D161056
This commit is contained in:
Родитель
71b86860bf
Коммит
55811f8006
|
@ -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(';')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -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"},
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Загрузка…
Ссылка в новой задаче