]>
Commit | Line | Data |
---|---|---|
e0df8241 JR |
1 | import sys |
2 | from typing import TYPE_CHECKING, List, Dict | |
3 | from distutils.command.build import build as _build | |
4 | ||
5 | from ..warnings import SetuptoolsDeprecationWarning | |
6 | ||
7 | if sys.version_info >= (3, 8): | |
8 | from typing import Protocol | |
9 | elif TYPE_CHECKING: | |
10 | from typing_extensions import Protocol | |
11 | else: | |
12 | from abc import ABC as Protocol | |
13 | ||
14 | ||
15 | _ORIGINAL_SUBCOMMANDS = {"build_py", "build_clib", "build_ext", "build_scripts"} | |
16 | ||
17 | ||
18 | class build(_build): | |
19 | # copy to avoid sharing the object with parent class | |
20 | sub_commands = _build.sub_commands[:] | |
21 | ||
22 | def get_sub_commands(self): | |
23 | subcommands = {cmd[0] for cmd in _build.sub_commands} | |
24 | if subcommands - _ORIGINAL_SUBCOMMANDS: | |
25 | SetuptoolsDeprecationWarning.emit( | |
26 | "Direct usage of `distutils` commands", | |
27 | """ | |
28 | It seems that you are using `distutils.command.build` to add | |
29 | new subcommands. Using `distutils` directly is considered deprecated, | |
30 | please use `setuptools.command.build`. | |
31 | """, | |
32 | due_date=(2023, 12, 13), # Warning introduced in 13 Jun 2022. | |
33 | see_url="https://peps.python.org/pep-0632/", | |
34 | ) | |
35 | self.sub_commands = _build.sub_commands | |
36 | return super().get_sub_commands() | |
37 | ||
38 | ||
39 | class SubCommand(Protocol): | |
40 | """In order to support editable installations (see :pep:`660`) all | |
41 | build subcommands **SHOULD** implement this protocol. They also **MUST** inherit | |
42 | from ``setuptools.Command``. | |
43 | ||
44 | When creating an :pep:`editable wheel <660>`, ``setuptools`` will try to evaluate | |
45 | custom ``build`` subcommands using the following procedure: | |
46 | ||
47 | 1. ``setuptools`` will set the ``editable_mode`` attribute to ``True`` | |
48 | 2. ``setuptools`` will execute the ``run()`` command. | |
49 | ||
50 | .. important:: | |
51 | Subcommands **SHOULD** take advantage of ``editable_mode=True`` to adequate | |
52 | its behaviour or perform optimisations. | |
53 | ||
54 | For example, if a subcommand doesn't need to generate an extra file and | |
55 | all it does is to copy a source file into the build directory, | |
56 | ``run()`` **SHOULD** simply "early return". | |
57 | ||
58 | Similarly, if the subcommand creates files that would be placed alongside | |
59 | Python files in the final distribution, during an editable install | |
60 | the command **SHOULD** generate these files "in place" (i.e. write them to | |
61 | the original source directory, instead of using the build directory). | |
62 | Note that ``get_output_mapping()`` should reflect that and include mappings | |
63 | for "in place" builds accordingly. | |
64 | ||
65 | 3. ``setuptools`` use any knowledge it can derive from the return values of | |
66 | ``get_outputs()`` and ``get_output_mapping()`` to create an editable wheel. | |
67 | When relevant ``setuptools`` **MAY** attempt to use file links based on the value | |
68 | of ``get_output_mapping()``. Alternatively, ``setuptools`` **MAY** attempt to use | |
69 | :doc:`import hooks <python:reference/import>` to redirect any attempt to import | |
70 | to the directory with the original source code and other files built in place. | |
71 | ||
72 | Please note that custom sub-commands **SHOULD NOT** rely on ``run()`` being | |
73 | executed (or not) to provide correct return values for ``get_outputs()``, | |
74 | ``get_output_mapping()`` or ``get_source_files()``. The ``get_*`` methods should | |
75 | work independently of ``run()``. | |
76 | """ | |
77 | ||
78 | editable_mode: bool = False | |
79 | """Boolean flag that will be set to ``True`` when setuptools is used for an | |
80 | editable installation (see :pep:`660`). | |
81 | Implementations **SHOULD** explicitly set the default value of this attribute to | |
82 | ``False``. | |
83 | When subcommands run, they can use this flag to perform optimizations or change | |
84 | their behaviour accordingly. | |
85 | """ | |
86 | ||
87 | build_lib: str | |
88 | """String representing the directory where the build artifacts should be stored, | |
89 | e.g. ``build/lib``. | |
90 | For example, if a distribution wants to provide a Python module named ``pkg.mod``, | |
91 | then a corresponding file should be written to ``{build_lib}/package/module.py``. | |
92 | A way of thinking about this is that the files saved under ``build_lib`` | |
93 | would be eventually copied to one of the directories in :obj:`site.PREFIXES` | |
94 | upon installation. | |
95 | ||
96 | A command that produces platform-independent files (e.g. compiling text templates | |
97 | into Python functions), **CAN** initialize ``build_lib`` by copying its value from | |
98 | the ``build_py`` command. On the other hand, a command that produces | |
99 | platform-specific files **CAN** initialize ``build_lib`` by copying its value from | |
100 | the ``build_ext`` command. In general this is done inside the ``finalize_options`` | |
101 | method with the help of the ``set_undefined_options`` command:: | |
102 | ||
103 | def finalize_options(self): | |
104 | self.set_undefined_options("build_py", ("build_lib", "build_lib")) | |
105 | ... | |
106 | """ | |
107 | ||
108 | def initialize_options(self): | |
109 | """(Required by the original :class:`setuptools.Command` interface)""" | |
110 | ||
111 | def finalize_options(self): | |
112 | """(Required by the original :class:`setuptools.Command` interface)""" | |
113 | ||
114 | def run(self): | |
115 | """(Required by the original :class:`setuptools.Command` interface)""" | |
116 | ||
117 | def get_source_files(self) -> List[str]: | |
118 | """ | |
119 | Return a list of all files that are used by the command to create the expected | |
120 | outputs. | |
121 | For example, if your build command transpiles Java files into Python, you should | |
122 | list here all the Java files. | |
123 | The primary purpose of this function is to help populating the ``sdist`` | |
124 | with all the files necessary to build the distribution. | |
125 | All files should be strings relative to the project root directory. | |
126 | """ | |
127 | ||
128 | def get_outputs(self) -> List[str]: | |
129 | """ | |
130 | Return a list of files intended for distribution as they would have been | |
131 | produced by the build. | |
132 | These files should be strings in the form of | |
133 | ``"{build_lib}/destination/file/path"``. | |
134 | ||
135 | .. note:: | |
136 | The return value of ``get_output()`` should include all files used as keys | |
137 | in ``get_output_mapping()`` plus files that are generated during the build | |
138 | and don't correspond to any source file already present in the project. | |
139 | """ | |
140 | ||
141 | def get_output_mapping(self) -> Dict[str, str]: | |
142 | """ | |
143 | Return a mapping between destination files as they would be produced by the | |
144 | build (dict keys) into the respective existing (source) files (dict values). | |
145 | Existing (source) files should be represented as strings relative to the project | |
146 | root directory. | |
147 | Destination files should be strings in the form of | |
148 | ``"{build_lib}/destination/file/path"``. | |
149 | """ |