taskcluster-taskgraph 19.2.0__tar.gz → 20.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/.pre-commit-config.yaml +3 -3
  2. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/CHANGELOG.md +19 -0
  3. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/PKG-INFO +1 -1
  4. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/reference/migrations.rst +8 -0
  5. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/pyproject.toml +1 -1
  6. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/generator.py +46 -26
  7. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/run-task/run-task +4 -4
  8. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/transforms/task.py +4 -0
  9. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/schema.py +89 -24
  10. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/vcs.py +5 -0
  11. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/config.yml +1 -0
  12. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/kinds/test/linux.yml +20 -0
  13. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_generator.py +30 -2
  14. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_scripts_run_task.py +2 -2
  15. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_schema.py +52 -17
  16. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_vcs.py +54 -0
  17. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/uv.lock +2 -2
  18. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/.codespell-ignore-words.txt +0 -0
  19. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/.github/CODEOWNERS +0 -0
  20. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/.github/dependabot.yml +0 -0
  21. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/.github/workflows/codeql-analysis.yml +0 -0
  22. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/.github/workflows/pre-commit-autoupdate.yml +0 -0
  23. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/.github/workflows/pre-commit.yml +0 -0
  24. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/.github/workflows/pypi-publish.yml +0 -0
  25. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/.gitignore +0 -0
  26. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/.readthedocs.yaml +0 -0
  27. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/.taskcluster.yml +0 -0
  28. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/.yamllint +0 -0
  29. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/CODE_OF_CONDUCT.md +0 -0
  30. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/CONTRIBUTING.rst +0 -0
  31. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/LICENSE +0 -0
  32. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/Makefile +0 -0
  33. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/README.rst +0 -0
  34. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/concepts/index.rst +0 -0
  35. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/concepts/kind.rst +0 -0
  36. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/concepts/loading.rst +0 -0
  37. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/concepts/optimization.rst +0 -0
  38. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/concepts/overview.rst +0 -0
  39. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/concepts/scopes.rst +0 -0
  40. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/concepts/task-graphs.rst +0 -0
  41. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/concepts/transforms.rst +0 -0
  42. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/conf.py +0 -0
  43. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/contributing.rst +0 -0
  44. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/glossary.rst +0 -0
  45. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/howto/bootstrap-taskgraph.rst +0 -0
  46. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/howto/create-actions.rst +0 -0
  47. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/howto/create-tasks.rst +0 -0
  48. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/howto/debugging.rst +0 -0
  49. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/howto/docker.rst +0 -0
  50. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/howto/index.rst +0 -0
  51. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/howto/load-task-locally.rst +0 -0
  52. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/howto/resolve-keyed-by.rst +0 -0
  53. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/howto/run-locally.rst +0 -0
  54. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/howto/send-notifications.rst +0 -0
  55. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/howto/use-fetches.rst +0 -0
  56. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/index.rst +0 -0
  57. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/reference/cli.rst +0 -0
  58. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/reference/index.rst +0 -0
  59. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/reference/optimization-strategies.rst +0 -0
  60. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/reference/parameters.rst +0 -0
  61. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/reference/source/modules.rst +0 -0
  62. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/reference/source/taskgraph.actions.rst +0 -0
  63. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/reference/source/taskgraph.loader.rst +0 -0
  64. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/reference/source/taskgraph.optimize.rst +0 -0
  65. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/reference/source/taskgraph.rst +0 -0
  66. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/reference/source/taskgraph.transforms.rst +0 -0
  67. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/reference/source/taskgraph.transforms.run.rst +0 -0
  68. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/reference/source/taskgraph.util.rst +0 -0
  69. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/reference/transforms/chunking.rst +0 -0
  70. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/reference/transforms/from_deps.rst +0 -0
  71. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/reference/transforms/index.rst +0 -0
  72. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/reference/transforms/matrix.rst +0 -0
  73. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/reference/transforms/task_context.rst +0 -0
  74. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/tutorials/connecting-taskcluster.rst +0 -0
  75. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/tutorials/creating-a-task-graph.rst +0 -0
  76. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/tutorials/example-taskcluster.yml +0 -0
  77. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/tutorials/getting-started.rst +0 -0
  78. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/docs/tutorials/index.rst +0 -0
  79. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/make.bat +0 -0
  80. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/packages/pytest-taskgraph/README.md +0 -0
  81. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/packages/pytest-taskgraph/pyproject.toml +0 -0
  82. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/packages/pytest-taskgraph/src/pytest_taskgraph/__init__.py +0 -0
  83. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/packages/pytest-taskgraph/src/pytest_taskgraph/fixtures/gen.py +0 -0
  84. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/packages/pytest-taskgraph/src/pytest_taskgraph/fixtures/vcs.py +0 -0
  85. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/packages/sphinx-taskgraph/README.md +0 -0
  86. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/packages/sphinx-taskgraph/pyproject.toml +0 -0
  87. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/packages/sphinx-taskgraph/src/sphinx_taskgraph/__init__.py +0 -0
  88. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/packages/sphinx-taskgraph/src/sphinx_taskgraph/autoschema.py +0 -0
  89. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/__init__.py +0 -0
  90. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/actions/__init__.py +0 -0
  91. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/actions/add_new_jobs.py +0 -0
  92. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/actions/cancel.py +0 -0
  93. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/actions/cancel_all.py +0 -0
  94. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/actions/rebuild_cached_tasks.py +0 -0
  95. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/actions/registry.py +0 -0
  96. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/actions/retrigger.py +0 -0
  97. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/actions/util.py +0 -0
  98. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/config.py +0 -0
  99. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/create.py +0 -0
  100. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/decision.py +0 -0
  101. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/docker.py +0 -0
  102. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/filter_tasks.py +0 -0
  103. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/graph.py +0 -0
  104. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/loader/__init__.py +0 -0
  105. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/loader/default.py +0 -0
  106. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/loader/transform.py +0 -0
  107. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/main.py +0 -0
  108. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/morph.py +0 -0
  109. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/optimize/__init__.py +0 -0
  110. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/optimize/base.py +0 -0
  111. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/optimize/strategies.py +0 -0
  112. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/parameters.py +0 -0
  113. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/run-task/fetch-content +0 -0
  114. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/run-task/hgrc +0 -0
  115. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/run-task/robustcheckout.py +0 -0
  116. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/target_tasks.py +0 -0
  117. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/task.py +0 -0
  118. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/taskgraph.py +0 -0
  119. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/transforms/__init__.py +0 -0
  120. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/transforms/base.py +0 -0
  121. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/transforms/cached_tasks.py +0 -0
  122. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/transforms/chunking.py +0 -0
  123. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/transforms/code_review.py +0 -0
  124. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/transforms/docker_image.py +0 -0
  125. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/transforms/fetch.py +0 -0
  126. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/transforms/from_deps.py +0 -0
  127. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/transforms/matrix.py +0 -0
  128. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/transforms/notify.py +0 -0
  129. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/transforms/run/__init__.py +0 -0
  130. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/transforms/run/common.py +0 -0
  131. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/transforms/run/index_search.py +0 -0
  132. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/transforms/run/run_task.py +0 -0
  133. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/transforms/run/toolchain.py +0 -0
  134. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/transforms/task_context.py +0 -0
  135. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/__init__.py +0 -0
  136. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/archive.py +0 -0
  137. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/attributes.py +0 -0
  138. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/cached_tasks.py +0 -0
  139. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/caches.py +0 -0
  140. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/copy.py +0 -0
  141. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/dependencies.py +0 -0
  142. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/docker.py +0 -0
  143. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/hash.py +0 -0
  144. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/json.py +0 -0
  145. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/keyed_by.py +0 -0
  146. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/parameterization.py +0 -0
  147. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/path.py +0 -0
  148. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/python_path.py +0 -0
  149. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/readonlydict.py +0 -0
  150. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/set_name.py +0 -0
  151. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/shell.py +0 -0
  152. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/taskcluster.py +0 -0
  153. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/taskgraph.py +0 -0
  154. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/templates.py +0 -0
  155. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/time.py +0 -0
  156. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/treeherder.py +0 -0
  157. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/verify.py +0 -0
  158. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/workertypes.py +0 -0
  159. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/src/taskgraph/util/yaml.py +0 -0
  160. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/docker/REGISTRY +0 -0
  161. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/docker/decision/Dockerfile +0 -0
  162. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/docker/decision/HASH +0 -0
  163. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/docker/decision/README.md +0 -0
  164. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/docker/decision/VERSION +0 -0
  165. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/docker/decision/system-setup.sh +0 -0
  166. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/docker/fetch/Dockerfile +0 -0
  167. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/docker/image_builder/README.rst +0 -0
  168. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/docker/index-task/Dockerfile +0 -0
  169. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/docker/index-task/README +0 -0
  170. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/docker/index-task/insert-indexes.js +0 -0
  171. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/docker/index-task/package.json +0 -0
  172. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/docker/index-task/yarn.lock +0 -0
  173. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/docker/python/Dockerfile +0 -0
  174. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/docker/run-task/Dockerfile +0 -0
  175. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/docker/run-task/system-setup.sh +0 -0
  176. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/docker/skopeo/Dockerfile +0 -0
  177. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/docker/skopeo/policy.json +0 -0
  178. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/docker/skopeo/push_image.sh +0 -0
  179. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/kinds/check/kind.yml +0 -0
  180. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/kinds/codecov/kind.yml +0 -0
  181. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/kinds/complete/kind.yml +0 -0
  182. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/kinds/doc/kind.yml +0 -0
  183. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/kinds/docker-image/kind.yml +0 -0
  184. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/kinds/fetch/kind.yml +0 -0
  185. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/kinds/push-image/kind.yml +0 -0
  186. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/kinds/test/kind.yml +0 -0
  187. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/kinds/test/windows.yml +0 -0
  188. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/scripts/codecov-upload.py +0 -0
  189. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/scripts/external_tools/tooltool.py +0 -0
  190. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/self_taskgraph/__init__.py +0 -0
  191. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/self_taskgraph/custom_parameters.py +0 -0
  192. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/self_taskgraph/custom_target_tasks.py +0 -0
  193. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/self_taskgraph/transforms/add_pr_route.py +0 -0
  194. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/self_taskgraph/transforms/push_image.py +0 -0
  195. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/test/params/main-repo-pull-request-untrusted.yml +0 -0
  196. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/test/params/main-repo-pull-request.yml +0 -0
  197. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/test/params/main-repo-push.yml +0 -0
  198. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/test/params/main-repo-release-pytest-taskgraph.yml +0 -0
  199. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/taskcluster/test/params/main-repo-release-taskcluster-taskgraph.yml +0 -0
  200. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/template/cookiecutter.json +0 -0
  201. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/template/hooks/post_gen_project.py +0 -0
  202. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/template/{{cookiecutter.project_name}}/taskcluster/config.yml +0 -0
  203. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/template/{{cookiecutter.project_name}}/taskcluster/docker/linux/Dockerfile +0 -0
  204. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/template/{{cookiecutter.project_name}}/taskcluster/kinds/docker-image/kind.yml +0 -0
  205. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/template/{{cookiecutter.project_name}}/taskcluster/kinds/hello/kind.yml +0 -0
  206. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/template/{{cookiecutter.project_name}}/taskcluster/{{cookiecutter.project_slug}}_taskgraph/transforms/hello.py +0 -0
  207. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/template/{{cookiecutter.project_name}}/taskcluster.github.yml +0 -0
  208. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/__init__.py +0 -0
  209. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/automationrelevance.json +0 -0
  210. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/conftest.py +0 -0
  211. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/data/task_context.yml +0 -0
  212. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/data/taskcluster/config.yml +0 -0
  213. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/data/taskcluster/docker/hello-world/Dockerfile +0 -0
  214. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/data/taskcluster/docker/hello-world-tag/Dockerfile +0 -0
  215. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/data/taskcluster/docker/hello-world-tag/REGISTRY +0 -0
  216. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/data/taskcluster/docker/hello-world-tag/VERSION +0 -0
  217. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/data/taskcluster/kinds/docker-image/kind.yml +0 -0
  218. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/data/taskcluster/scripts/toolchain/run.ps1 +0 -0
  219. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/data/taskcluster/scripts/toolchain/run.sh +0 -0
  220. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/data/taskcluster/test_taskgraph/transforms/foo.py +0 -0
  221. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/data/testmod/thing.py +0 -0
  222. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/mockedopen.py +0 -0
  223. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_actions_rebuild_cached_tasks.py +0 -0
  224. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_actions_registry.py +0 -0
  225. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_config.py +0 -0
  226. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_create.py +0 -0
  227. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_decision.py +0 -0
  228. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_docker.py +0 -0
  229. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_graph.py +0 -0
  230. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_main.py +0 -0
  231. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_morph.py +0 -0
  232. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_optimize.py +0 -0
  233. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_optimize_strategies.py +0 -0
  234. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_parameters.py +0 -0
  235. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_scripts_fetch_content.py +0 -0
  236. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_target_tasks.py +0 -0
  237. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_taskgraph.py +0 -0
  238. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_transform_chunking.py +0 -0
  239. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_transform_docker_image.py +0 -0
  240. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_transform_task_context.py +0 -0
  241. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_transforms_base.py +0 -0
  242. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_transforms_cached_tasks.py +0 -0
  243. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_transforms_fetch.py +0 -0
  244. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_transforms_from_deps.py +0 -0
  245. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_transforms_matrix.py +0 -0
  246. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_transforms_notify.py +0 -0
  247. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_transforms_run.py +0 -0
  248. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_transforms_run_run_task.py +0 -0
  249. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_transforms_run_toolchain.py +0 -0
  250. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_transforms_task.py +0 -0
  251. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_archive.py +0 -0
  252. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_attributes.py +0 -0
  253. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_cached_tasks.py +0 -0
  254. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_copy.py +0 -0
  255. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_dependencies.py +0 -0
  256. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_docker.py +0 -0
  257. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_json.py +0 -0
  258. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_keyed_by.py +0 -0
  259. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_parameterization.py +0 -0
  260. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_path.py +0 -0
  261. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_python_path.py +0 -0
  262. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_readonlydict.py +0 -0
  263. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_taskcluster.py +0 -0
  264. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_templates.py +0 -0
  265. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_time.py +0 -0
  266. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_treeherder.py +0 -0
  267. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_verify.py +0 -0
  268. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_workertypes.py +0 -0
  269. {taskcluster_taskgraph-19.2.0 → taskcluster_taskgraph-20.0.0}/test/test_util_yaml.py +0 -0
@@ -13,13 +13,13 @@ repos:
13
13
  exclude: template
14
14
  - id: check-added-large-files
15
15
  - repo: https://github.com/astral-sh/ruff-pre-commit
16
- rev: v0.14.14
16
+ rev: v0.15.4
17
17
  hooks:
18
18
  - id: ruff
19
19
  args: [--fix, --exit-non-zero-on-fix]
20
20
  - id: ruff-format
21
21
  - repo: https://github.com/astral-sh/uv-pre-commit
22
- rev: 0.9.28
22
+ rev: 0.10.7
23
23
  hooks:
24
24
  - id: uv-lock
25
25
  - repo: https://github.com/adrienverge/yamllint.git
@@ -37,7 +37,7 @@ repos:
37
37
  test/test_util_path.py
38
38
  )$
39
39
  - repo: https://github.com/compilerla/conventional-pre-commit
40
- rev: v4.3.0
40
+ rev: v4.4.0
41
41
  hooks:
42
42
  - id: conventional-pre-commit
43
43
  stages: [commit-msg]
@@ -1,5 +1,24 @@
1
1
  # Change Log
2
2
 
3
+ ## [20.0.0] - 2026-03-17
4
+
5
+ ### Added
6
+
7
+ - Support for generic-worker's `hide-cmd-window` feature
8
+ - Ability to process kinds concurrently with threading
9
+
10
+ ### Fixed
11
+
12
+ - BREAKING CHANGE: Binaries installed as part of `PIP_REQUIREMENTS` by `run-task` now end up in `~/.local/bin`
13
+ - Exception when base revision is the null revision
14
+ - Ability to use `optionally_keyed_by` with underlying dict using msgspec
15
+
16
+ ## [19.2.1] - 2026-02-19
17
+
18
+ ### Fixed
19
+
20
+ - Loosen `optionally_keyed_by` constraints for msgspec compatibility
21
+
3
22
  ## [19.2.0] - 2026-02-18
4
23
 
5
24
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: taskcluster-taskgraph
3
- Version: 19.2.0
3
+ Version: 20.0.0
4
4
  Summary: Build taskcluster taskgraphs
5
5
  Project-URL: Repository, https://github.com/taskcluster/taskgraph
6
6
  Project-URL: Issues, https://github.com/taskcluster/taskgraph/issues
@@ -3,6 +3,14 @@ Migration Guide
3
3
 
4
4
  This page can help when migrating Taskgraph across major versions.
5
5
 
6
+ 19.x -> 20.x
7
+ ------------
8
+
9
+ * Binaries belonging to packages that get installed by `run-task` as part of
10
+ the `PIP_REQUIREMENTS` variable, are now installed to `~/.local/bin` instead
11
+ of `~/.local/lib/<python>/site-packages/bin`. Update any task commands that hardcode
12
+ the binary path accordingly.
13
+
6
14
  18.x -> 19.x
7
15
  ------------
8
16
 
@@ -1,7 +1,7 @@
1
1
  ### Project
2
2
  [project]
3
3
  name = "taskcluster-taskgraph"
4
- version = "19.2.0"
4
+ version = "20.0.0"
5
5
  description = "Build taskcluster taskgraphs"
6
6
  readme = "README.rst"
7
7
  authors = [
@@ -11,6 +11,7 @@ import platform
11
11
  from concurrent.futures import (
12
12
  FIRST_COMPLETED,
13
13
  ProcessPoolExecutor,
14
+ ThreadPoolExecutor,
14
15
  wait,
15
16
  )
16
17
  from dataclasses import dataclass
@@ -312,15 +313,13 @@ class TaskGraphGenerator:
312
313
 
313
314
  return all_tasks
314
315
 
315
- def _load_tasks_parallel(self, kinds, kind_graph, parameters):
316
+ def _load_tasks_parallel(self, kinds, kind_graph, parameters, executor):
316
317
  all_tasks = {}
317
318
  futures_to_kind = {}
318
319
  futures = set()
319
320
  edges = set(kind_graph.edges)
320
321
 
321
- with ProcessPoolExecutor(
322
- mp_context=multiprocessing.get_context("fork")
323
- ) as executor:
322
+ with executor:
324
323
 
325
324
  def submit_ready_kinds():
326
325
  """Create the next batch of tasks for kinds without dependencies."""
@@ -434,29 +433,50 @@ class TaskGraphGenerator:
434
433
  yield "kind_graph", kind_graph
435
434
 
436
435
  logger.info("Generating full task set")
437
- # The short version of the below is: we only support parallel kind
438
- # processing on Linux.
436
+
437
+ # The next block deals with enabling parallel kind processing, which
438
+ # currently has different support on different platforms. In summary:
439
+ # * Parallel kind processing is supported and enabled by default on
440
+ # Linux. We use multiple processes by default, but experimental
441
+ # support for multiple threads can be enabled instead.
442
+ # * On other platforms, we have experimental support for parallel
443
+ # kind processing with multiple threads.
439
444
  #
440
- # Current parallel generation relies on multiprocessing, and more
441
- # specifically: the "fork" multiprocessing method. This is not supported
442
- # at all on Windows (it uses "spawn"). Forking is supported on macOS,
443
- # but no longer works reliably in all cases, and our usage of it here
444
- # causes crashes. See https://github.com/python/cpython/issues/77906
445
- # and http://sealiesoftware.com/blog/archive/2017/6/5/Objective-C_and_fork_in_macOS_1013.html
446
- # for more details on that.
447
- # Other methods of multiprocessing (both "spawn" and "forkserver")
448
- # do not work for our use case, because they cause global variables
449
- # to be reinitialized, which are sometimes modified earlier in graph
450
- # generation. These issues can theoretically be worked around by
451
- # eliminating all reliance on globals as part of task generation, but
452
- # is far from a small amount of work in users like Gecko/Firefox.
453
- # In the long term, the better path forward is likely to be switching
454
- # to threading with a free-threaded python to achieve similar parallel
455
- # processing.
456
- if platform.system() != "Linux" or os.environ.get("TASKGRAPH_SERIAL"):
457
- all_tasks = self._load_tasks_serial(kinds, kind_graph, parameters)
458
- else:
459
- all_tasks = self._load_tasks_parallel(kinds, kind_graph, parameters)
445
+ # On all platforms serial kind processing can be enabled by setting
446
+ # TASKGRAPH_SERIAL in the environment.
447
+ #
448
+ # On all platforms, multiple threads can be enabled by setting
449
+ # TASKGRAPH_USE_THREADS in the environment. Taskgraph must be running
450
+ # from a free-threaded Python build to see any performance benefits.
451
+ #
452
+ # In the long term, the goal is turn enabled parallel kind processing for
453
+ # all platforms by default using threads, and remove support for multiple
454
+ # processes altogether.
455
+ def load_tasks():
456
+ if platform.system() == "Linux":
457
+ if os.environ.get("TASKGRAPH_SERIAL"):
458
+ return self._load_tasks_serial(kinds, kind_graph, parameters)
459
+ elif os.environ.get("TASKGRAPH_USE_THREADS"):
460
+ executor = ThreadPoolExecutor(max_workers=os.process_cpu_count())
461
+ else:
462
+ executor = ProcessPoolExecutor(
463
+ mp_context=multiprocessing.get_context("fork")
464
+ )
465
+ return self._load_tasks_parallel(
466
+ kinds, kind_graph, parameters, executor
467
+ )
468
+ else:
469
+ if os.environ.get("TASKGRAPH_SERIAL") or not os.environ.get(
470
+ "TASKGRAPH_USE_THREADS"
471
+ ):
472
+ return self._load_tasks_serial(kinds, kind_graph, parameters)
473
+ else:
474
+ executor = ThreadPoolExecutor(max_workers=os.process_cpu_count())
475
+ return self._load_tasks_parallel(
476
+ kinds, kind_graph, parameters, executor
477
+ )
478
+
479
+ all_tasks = load_tasks()
460
480
 
461
481
  full_task_set = TaskGraph(all_tasks, Graph(frozenset(all_tasks), frozenset()))
462
482
  yield self.verify("full_task_set", full_task_set, graph_config, parameters)
@@ -1044,8 +1044,8 @@ def install_pip_requirements(repositories):
1044
1044
 
1045
1045
  # TODO: Stop using system Python (#381)
1046
1046
  if shutil.which("uv"):
1047
- user_site_dir = subprocess.run(
1048
- [sys.executable, "-msite", "--user-site"], capture_output=True, text=True
1047
+ user_base_dir = subprocess.run(
1048
+ [sys.executable, "-msite", "--user-base"], capture_output=True, text=True
1049
1049
  ).stdout.strip()
1050
1050
  cmd = [
1051
1051
  "uv",
@@ -1053,8 +1053,8 @@ def install_pip_requirements(repositories):
1053
1053
  "install",
1054
1054
  "--python",
1055
1055
  sys.executable,
1056
- "--target",
1057
- user_site_dir,
1056
+ "--prefix",
1057
+ user_base_dir,
1058
1058
  ]
1059
1059
  else:
1060
1060
  cmd = [sys.executable, "-mpip", "install", "--user", "--break-system-packages"]
@@ -569,6 +569,7 @@ class GenericWorkerPayloadSchema(Schema, forbid_unknown_fields=False, kw_only=Tr
569
569
  # feature for task to run as current OS user
570
570
  run_task_as_current_user: Optional[bool] = None
571
571
  taskcluster_proxy: Optional[bool] = None
572
+ hide_cmd_window: Optional[bool] = None
572
573
  # Whether any artifacts are assigned to this worker
573
574
  skip_artifacts: Optional[bool] = None
574
575
 
@@ -681,6 +682,9 @@ def build_generic_worker_payload(config, task, task_def):
681
682
  "generic-worker:run-task-as-current-user:{}".format(task["worker-type"]),
682
683
  )
683
684
 
685
+ if worker.get("hide-cmd-window"):
686
+ features["hideCmdWindow"] = True
687
+
684
688
  if features:
685
689
  task_def["payload"]["features"] = features
686
690
 
@@ -4,9 +4,9 @@
4
4
 
5
5
  import pprint
6
6
  import re
7
+ import threading
7
8
  from collections.abc import Mapping
8
- from functools import reduce
9
- from typing import Literal, Optional, Union
9
+ from typing import Annotated, Any, Literal, Optional, Union, get_args, get_origin
10
10
 
11
11
  import msgspec
12
12
  import voluptuous
@@ -14,6 +14,8 @@ import voluptuous
14
14
  import taskgraph
15
15
  from taskgraph.util.keyed_by import evaluate_keyed_by, iter_dot_path
16
16
 
17
+ _schema_creation_lock = threading.RLock()
18
+
17
19
  # Common type definitions that are used across multiple schemas
18
20
  TaskPriority = Literal[
19
21
  "highest", "very-high", "high", "medium", "low", "very-low", "lowest"
@@ -67,11 +69,37 @@ def validate_schema(schema, obj, msg_prefix):
67
69
  raise Exception(f"{msg_prefix}\n{str(exc)}\n{pprint.pformat(obj)}")
68
70
 
69
71
 
70
- def UnionTypes(*types):
71
- """Use `functools.reduce` to simulate `Union[*allowed_types]` on older
72
- Python versions.
73
- """
74
- return reduce(lambda a, b: Union[a, b], types)
72
+ class OptionallyKeyedBy:
73
+ """Metadata class for optionally_keyed_by fields in msgspec schemas."""
74
+
75
+ def __init__(self, *fields, wrapped_type):
76
+ self.fields = {f"by-{field}" for field in fields}
77
+ self.wrapped_type = wrapped_type
78
+
79
+ def uses_keyed_by(self, obj) -> bool:
80
+ if not isinstance(obj, dict) or len(obj) != 1:
81
+ return False
82
+
83
+ key = list(obj)[0]
84
+ if key not in self.fields:
85
+ return False
86
+
87
+ return True
88
+
89
+ def validate(self, obj) -> None:
90
+ if not self.uses_keyed_by(obj):
91
+ # Not using keyed by, validate directly against wrapped type
92
+ msgspec.convert(obj, self.wrapped_type)
93
+ return
94
+
95
+ # First validate the outer keyed-by dict
96
+ msgspec.convert(obj, dict[str, dict])
97
+
98
+ # Next validate each inner value. We call self.validate recursively to
99
+ # support nested `by-*` keys.
100
+ keyed_by_dict = list(obj.values())[0]
101
+ for value in keyed_by_dict.values():
102
+ self.validate(value)
75
103
 
76
104
 
77
105
  def optionally_keyed_by(*arguments, use_msgspec=False):
@@ -83,11 +111,15 @@ def optionally_keyed_by(*arguments, use_msgspec=False):
83
111
  use_msgspec: If True, return msgspec type hints; if False, return voluptuous validator
84
112
  """
85
113
  if use_msgspec:
86
- # msgspec implementation - return type hints
114
+ # msgspec implementation - use Annotated[Any, OptionallyKeyedBy]
87
115
  _type = arguments[-1]
116
+ if _type is object:
117
+ return object
88
118
  fields = arguments[:-1]
89
- bykeys = [Literal[f"by-{field}"] for field in fields]
90
- return Union[_type, dict[UnionTypes(*bykeys), dict[str, _type]]]
119
+ wrapper = OptionallyKeyedBy(*fields, wrapped_type=_type)
120
+ # Annotating Any allows msgspec to accept any value without validation.
121
+ # The actual validation then happens in Schema.__post_init__
122
+ return Annotated[Any, wrapper]
91
123
  else:
92
124
  # voluptuous implementation - return validator function
93
125
  schema = arguments[-1]
@@ -251,28 +283,36 @@ class LegacySchema(voluptuous.Schema):
251
283
  """
252
284
  Operates identically to voluptuous.Schema, but applying some taskgraph-specific checks
253
285
  in the process.
286
+
287
+ voluptuous.Schema's `_compile` method is thread-unsafe. Any usage (whether direct or
288
+ indirect) of it must be protected by a lock.
254
289
  """
255
290
 
256
291
  def __init__(self, *args, check=True, **kwargs):
257
- super().__init__(*args, **kwargs)
292
+ with _schema_creation_lock:
293
+ # this constructor may call `_compile`
294
+ super().__init__(*args, **kwargs)
258
295
 
259
- self.check = check
260
- if not taskgraph.fast and self.check:
261
- check_schema(self)
296
+ self.check = check
297
+ if not taskgraph.fast and self.check:
298
+ check_schema(self)
262
299
 
263
300
  def extend(self, *args, **kwargs):
264
- schema = super().extend(*args, **kwargs)
301
+ with _schema_creation_lock:
302
+ # `extend` may create a new Schema object, which may call `_compile`
303
+ schema = super().extend(*args, **kwargs)
265
304
 
266
- if self.check:
267
- check_schema(schema)
268
- # We want twice extend schema to be checked too.
269
- schema.__class__ = LegacySchema
270
- return schema
305
+ if self.check:
306
+ check_schema(schema)
307
+ # We want twice extend schema to be checked too.
308
+ schema.__class__ = LegacySchema
309
+ return schema
271
310
 
272
311
  def _compile(self, schema):
273
- if taskgraph.fast:
274
- return
275
- return super()._compile(schema)
312
+ with _schema_creation_lock:
313
+ if taskgraph.fast:
314
+ return
315
+ return super()._compile(schema)
276
316
 
277
317
  def __getitem__(self, item):
278
318
  return self.schema[item] # type: ignore
@@ -305,6 +345,31 @@ class Schema(
305
345
  foo: str
306
346
  """
307
347
 
348
+ def __post_init__(self):
349
+ if taskgraph.fast:
350
+ return
351
+
352
+ # Validate fields that use optionally_keyed_by. We need to validate this
353
+ # manually because msgspec doesn't support union types with multiple
354
+ # dicts. Any fields that use `optionally_keyed_by("foo", dict)` would
355
+ # otherwise raise an exception.
356
+ for field_name, field_type in self.__class__.__annotations__.items():
357
+ origin = get_origin(field_type)
358
+ args = get_args(field_type)
359
+
360
+ if (
361
+ origin is not Annotated
362
+ or len(args) < 2
363
+ or not isinstance(args[1], OptionallyKeyedBy)
364
+ ):
365
+ # Not using `optionally_keyed_by`
366
+ continue
367
+
368
+ keyed_by = args[1]
369
+ obj = getattr(self, field_name)
370
+
371
+ keyed_by.validate(obj)
372
+
308
373
  @classmethod
309
374
  def validate(cls, data):
310
375
  """Validate data against this schema."""
@@ -312,7 +377,7 @@ class Schema(
312
377
  return data
313
378
 
314
379
  try:
315
- return msgspec.convert(data, cls)
380
+ msgspec.convert(data, cls)
316
381
  except (msgspec.ValidationError, msgspec.DecodeError) as e:
317
382
  raise msgspec.ValidationError(str(e))
318
383
 
@@ -505,6 +505,11 @@ class GitRepository(Repository):
505
505
  cmd.append("--cached")
506
506
  elif mode == "all":
507
507
  cmd.append("HEAD")
508
+ elif base == self.NULL_REVISION:
509
+ # When base is NULL_REVISION (e.g new branches on Github), diff
510
+ # from the empty tree to show all files present at rev. Use Git's
511
+ # well-known empty tree hash.
512
+ cmd = ["diff", "4b825dc642cb6eb9a060e54bf8d69288fbee4904", rev]
508
513
  elif self.is_shallow:
509
514
  # In shallow clones, `git log` won't have the history necessary to
510
515
  # determine the files changed. Using `git diff` finds the
@@ -7,6 +7,7 @@ treeherder:
7
7
  'check': 'Checks and lints'
8
8
  'doc': 'Documentation tasks'
9
9
  'unit': 'Unit test tasks'
10
+ 'unit-multithread': 'Unit test tasks with multithreading enabled'
10
11
  'integration': 'Integration test tasks'
11
12
 
12
13
  index:
@@ -50,6 +50,26 @@ unit:
50
50
  command: >-
51
51
  uv run coverage run --data-file /builds/worker/artifacts/coverage --context=py{matrix[python]} -m pytest -vv
52
52
 
53
+ unit-multithread:
54
+ description: "Run unit tests with py{matrix[python]} on Linux with multithreading enabled"
55
+ matrix:
56
+ set-name: "unit-multithread-py{matrix[python]}"
57
+ substitution-fields: [description, run.command, treeherder, worker, attributes]
58
+ python: ["314t"]
59
+ worker:
60
+ docker-image: {in-tree: python}
61
+ env:
62
+ TASKGRAPH_USE_THREADS: "1"
63
+ artifacts:
64
+ - type: file
65
+ path: "/builds/worker/artifacts/coverage"
66
+ name: "public/coverage.py{matrix[python]}"
67
+ treeherder:
68
+ symbol: unit-multithread(py{matrix[python]})
69
+ run:
70
+ command: >-
71
+ uv run coverage run --data-file /builds/worker/artifacts/coverage --context=py{matrix[python]} -m pytest -vv
72
+
53
73
  integration:
54
74
  description: "Run unit tests with py{matrix[python]} on Linux with resolution {matrix[resolution]}"
55
75
  attributes:
@@ -3,6 +3,7 @@
3
3
  # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
4
 
5
5
 
6
+ import os
6
7
  import platform
7
8
  from concurrent.futures import ProcessPoolExecutor
8
9
 
@@ -14,9 +15,13 @@ from taskgraph.generator import Kind, load_tasks_for_kind, load_tasks_for_kinds
14
15
  from taskgraph.loader.default import loader as default_loader
15
16
 
16
17
  linuxonly = pytest.mark.skipif(
17
- platform.system() != "Linux",
18
+ platform.system() != "Linux" or os.environ.get("TASKGRAPH_USE_THREADS"),
18
19
  reason="requires Linux and 'fork' multiprocessing support",
19
20
  )
21
+ threadsonly = pytest.mark.skipif(
22
+ not os.environ.get("TASKGRAPH_USE_THREADS"),
23
+ reason="requires multithreading to be enabled",
24
+ )
20
25
 
21
26
 
22
27
  class FakePPE(ProcessPoolExecutor):
@@ -27,8 +32,16 @@ class FakePPE(ProcessPoolExecutor):
27
32
  return super().submit(kind_load_tasks, *args)
28
33
 
29
34
 
35
+ class FakeTPE(ProcessPoolExecutor):
36
+ loaded_kinds = []
37
+
38
+ def submit(self, kind_load_tasks, *args):
39
+ self.loaded_kinds.append(kind_load_tasks.__self__.name)
40
+ return super().submit(kind_load_tasks, *args)
41
+
42
+
30
43
  @linuxonly
31
- def test_kind_ordering(mocker, maketgg):
44
+ def test_kind_ordering_multiprocess(mocker, maketgg):
32
45
  "When task kinds depend on each other, they are loaded in postorder"
33
46
  mocked_ppe = mocker.patch.object(generator, "ProcessPoolExecutor", new=FakePPE)
34
47
  tgg = maketgg(
@@ -42,6 +55,21 @@ def test_kind_ordering(mocker, maketgg):
42
55
  assert mocked_ppe.loaded_kinds == ["_fake1", "_fake2", "_fake3"]
43
56
 
44
57
 
58
+ @threadsonly
59
+ def test_kind_ordering_multithread(mocker, maketgg):
60
+ "When task kinds depend on each other, they are loaded in postorder"
61
+ mocked_tpe = mocker.patch.object(generator, "ThreadPoolExecutor", new=FakeTPE)
62
+ tgg = maketgg(
63
+ kinds=[
64
+ ("_fake3", {"kind-dependencies": ["_fake2", "_fake1"]}),
65
+ ("_fake2", {"kind-dependencies": ["_fake1"]}),
66
+ ("_fake1", {"kind-dependencies": []}),
67
+ ]
68
+ )
69
+ tgg._run_until("full_task_set")
70
+ assert mocked_tpe.loaded_kinds == ["_fake1", "_fake2", "_fake3"]
71
+
72
+
45
73
  def test_full_task_set(maketgg):
46
74
  "The full_task_set property has all tasks"
47
75
  tgg = maketgg()
@@ -143,8 +143,8 @@ def test_install_pip_requirements_with_uv(
143
143
  "install",
144
144
  "--python",
145
145
  sys.executable,
146
- "--target",
147
- site.getusersitepackages(),
146
+ "--prefix",
147
+ site.getuserbase(),
148
148
  "--require-hashes",
149
149
  "-r",
150
150
  str(req),
@@ -284,32 +284,67 @@ class TestValidateSchemaDictHandler(unittest.TestCase):
284
284
 
285
285
 
286
286
  def test_optionally_keyed_by():
287
- typ = optionally_keyed_by("foo", str, use_msgspec=True)
288
- assert msgspec.convert("baz", typ) == "baz"
289
- assert msgspec.convert({"by-foo": {"a": "b", "c": "d"}}, typ) == {
290
- "by-foo": {"a": "b", "c": "d"}
291
- }
287
+ class TestSchema(Schema):
288
+ field: optionally_keyed_by("foo", str, use_msgspec=True) # type: ignore
289
+
290
+ TestSchema.validate({"field": "baz"})
291
+ TestSchema.validate({"field": {"by-foo": {"a": "b", "c": "d"}}})
292
+
293
+ with pytest.raises(msgspec.ValidationError):
294
+ TestSchema.validate({"field": 1})
295
+
296
+ with pytest.raises(msgspec.ValidationError):
297
+ TestSchema.validate({"field": {"by-bar": "a"}})
292
298
 
293
299
  with pytest.raises(msgspec.ValidationError):
294
- msgspec.convert({"by-foo": {"a": 1, "c": "d"}}, typ)
300
+ TestSchema.validate({"field": {"by-bar": {1: "b"}}})
295
301
 
296
302
  with pytest.raises(msgspec.ValidationError):
297
- msgspec.convert({"by-bar": {"a": "b"}}, typ)
303
+ TestSchema.validate({"field": {"by-bar": {"a": "b"}}})
304
+
305
+ with pytest.raises(msgspec.ValidationError):
306
+ TestSchema.validate({"field": {"by-foo": {"a": 1, "c": "d"}}})
307
+
308
+
309
+ def test_optionally_keyed_by_multiple_keys():
310
+ class TestSchema(Schema):
311
+ field: optionally_keyed_by("foo", "bar", str, use_msgspec=True) # type: ignore
312
+
313
+ TestSchema.validate({"field": {"by-foo": {"a": "b"}}})
314
+ TestSchema.validate({"field": {"by-bar": {"x": "y"}}})
315
+ TestSchema.validate({"field": {"by-foo": {"a": {"by-bar": {"x": "y"}}}}})
316
+
317
+ # Test invalid keyed-by field
318
+ with pytest.raises(msgspec.ValidationError):
319
+ TestSchema.validate({"field": {"by-unknown": {"a": "b"}}})
320
+
321
+ with pytest.raises(msgspec.ValidationError):
322
+ TestSchema.validate({"field": {"by-foo": {"a": {"by-bar": {"x": 1}}}}})
323
+
324
+
325
+ def test_optionally_keyed_by_object_passthrough():
326
+ """When the type argument is `object`, optionally_keyed_by returns object directly."""
327
+ typ = optionally_keyed_by("foo", object, use_msgspec=True)
328
+ assert typ is object
329
+ # object accepts anything via msgspec.convert
330
+ assert msgspec.convert("hello", typ) == "hello"
331
+ assert msgspec.convert(42, typ) == 42
332
+ assert msgspec.convert({"by-foo": {"a": "b"}}, typ) == {"by-foo": {"a": "b"}}
333
+ assert msgspec.convert({"arbitrary": "dict"}, typ) == {"arbitrary": "dict"}
334
+
298
335
 
336
+ def test_optionally_keyed_by_dict():
337
+ class TestSchema(Schema):
338
+ field: optionally_keyed_by("foo", dict[str, str], use_msgspec=True) # type: ignore
299
339
 
300
- def test_optionally_keyed_by_mulitple_keys():
301
- typ = optionally_keyed_by("foo", "bar", str, use_msgspec=True)
302
- assert msgspec.convert("baz", typ) == "baz"
303
- assert msgspec.convert({"by-foo": {"a": "b", "c": "d"}}, typ) == {
304
- "by-foo": {"a": "b", "c": "d"}
305
- }
306
- assert msgspec.convert({"by-bar": {"x": "y"}}, typ) == {"by-bar": {"x": "y"}}
340
+ TestSchema.validate({"field": {"by-foo": {"a": {"x": "y"}}}})
341
+ TestSchema.validate({"field": {"a": "b"}})
307
342
 
308
343
  with pytest.raises(msgspec.ValidationError):
309
- msgspec.convert({"by-foo": {"a": 123, "c": "d"}}, typ)
344
+ TestSchema.validate({"field": {"a": 1}})
310
345
 
311
346
  with pytest.raises(msgspec.ValidationError):
312
- msgspec.convert({"by-bar": {"a": 1}}, typ)
347
+ TestSchema.validate({"field": {"by-foo": {"a": {"x": 1}}}})
313
348
 
314
349
  with pytest.raises(msgspec.ValidationError):
315
- msgspec.convert({"by-unknown": {"a": "b"}}, typ)
350
+ TestSchema.validate({"field": {"by-foo": {"a": "b"}}})