prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  python_source_root not added to syspath
  I have a project structure along the lines of this:
  ```
  packages/
      package_a/
          BUILD
          __init__.py
          package_a.py
      package_b/
          BUILD
          __init__.py
          package_b.py
  protos/
      package_a/
          BUILD
          foobar.proto
      service_a/
          BUILD
          bazqux.proto
  services/
      service_a/
          BUILD
          __init__.py
          main.py
      service_b/
          BUILD
          __init__.py
          main.py
  ```
  
  `/packages`, `/protos` and `/services` are all configured as source roots. In each BUILD files residing in `protos/*` I have either the line `protobuf_library(python_source_root='services')` or `protobuf_library(python_source_root='packages')`, which means that the generated code ends up in `packages/*` or `services/*` instead.
  
  This is working just fine as long as the running code is in the same source root as the generated protobuf code, but when code in `services/` is dependent on protos that has `python_source_root` set to `packages`, Python can't find the module unless an actual module from the same source root is also a dependency. I did some digging around, and it seems like the issue is that the source root specified in `python_source_root` is never explicitly added to Python's syspath, which is why imports fail if no "real" packages from the same source roots are used. So using the same example as earlier I see `services` and `protos`, but `packages`, where the generated code is placed, is missing.
  
  I created a [proof-of-concept repository](https://github.com/jyggen/pants-issue-11666) in case my rambling makes little sense. The issue can be seen by running `./pants test services/::`.
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `src/python/pants/backend/python/util_rules/python_sources.py`
  Content:
  ```
  1 # Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).
  2 # Licensed under the Apache License, Version 2.0 (see LICENSE).
  3 
  4 from dataclasses import dataclass
  5 from typing import Iterable, List, Tuple, Type
  6 
  7 from pants.backend.python.target_types import PythonSources
  8 from pants.backend.python.util_rules import ancestor_files
  9 from pants.backend.python.util_rules.ancestor_files import AncestorFiles, AncestorFilesRequest
  10 from pants.core.target_types import FilesSources, ResourcesSources
  11 from pants.core.util_rules import source_files, stripped_source_files
  12 from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest
  13 from pants.core.util_rules.stripped_source_files import StrippedSourceFiles
  14 from pants.engine.fs import MergeDigests, Snapshot
  15 from pants.engine.rules import Get, MultiGet, collect_rules, rule
  16 from pants.engine.target import Sources, Target
  17 from pants.engine.unions import UnionMembership
  18 from pants.source.source_root import SourceRoot, SourceRootRequest
  19 from pants.util.logging import LogLevel
  20 from pants.util.meta import frozen_after_init
  21 
  22 
  23 @dataclass(frozen=True)
  24 class PythonSourceFiles:
  25     """Sources that can be introspected by Python, relative to a set of source roots.
  26 
  27     Specifically, this will filter out to only have Python, and, optionally, resources() and
  28     files() targets; and will add any missing `__init__.py` files to ensure that modules are
  29     recognized correctly.
  30 
  31     Use-cases that introspect Python source code (e.g., the `test, `lint`, `fmt` goals) can
  32     request this type to get relevant sources that are still relative to their source roots.
  33     That way the paths they report are the unstripped ones the user is familiar with.
  34 
  35     The sources can also be imported and used by Python (e.g., for the `test` goal), but only
  36     if sys.path is modified to include the source roots.
  37     """
  38 
  39     source_files: SourceFiles
  40     source_roots: Tuple[str, ...]  # Source roots for the specified source files.
  41 
  42 
  43 @dataclass(frozen=True)
  44 class StrippedPythonSourceFiles:
  45     """A PythonSourceFiles that has had its source roots stripped."""
  46 
  47     stripped_source_files: StrippedSourceFiles
  48 
  49 
  50 @frozen_after_init
  51 @dataclass(unsafe_hash=True)
  52 class PythonSourceFilesRequest:
  53     targets: Tuple[Target, ...]
  54     include_resources: bool
  55     include_files: bool
  56 
  57     def __init__(
  58         self,
  59         targets: Iterable[Target],
  60         *,
  61         include_resources: bool = True,
  62         include_files: bool = False
  63     ) -> None:
  64         self.targets = tuple(targets)
  65         self.include_resources = include_resources
  66         self.include_files = include_files
  67 
  68     @property
  69     def valid_sources_types(self) -> Tuple[Type[Sources], ...]:
  70         types: List[Type[Sources]] = [PythonSources]
  71         if self.include_resources:
  72             types.append(ResourcesSources)
  73         if self.include_files:
  74             types.append(FilesSources)
  75         return tuple(types)
  76 
  77 
  78 @rule(level=LogLevel.DEBUG)
  79 async def prepare_python_sources(
  80     request: PythonSourceFilesRequest, union_membership: UnionMembership
  81 ) -> PythonSourceFiles:
  82     sources = await Get(
  83         SourceFiles,
  84         SourceFilesRequest(
  85             (tgt.get(Sources) for tgt in request.targets),
  86             for_sources_types=request.valid_sources_types,
  87             enable_codegen=True,
  88         ),
  89     )
  90 
  91     missing_init_files = await Get(
  92         AncestorFiles,
  93         AncestorFilesRequest("__init__.py", sources.snapshot),
  94     )
  95 
  96     init_injected = await Get(
  97         Snapshot,
  98         MergeDigests((sources.snapshot.digest, missing_init_files.snapshot.digest)),
  99     )
  100 
  101     source_root_objs = await MultiGet(
  102         Get(SourceRoot, SourceRootRequest, SourceRootRequest.for_target(tgt))
  103         for tgt in request.targets
  104         if (
  105             tgt.has_field(PythonSources)
  106             or tgt.has_field(ResourcesSources)
  107             or tgt.get(Sources).can_generate(PythonSources, union_membership)
  108             or tgt.get(Sources).can_generate(ResourcesSources, union_membership)
  109         )
  110     )
  111     source_root_paths = {source_root_obj.path for source_root_obj in source_root_objs}
  112     return PythonSourceFiles(
  113         SourceFiles(init_injected, sources.unrooted_files), tuple(sorted(source_root_paths))
  114     )
  115 
  116 
  117 @rule(level=LogLevel.DEBUG)
  118 async def strip_python_sources(python_sources: PythonSourceFiles) -> StrippedPythonSourceFiles:
  119     stripped = await Get(StrippedSourceFiles, SourceFiles, python_sources.source_files)
  120     return StrippedPythonSourceFiles(stripped)
  121 
  122 
  123 def rules():
  124     return [
  125         *collect_rules(),
  126         *ancestor_files.rules(),
  127         *source_files.rules(),
  128         *stripped_source_files.rules(),
  129     ]
  130
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/src/python/pants/backend/python/util_rules/python_sources.py b/src/python/pants/backend/python/util_rules/python_sources.py
  --- a/src/python/pants/backend/python/util_rules/python_sources.py
  +++ b/src/python/pants/backend/python/util_rules/python_sources.py
  @@ -13,7 +13,7 @@
   from pants.core.util_rules.stripped_source_files import StrippedSourceFiles
   from pants.engine.fs import MergeDigests, Snapshot
   from pants.engine.rules import Get, MultiGet, collect_rules, rule
  -from pants.engine.target import Sources, Target
  +from pants.engine.target import HydratedSources, HydrateSourcesRequest, Sources, Target
   from pants.engine.unions import UnionMembership
   from pants.source.source_root import SourceRoot, SourceRootRequest
   from pants.util.logging import LogLevel
  @@ -98,15 +98,39 @@
           MergeDigests((sources.snapshot.digest, missing_init_files.snapshot.digest)),
       )
   
  -    source_root_objs = await MultiGet(
  -        Get(SourceRoot, SourceRootRequest, SourceRootRequest.for_target(tgt))
  -        for tgt in request.targets
  -        if (
  -            tgt.has_field(PythonSources)
  -            or tgt.has_field(ResourcesSources)
  -            or tgt.get(Sources).can_generate(PythonSources, union_membership)
  -            or tgt.get(Sources).can_generate(ResourcesSources, union_membership)
  +    # Codegen is able to generate code in any arbitrary location, unlike sources normally being
  +    # rooted under the target definition. To determine source roots for these generated files, we
  +    # cannot use the normal `SourceRootRequest.for_target()` and we instead must determine
  +    # a source root for every individual generated file. So, we re-resolve the codegen sources here.
  +    python_and_resources_targets = []
  +    codegen_targets = []
  +    for tgt in request.targets:
  +        if tgt.has_field(PythonSources) or tgt.has_field(ResourcesSources):
  +            python_and_resources_targets.append(tgt)
  +        elif tgt.get(Sources).can_generate(PythonSources, union_membership) or tgt.get(
  +            Sources
  +        ).can_generate(ResourcesSources, union_membership):
  +            codegen_targets.append(tgt)
  +    codegen_sources = await MultiGet(
  +        Get(
  +            HydratedSources,
  +            HydrateSourcesRequest(
  +                tgt.get(Sources), for_sources_types=request.valid_sources_types, enable_codegen=True
  +            ),
           )
  +        for tgt in codegen_targets
  +    )
  +    source_root_requests = [
  +        *(SourceRootRequest.for_target(tgt) for tgt in python_and_resources_targets),
  +        *(
  +            SourceRootRequest.for_file(f)
  +            for sources in codegen_sources
  +            for f in sources.snapshot.files
  +        ),
  +    ]
  +
  +    source_root_objs = await MultiGet(
  +        Get(SourceRoot, SourceRootRequest, req) for req in source_root_requests
       )
       source_root_paths = {source_root_obj.path for source_root_obj in source_root_objs}
       return PythonSourceFiles(
  
verification_info: {"golden_diff": "diff --git a/src/python/pants/backend/python/util_rules/python_sources.py b/src/python/pants/backend/python/util_rules/python_sources.py\n--- a/src/python/pants/backend/python/util_rules/python_sources.py\n+++ b/src/python/pants/backend/python/util_rules/python_sources.py\n@@ -13,7 +13,7 @@\n from pants.core.util_rules.stripped_source_files import StrippedSourceFiles\n from pants.engine.fs import MergeDigests, Snapshot\n from pants.engine.rules import Get, MultiGet, collect_rules, rule\n-from pants.engine.target import Sources, Target\n+from pants.engine.target import HydratedSources, HydrateSourcesRequest, Sources, Target\n from pants.engine.unions import UnionMembership\n from pants.source.source_root import SourceRoot, SourceRootRequest\n from pants.util.logging import LogLevel\n@@ -98,15 +98,39 @@\n         MergeDigests((sources.snapshot.digest, missing_init_files.snapshot.digest)),\n     )\n \n-    source_root_objs = await MultiGet(\n-        Get(SourceRoot, SourceRootRequest, SourceRootRequest.for_target(tgt))\n-        for tgt in request.targets\n-        if (\n-            tgt.has_field(PythonSources)\n-            or tgt.has_field(ResourcesSources)\n-            or tgt.get(Sources).can_generate(PythonSources, union_membership)\n-            or tgt.get(Sources).can_generate(ResourcesSources, union_membership)\n+    # Codegen is able to generate code in any arbitrary location, unlike sources normally being\n+    # rooted under the target definition. To determine source roots for these generated files, we\n+    # cannot use the normal `SourceRootRequest.for_target()` and we instead must determine\n+    # a source root for every individual generated file. So, we re-resolve the codegen sources here.\n+    python_and_resources_targets = []\n+    codegen_targets = []\n+    for tgt in request.targets:\n+        if tgt.has_field(PythonSources) or tgt.has_field(ResourcesSources):\n+            python_and_resources_targets.append(tgt)\n+        elif tgt.get(Sources).can_generate(PythonSources, union_membership) or tgt.get(\n+            Sources\n+        ).can_generate(ResourcesSources, union_membership):\n+            codegen_targets.append(tgt)\n+    codegen_sources = await MultiGet(\n+        Get(\n+            HydratedSources,\n+            HydrateSourcesRequest(\n+                tgt.get(Sources), for_sources_types=request.valid_sources_types, enable_codegen=True\n+            ),\n         )\n+        for tgt in codegen_targets\n+    )\n+    source_root_requests = [\n+        *(SourceRootRequest.for_target(tgt) for tgt in python_and_resources_targets),\n+        *(\n+            SourceRootRequest.for_file(f)\n+            for sources in codegen_sources\n+            for f in sources.snapshot.files\n+        ),\n+    ]\n+\n+    source_root_objs = await MultiGet(\n+        Get(SourceRoot, SourceRootRequest, req) for req in source_root_requests\n     )\n     source_root_paths = {source_root_obj.path for source_root_obj in source_root_objs}\n     return PythonSourceFiles(\n", "issue": "python_source_root not added to syspath\nI have a project structure along the lines of this:\r\n```\r\npackages/\r\n    package_a/\r\n        BUILD\r\n        __init__.py\r\n        package_a.py\r\n    package_b/\r\n        BUILD\r\n        __init__.py\r\n        package_b.py\r\nprotos/\r\n    package_a/\r\n        BUILD\r\n        foobar.proto\r\n    service_a/\r\n        BUILD\r\n        bazqux.proto\r\nservices/\r\n    service_a/\r\n        BUILD\r\n        __init__.py\r\n        main.py\r\n    service_b/\r\n        BUILD\r\n        __init__.py\r\n        main.py\r\n```\r\n\r\n`/packages`, `/protos` and `/services` are all configured as source roots. In each BUILD files residing in `protos/*` I have either the line `protobuf_library(python_source_root='services')` or `protobuf_library(python_source_root='packages')`, which means that the generated code ends up in `packages/*` or `services/*` instead.\r\n\r\nThis is working just fine as long as the running code is in the same source root as the generated protobuf code, but when code in `services/` is dependent on protos that has `python_source_root` set to `packages`, Python can't find the module unless an actual module from the same source root is also a dependency. I did some digging around, and it seems like the issue is that the source root specified in `python_source_root` is never explicitly added to Python's syspath, which is why imports fail if no \"real\" packages from the same source roots are used. So using the same example as earlier I see `services` and `protos`, but `packages`, where the generated code is placed, is missing.\r\n\r\nI created a [proof-of-concept repository](https://github.com/jyggen/pants-issue-11666) in case my rambling makes little sense. The issue can be seen by running `./pants test services/::`.\r\n\n", "before_files": [{"content": "# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).\n# Licensed under the Apache License, Version 2.0 (see LICENSE).\n\nfrom dataclasses import dataclass\nfrom typing import Iterable, List, Tuple, Type\n\nfrom pants.backend.python.target_types import PythonSources\nfrom pants.backend.python.util_rules import ancestor_files\nfrom pants.backend.python.util_rules.ancestor_files import AncestorFiles, AncestorFilesRequest\nfrom pants.core.target_types import FilesSources, ResourcesSources\nfrom pants.core.util_rules import source_files, stripped_source_files\nfrom pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest\nfrom pants.core.util_rules.stripped_source_files import StrippedSourceFiles\nfrom pants.engine.fs import MergeDigests, Snapshot\nfrom pants.engine.rules import Get, MultiGet, collect_rules, rule\nfrom pants.engine.target import Sources, Target\nfrom pants.engine.unions import UnionMembership\nfrom pants.source.source_root import SourceRoot, SourceRootRequest\nfrom pants.util.logging import LogLevel\nfrom pants.util.meta import frozen_after_init\n\n\n@dataclass(frozen=True)\nclass PythonSourceFiles:\n    \"\"\"Sources that can be introspected by Python, relative to a set of source roots.\n\n    Specifically, this will filter out to only have Python, and, optionally, resources() and\n    files() targets; and will add any missing `__init__.py` files to ensure that modules are\n    recognized correctly.\n\n    Use-cases that introspect Python source code (e.g., the `test, `lint`, `fmt` goals) can\n    request this type to get relevant sources that are still relative to their source roots.\n    That way the paths they report are the unstripped ones the user is familiar with.\n\n    The sources can also be imported and used by Python (e.g., for the `test` goal), but only\n    if sys.path is modified to include the source roots.\n    \"\"\"\n\n    source_files: SourceFiles\n    source_roots: Tuple[str, ...]  # Source roots for the specified source files.\n\n\n@dataclass(frozen=True)\nclass StrippedPythonSourceFiles:\n    \"\"\"A PythonSourceFiles that has had its source roots stripped.\"\"\"\n\n    stripped_source_files: StrippedSourceFiles\n\n\n@frozen_after_init\n@dataclass(unsafe_hash=True)\nclass PythonSourceFilesRequest:\n    targets: Tuple[Target, ...]\n    include_resources: bool\n    include_files: bool\n\n    def __init__(\n        self,\n        targets: Iterable[Target],\n        *,\n        include_resources: bool = True,\n        include_files: bool = False\n    ) -> None:\n        self.targets = tuple(targets)\n        self.include_resources = include_resources\n        self.include_files = include_files\n\n    @property\n    def valid_sources_types(self) -> Tuple[Type[Sources], ...]:\n        types: List[Type[Sources]] = [PythonSources]\n        if self.include_resources:\n            types.append(ResourcesSources)\n        if self.include_files:\n            types.append(FilesSources)\n        return tuple(types)\n\n\n@rule(level=LogLevel.DEBUG)\nasync def prepare_python_sources(\n    request: PythonSourceFilesRequest, union_membership: UnionMembership\n) -> PythonSourceFiles:\n    sources = await Get(\n        SourceFiles,\n        SourceFilesRequest(\n            (tgt.get(Sources) for tgt in request.targets),\n            for_sources_types=request.valid_sources_types,\n            enable_codegen=True,\n        ),\n    )\n\n    missing_init_files = await Get(\n        AncestorFiles,\n        AncestorFilesRequest(\"__init__.py\", sources.snapshot),\n    )\n\n    init_injected = await Get(\n        Snapshot,\n        MergeDigests((sources.snapshot.digest, missing_init_files.snapshot.digest)),\n    )\n\n    source_root_objs = await MultiGet(\n        Get(SourceRoot, SourceRootRequest, SourceRootRequest.for_target(tgt))\n        for tgt in request.targets\n        if (\n            tgt.has_field(PythonSources)\n            or tgt.has_field(ResourcesSources)\n            or tgt.get(Sources).can_generate(PythonSources, union_membership)\n            or tgt.get(Sources).can_generate(ResourcesSources, union_membership)\n        )\n    )\n    source_root_paths = {source_root_obj.path for source_root_obj in source_root_objs}\n    return PythonSourceFiles(\n        SourceFiles(init_injected, sources.unrooted_files), tuple(sorted(source_root_paths))\n    )\n\n\n@rule(level=LogLevel.DEBUG)\nasync def strip_python_sources(python_sources: PythonSourceFiles) -> StrippedPythonSourceFiles:\n    stripped = await Get(StrippedSourceFiles, SourceFiles, python_sources.source_files)\n    return StrippedPythonSourceFiles(stripped)\n\n\ndef rules():\n    return [\n        *collect_rules(),\n        *ancestor_files.rules(),\n        *source_files.rules(),\n        *stripped_source_files.rules(),\n    ]\n", "path": "src/python/pants/backend/python/util_rules/python_sources.py"}], "after_files": [{"content": "# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).\n# Licensed under the Apache License, Version 2.0 (see LICENSE).\n\nfrom dataclasses import dataclass\nfrom typing import Iterable, List, Tuple, Type\n\nfrom pants.backend.python.target_types import PythonSources\nfrom pants.backend.python.util_rules import ancestor_files\nfrom pants.backend.python.util_rules.ancestor_files import AncestorFiles, AncestorFilesRequest\nfrom pants.core.target_types import FilesSources, ResourcesSources\nfrom pants.core.util_rules import source_files, stripped_source_files\nfrom pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest\nfrom pants.core.util_rules.stripped_source_files import StrippedSourceFiles\nfrom pants.engine.fs import MergeDigests, Snapshot\nfrom pants.engine.rules import Get, MultiGet, collect_rules, rule\nfrom pants.engine.target import HydratedSources, HydrateSourcesRequest, Sources, Target\nfrom pants.engine.unions import UnionMembership\nfrom pants.source.source_root import SourceRoot, SourceRootRequest\nfrom pants.util.logging import LogLevel\nfrom pants.util.meta import frozen_after_init\n\n\n@dataclass(frozen=True)\nclass PythonSourceFiles:\n    \"\"\"Sources that can be introspected by Python, relative to a set of source roots.\n\n    Specifically, this will filter out to only have Python, and, optionally, resources() and\n    files() targets; and will add any missing `__init__.py` files to ensure that modules are\n    recognized correctly.\n\n    Use-cases that introspect Python source code (e.g., the `test, `lint`, `fmt` goals) can\n    request this type to get relevant sources that are still relative to their source roots.\n    That way the paths they report are the unstripped ones the user is familiar with.\n\n    The sources can also be imported and used by Python (e.g., for the `test` goal), but only\n    if sys.path is modified to include the source roots.\n    \"\"\"\n\n    source_files: SourceFiles\n    source_roots: Tuple[str, ...]  # Source roots for the specified source files.\n\n\n@dataclass(frozen=True)\nclass StrippedPythonSourceFiles:\n    \"\"\"A PythonSourceFiles that has had its source roots stripped.\"\"\"\n\n    stripped_source_files: StrippedSourceFiles\n\n\n@frozen_after_init\n@dataclass(unsafe_hash=True)\nclass PythonSourceFilesRequest:\n    targets: Tuple[Target, ...]\n    include_resources: bool\n    include_files: bool\n\n    def __init__(\n        self,\n        targets: Iterable[Target],\n        *,\n        include_resources: bool = True,\n        include_files: bool = False\n    ) -> None:\n        self.targets = tuple(targets)\n        self.include_resources = include_resources\n        self.include_files = include_files\n\n    @property\n    def valid_sources_types(self) -> Tuple[Type[Sources], ...]:\n        types: List[Type[Sources]] = [PythonSources]\n        if self.include_resources:\n            types.append(ResourcesSources)\n        if self.include_files:\n            types.append(FilesSources)\n        return tuple(types)\n\n\n@rule(level=LogLevel.DEBUG)\nasync def prepare_python_sources(\n    request: PythonSourceFilesRequest, union_membership: UnionMembership\n) -> PythonSourceFiles:\n    sources = await Get(\n        SourceFiles,\n        SourceFilesRequest(\n            (tgt.get(Sources) for tgt in request.targets),\n            for_sources_types=request.valid_sources_types,\n            enable_codegen=True,\n        ),\n    )\n\n    missing_init_files = await Get(\n        AncestorFiles,\n        AncestorFilesRequest(\"__init__.py\", sources.snapshot),\n    )\n\n    init_injected = await Get(\n        Snapshot,\n        MergeDigests((sources.snapshot.digest, missing_init_files.snapshot.digest)),\n    )\n\n    # Codegen is able to generate code in any arbitrary location, unlike sources normally being\n    # rooted under the target definition. To determine source roots for these generated files, we\n    # cannot use the normal `SourceRootRequest.for_target()` and we instead must determine\n    # a source root for every individual generated file. So, we re-resolve the codegen sources here.\n    python_and_resources_targets = []\n    codegen_targets = []\n    for tgt in request.targets:\n        if tgt.has_field(PythonSources) or tgt.has_field(ResourcesSources):\n            python_and_resources_targets.append(tgt)\n        elif tgt.get(Sources).can_generate(PythonSources, union_membership) or tgt.get(\n            Sources\n        ).can_generate(ResourcesSources, union_membership):\n            codegen_targets.append(tgt)\n    codegen_sources = await MultiGet(\n        Get(\n            HydratedSources,\n            HydrateSourcesRequest(\n                tgt.get(Sources), for_sources_types=request.valid_sources_types, enable_codegen=True\n            ),\n        )\n        for tgt in codegen_targets\n    )\n    source_root_requests = [\n        *(SourceRootRequest.for_target(tgt) for tgt in python_and_resources_targets),\n        *(\n            SourceRootRequest.for_file(f)\n            for sources in codegen_sources\n            for f in sources.snapshot.files\n        ),\n    ]\n\n    source_root_objs = await MultiGet(\n        Get(SourceRoot, SourceRootRequest, req) for req in source_root_requests\n    )\n    source_root_paths = {source_root_obj.path for source_root_obj in source_root_objs}\n    return PythonSourceFiles(\n        SourceFiles(init_injected, sources.unrooted_files), tuple(sorted(source_root_paths))\n    )\n\n\n@rule(level=LogLevel.DEBUG)\nasync def strip_python_sources(python_sources: PythonSourceFiles) -> StrippedPythonSourceFiles:\n    stripped = await Get(StrippedSourceFiles, SourceFiles, python_sources.source_files)\n    return StrippedPythonSourceFiles(stripped)\n\n\ndef rules():\n    return [\n        *collect_rules(),\n        *ancestor_files.rules(),\n        *source_files.rules(),\n        *stripped_source_files.rules(),\n    ]\n", "path": "src/python/pants/backend/python/util_rules/python_sources.py"}]}