split-dex 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,56 @@
1
+ """The `package-order` strategy: whole packages in name order, without code analysis."""
2
+
3
+ import itertools
4
+
5
+ from split_dex import partition
6
+ from split_dex import strategy
7
+
8
+
9
+ class PackageOrder:
10
+ """Put whole packages, sorted by name, into files of about `cap` classes; no code analysis."""
11
+
12
+ def __init__(self, *, cap: int = 1000) -> None:
13
+ """Create the strategy.
14
+
15
+ Args:
16
+ cap: The target number of classes per DEX file.
17
+
18
+ Raises:
19
+ ValueError: If `cap` is below 1.
20
+ """
21
+ if cap < 1:
22
+ raise ValueError("cap must be >= 1")
23
+ self._cap = cap
24
+
25
+ def plan(self, classes: strategy.Classes) -> list[strategy.Group]:
26
+ """Sort the classes by package, then by name, and fill files with whole packages.
27
+
28
+ The classes are sorted by package first: sorted by descriptor alone, a package would be
29
+ interleaved with its subpackages (`Lcom/a/z;` sorts after `Lcom/a/m/X;`). Packages next
30
+ to each other in name order are usually parts of one library or feature, so this keeps
31
+ related code together without reading any code: it is fast, and a new version of the app
32
+ moves few classes between files. Each package is a block named after it, so a group that
33
+ goes over a 64K limit is cut at a package boundary.
34
+
35
+ Args:
36
+ classes: The classes to group.
37
+
38
+ Returns:
39
+ The groups.
40
+ """
41
+ descriptors = classes.descriptors
42
+ ordered = sorted(
43
+ classes.ids,
44
+ key=lambda class_id: (
45
+ partition.package_of(descriptors[class_id]),
46
+ descriptors[class_id],
47
+ ),
48
+ )
49
+ runs = itertools.groupby(
50
+ ordered, key=lambda class_id: partition.package_of(descriptors[class_id])
51
+ )
52
+ packages = [list(ids) for _, ids in runs]
53
+ return [
54
+ strategy.Group.of_packages(group, descriptors)
55
+ for group in partition.fill_groups(packages, self._cap)
56
+ ]
@@ -0,0 +1,23 @@
1
+ """The `single` strategy: all the classes in one DEX file."""
2
+
3
+ from split_dex import strategy
4
+
5
+
6
+ class Single:
7
+ """Put all the classes in one DEX file, cut only where it goes over a 64K limit."""
8
+
9
+ def plan(self, classes: strategy.Classes) -> list[strategy.Group]:
10
+ """Put every class in one group, in multidex order.
11
+
12
+ Each class is a block of its own, so a group that goes over a 64K limit is cut in the
13
+ middle, and the halves are cut again until each one fits.
14
+
15
+ Args:
16
+ classes: The classes to group.
17
+
18
+ Returns:
19
+ One group, or none when the set is empty.
20
+ """
21
+ if len(classes.ids) == 0:
22
+ return []
23
+ return [strategy.Group.of_classes(classes.ids)]
split_dex/strategy.py ADDED
@@ -0,0 +1,233 @@
1
+ """The interface between the splitter and the strategies that group classes into DEX files.
2
+
3
+ The splitter sorts the classes of an APK into sets (see `split_dex.class_sets`). For each set, a
4
+ strategy decides how its classes are grouped: it returns groups, and each group becomes one DEX
5
+ file, or several when it goes over a 64K limit of the DEX format.
6
+
7
+ A strategy is a class whose keyword-only constructor parameters are its settings and whose
8
+ `plan` method returns the groups. Built-in strategies are registered in `split_dex.registry`; a
9
+ user's strategy is given on the command line as `FILE.py:CLASS` or `MODULE:CLASS`, or installed
10
+ as a plugin (see `split_dex.registry`).
11
+ """
12
+
13
+ import collections.abc
14
+ import dataclasses
15
+ import enum
16
+ import itertools
17
+ import typing
18
+ from typing import Self
19
+
20
+ import dexpp
21
+
22
+ import split_dex
23
+ from split_dex import partition
24
+
25
+
26
+ class StrategyError(split_dex.Error):
27
+ """Raised when a strategy cannot be created, or returns groups that break the rules."""
28
+
29
+
30
+ @enum.unique
31
+ class LinkKind(enum.Enum):
32
+ """The kinds of links between two classes."""
33
+
34
+ SUPERCLASS = "superclass"
35
+ """One class is the superclass of the other."""
36
+ INTERFACE = "interface"
37
+ """One class implements the other, an interface."""
38
+ NESTING = "nesting"
39
+ """One class is nested in the other: `LFoo$Bar;` is linked to `LFoo;`."""
40
+ CALL = "call"
41
+ """A method of one class calls a method of the other."""
42
+ FIELD = "field"
43
+ """A method of one class reads or writes a field of the other."""
44
+ TYPE = "type"
45
+ """A method of one class uses the other as a type: `new`, a cast, `instanceof`, ..."""
46
+
47
+
48
+ @dataclasses.dataclass(frozen=True)
49
+ class Block:
50
+ """Classes that should stay in the same DEX file.
51
+
52
+ When a group goes over a 64K limit, the splitter cuts it at the block boundary nearest its
53
+ middle. Only a group of a single block is cut inside that block.
54
+
55
+ Attributes:
56
+ class_ids: The classes, in write order.
57
+ name: How the warning names the block when it has to be cut, such as
58
+ `package Lcom/a`. None for a block that can be cut without a warning.
59
+ """
60
+
61
+ class_ids: tuple[int, ...]
62
+ name: str | None = None
63
+
64
+
65
+ @dataclasses.dataclass(frozen=True)
66
+ class Group:
67
+ """The classes of one DEX file, in write order, split into blocks.
68
+
69
+ Attributes:
70
+ blocks: The blocks, in write order.
71
+ """
72
+
73
+ blocks: tuple[Block, ...]
74
+
75
+ @classmethod
76
+ def of_classes(cls, class_ids: collections.abc.Iterable[int]) -> Self:
77
+ """A group with one block per class, so that a cut can fall anywhere.
78
+
79
+ Args:
80
+ class_ids: The classes, in write order.
81
+
82
+ Returns:
83
+ The group.
84
+ """
85
+ return cls(tuple(Block((class_id,)) for class_id in class_ids))
86
+
87
+ @classmethod
88
+ def of_packages(
89
+ cls, class_ids: collections.abc.Iterable[int], descriptors: collections.abc.Sequence[str]
90
+ ) -> Self:
91
+ """A group with one block per package, named after it.
92
+
93
+ A group that goes over a 64K limit is then cut at a package boundary, and a package that
94
+ has to be cut is named in the warning.
95
+
96
+ Args:
97
+ class_ids: The classes, in write order. The classes of a package should be next to
98
+ each other: each run of one package's classes becomes a block.
99
+ descriptors: The descriptor of every class, by class id.
100
+
101
+ Returns:
102
+ The group.
103
+ """
104
+ runs = itertools.groupby(
105
+ class_ids, key=lambda class_id: partition.package_of(descriptors[class_id])
106
+ )
107
+ return cls(tuple(Block(tuple(ids), f"package {package}") for package, ids in runs))
108
+
109
+ @property
110
+ def class_ids(self) -> list[int]:
111
+ """Every class of the group, in write order."""
112
+ return [class_id for block in self.blocks for class_id in block.class_ids]
113
+
114
+
115
+ class App(typing.Protocol):
116
+ """What the splitter knows about the APK. Every set shares it."""
117
+
118
+ @property
119
+ def descriptors(self) -> collections.abc.Sequence[str]:
120
+ """The descriptor of every class of the APK, by class id."""
121
+ ...
122
+
123
+ def source_dex(self, class_id: int) -> int:
124
+ """The input DEX file a class comes from: 0 for `classes.dex`, 1 for `classes2.dex`, ..."""
125
+ ...
126
+
127
+ def links(
128
+ self,
129
+ class_ids: collections.abc.Sequence[int],
130
+ weights: collections.abc.Mapping[LinkKind, int],
131
+ ) -> partition.AffinityGraph:
132
+ """The links among the given classes, each link counted with the weight of its kind."""
133
+ ...
134
+
135
+ @property
136
+ def apk(self) -> dexpp.Apk:
137
+ """The APK."""
138
+ ...
139
+
140
+ @property
141
+ def analysis(self) -> dexpp.AnalysisContext:
142
+ """The analysis of the APK's code."""
143
+ ...
144
+
145
+
146
+ class Classes:
147
+ """A set of classes to group, and what a strategy can know about them.
148
+
149
+ Class ids count every class of the APK, in multidex order, so they index `descriptors`
150
+ directly. A class defined in more than one DEX file is counted once, for its first copy.
151
+ """
152
+
153
+ def __init__(self, ids: collections.abc.Sequence[int], app: App) -> None:
154
+ """Create the view of a set of classes.
155
+
156
+ Args:
157
+ ids: The classes of the set, in multidex order.
158
+ app: What the splitter knows about the APK.
159
+ """
160
+ self._ids = ids
161
+ self._app = app
162
+
163
+ @property
164
+ def ids(self) -> collections.abc.Sequence[int]:
165
+ """The classes of the set, in multidex order."""
166
+ return self._ids
167
+
168
+ @property
169
+ def descriptors(self) -> collections.abc.Sequence[str]:
170
+ """The descriptor of every class of the APK, by class id."""
171
+ return self._app.descriptors
172
+
173
+ def source_dex(self, class_id: int) -> int:
174
+ """The input DEX file a class comes from: 0 for `classes.dex`, 1 for `classes2.dex`, ...
175
+
176
+ Args:
177
+ class_id: The class.
178
+
179
+ Returns:
180
+ The position of its DEX file in multidex order.
181
+ """
182
+ return self._app.source_dex(class_id)
183
+
184
+ def links(
185
+ self, weights: collections.abc.Mapping[LinkKind, int] | None = None
186
+ ) -> partition.AffinityGraph:
187
+ """The links among the classes of the set, counted per pair of classes.
188
+
189
+ The graph is built on each call, from the code of the classes of the set.
190
+
191
+ Args:
192
+ weights: The weight of each kind of link. A kind left out weighs 1, and a kind that
193
+ weighs 0 is ignored. None weighs every link 1.
194
+
195
+ Returns:
196
+ The graph, over class ids. The weight of an edge is the sum of the weights of the
197
+ links between its two classes, in both directions.
198
+ """
199
+ merged = dict.fromkeys(LinkKind, 1)
200
+ if weights is not None:
201
+ merged.update(weights)
202
+ return self._app.links(self._ids, merged)
203
+
204
+ @property
205
+ def apk(self) -> dexpp.Apk:
206
+ """The APK, for its manifest, resources or entries."""
207
+ return self._app.apk
208
+
209
+ @property
210
+ def analysis(self) -> dexpp.AnalysisContext:
211
+ """The analysis of the APK's code: classes, methods, call graph, class hierarchy, xrefs.
212
+
213
+ It covers every class of the APK, not only the set. It is made on first use and shared by
214
+ every set.
215
+ """
216
+ return self._app.analysis
217
+
218
+
219
+ @typing.runtime_checkable
220
+ class Strategy(typing.Protocol):
221
+ """How the classes of a set are grouped into DEX files."""
222
+
223
+ def plan(self, classes: Classes) -> collections.abc.Sequence[Group]:
224
+ """Group the classes of a set.
225
+
226
+ Args:
227
+ classes: The classes to group.
228
+
229
+ Returns:
230
+ The groups, in the order their DEX files are written. Every class of the set must be
231
+ in exactly one group, and no group or block may be empty.
232
+ """
233
+ ...
@@ -0,0 +1,253 @@
1
+ Metadata-Version: 2.5
2
+ Name: split-dex
3
+ Version: 0.1.0
4
+ Summary: Split the classes of an Android APK into grouped DEX files
5
+ Project-URL: Homepage, https://github.com/burogurama/split-dex
6
+ Project-URL: Issues, https://github.com/burogurama/split-dex/issues
7
+ Project-URL: Changelog, https://github.com/burogurama/split-dex/blob/main/CHANGELOG.md
8
+ Author: Mohamed Nasser
9
+ License-Expression: Apache-2.0
10
+ License-File: LICENSE
11
+ Keywords: android,apk,dex,multidex,split
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Software Development :: Build Tools
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.14
22
+ Requires-Dist: dexpp<0.3,>=0.2.0
23
+ Description-Content-Type: text/markdown
24
+
25
+ # split-dex
26
+
27
+ `split-dex` splits the classes of an Android APK into grouped DEX files.
28
+
29
+ It reads every `classes*.dex` in the APK, sorts the classes into sets, and
30
+ splits each set into DEX files with a strategy. Both are chosen on the command
31
+ line. Every class ends up in exactly one output DEX file, two sets never share a
32
+ file, and maps record where each class went.
33
+
34
+ It is written in Python, on the Python binding of
35
+ [dexpp](https://github.com/burogurama/dexpp), which reads, analyses and writes
36
+ the DEX files.
37
+
38
+ ## Usage
39
+
40
+ ```sh
41
+ split-dex --apk app.apk --out out/
42
+ ```
43
+
44
+ With no other option, every class is in one set, `all`, split by the
45
+ `package-affinity` strategy into files of about 1000 classes.
46
+
47
+ | Option | Meaning |
48
+ | ------ | ------- |
49
+ | `--apk FILE` | Input APK. All its `classes*.dex` entries are read. Required. |
50
+ | `--out DIR` | Output directory. Created if it does not exist. Required. |
51
+ | `--set NAME=STRATEGY[,KEY=VALUE...]` | A set of classes and the strategy that splits it, with the strategy's settings. `STRATEGY` is a built-in or plugin name (`--list-strategies` shows them), or your own class as `MODULE:CLASS` or `FILE.py:CLASS` (see [Writing a strategy](#writing-a-strategy)). Repeat it for more sets. The DEX files are written set by set, in this order. |
52
+ | `--assign PATTERN[,PATTERN...]=NAME` | Send the classes whose names match a pattern to the set `NAME`. Repeat it; each class goes to the first pattern it matches. |
53
+ | `--list-strategies` | Show the strategies and their settings. |
54
+
55
+ With a single `--set`, `--assign` is not needed: every class goes to that set.
56
+ With several, every class must match some pattern; end with `'**=NAME'` to
57
+ catch the rest.
58
+
59
+ A DEX file can reference at most 65,536 methods, 65,536 fields and 65,535
60
+ types. When a group of classes goes over one of these limits, it is cut in two,
61
+ and each half is written again (and cut again if needed). The cut falls nearest
62
+ the middle, between the units the strategy keeps together, such as packages.
63
+ Only a unit too big for one DEX file is cut inside, and `split-dex` prints a
64
+ warning that names it.
65
+
66
+ If the APK defines a class in more than one DEX file, only the first copy in
67
+ multidex order is kept. That is the copy Android loads.
68
+
69
+ ### Patterns
70
+
71
+ Patterns match class names such as `com.myapp.Main` or `com.myapp.Outer$Inner`.
72
+ `*` matches any characters except a dot, and `**` matches any characters:
73
+
74
+ | Pattern | Matches |
75
+ | ------- | ------- |
76
+ | `com.myapp.**` | Every class under `com.myapp`, subpackages included. |
77
+ | `com.myapp.*` | The classes directly in `com.myapp`. |
78
+ | `com.myapp.Main` | One class. |
79
+ | `**` | Every class. |
80
+ | `@FILE` | The patterns listed in `FILE`, one per line. Blank lines and lines starting with `#` are skipped. |
81
+
82
+ ### Examples
83
+
84
+ Smaller files:
85
+
86
+ ```sh
87
+ split-dex --apk app.apk --out out/ --set all=package-affinity,cap=500
88
+ ```
89
+
90
+ Split the app's own classes, and put all the library classes in one DEX file
91
+ (cut in halves where it goes over the 64K limits):
92
+
93
+ ```sh
94
+ split-dex --apk app.apk --out out/ \
95
+ --set app=package-affinity,cap=500 \
96
+ --set libraries=single \
97
+ --assign 'com.myapp.**=app' \
98
+ --assign '**=libraries'
99
+ ```
100
+
101
+ Split the app's own classes, but keep some of its packages with the libraries,
102
+ which stay in their original DEX files (at most 16):
103
+
104
+ ```sh
105
+ split-dex --apk app.apk --out out/ \
106
+ --set app=package-affinity,cap=1000 \
107
+ --set libraries=original,max_files=16 \
108
+ --assign '@startup-packages.txt=libraries' \
109
+ --assign 'com.myapp.**=app' \
110
+ --assign '**=libraries'
111
+ ```
112
+
113
+ ## Output
114
+
115
+ In the output directory:
116
+
117
+ | File | Content |
118
+ | ----------- | ------------------------------------------------------- |
119
+ | `<k>.dex` | The DEX files: `0.dex`, `1.dex`, ..., set by set. |
120
+ | `index.csv` | `class,file`: every class descriptor and its DEX file. |
121
+ | `sets.csv` | `file,set`: the set of each DEX file. |
122
+
123
+ Each DEX file is a valid, standalone DEX.
124
+
125
+ Running again into the same directory replaces these files. If an earlier run
126
+ wrote more `<k>.dex` files, the extra ones are removed, so every DEX file in the
127
+ directory is listed in `index.csv`. Other files in the directory are not
128
+ touched.
129
+
130
+ At the end, `split-dex` prints a summary: how many classes were read, how many
131
+ files were written, how many groups had to be cut for the 64K limits, and the
132
+ classes and files of each set.
133
+
134
+ ## Install
135
+
136
+ split-dex needs Python 3.14 or newer. Install the `split-dex` command from
137
+ PyPI with [uv](https://docs.astral.sh/uv/) or pip:
138
+
139
+ ```sh
140
+ uv tool install split-dex
141
+ # or
142
+ pip install split-dex
143
+ ```
144
+
145
+ Its dependency dexpp has prebuilt wheels for Linux x86_64 (glibc 2.28+ and
146
+ musl). On other platforms, the installer builds dexpp from source, which needs
147
+ a C++23 compiler (GCC 13 or newer, or Clang 19 or newer) and CMake 3.18 or
148
+ newer.
149
+
150
+ From a checkout, `uv tool install .` installs the command.
151
+
152
+ ## Writing a strategy
153
+
154
+ The command-line options cover the common setups. A strategy of your own is for
155
+ the cases they don't. A strategy is a Python class:
156
+
157
+ ```python
158
+ from split_dex import strategy
159
+
160
+
161
+ class OnePerFile:
162
+ """Each class in a DEX file of its own, up to `limit` files."""
163
+
164
+ def __init__(self, *, limit: int = 100) -> None:
165
+ self._limit = limit
166
+
167
+ def plan(self, classes: strategy.Classes) -> list[strategy.Group]:
168
+ ids = list(classes.ids)
169
+ groups = [strategy.Group.of_classes([class_id]) for class_id in ids[: self._limit - 1]]
170
+ return groups + [strategy.Group.of_classes(ids[self._limit - 1 :])]
171
+ ```
172
+
173
+ - Its keyword-only constructor parameters are its settings. The command line
174
+ converts each `KEY=VALUE` to the parameter's type: int, float, bool, str or
175
+ Path, optionally `| None`.
176
+ - `plan(classes)` receives the classes of one set and returns the groups to
177
+ write, in order. Each group (`strategy.Group`) becomes one DEX file, and is
178
+ made of blocks (`strategy.Block`): classes that should stay in one file.
179
+ When a group goes over a 64K limit, the splitter cuts it at the block
180
+ boundary nearest its middle. `Group.of_classes(ids)` makes one block per
181
+ class.
182
+ - The splitter checks that every class of the set is in exactly one group;
183
+ otherwise the run fails with an error that names the strategy's mistake.
184
+
185
+ What `classes` (`strategy.Classes`) gives:
186
+
187
+ | Member | Meaning |
188
+ | ------ | ------- |
189
+ | `ids` | The class ids of the set, in multidex order. |
190
+ | `descriptors` | The descriptor of every class of the APK, indexed by class id. |
191
+ | `source_dex(id)` | The input DEX file of a class: 0 for `classes.dex`, 1 for `classes2.dex`, ... |
192
+ | `links(weights=None)` | The links among the classes of the set, as a `partition.AffinityGraph`. `weights` maps a `strategy.LinkKind` (superclass, interface, nesting, call, field, type) to its weight; a kind left out weighs 1, and 0 ignores it. |
193
+ | `analysis` | dexpp's `AnalysisContext` for the whole APK: classes, methods, call graph, class hierarchy, cross-references. |
194
+ | `apk` | dexpp's `Apk`: manifest, resources, entries. |
195
+
196
+ There are four ways to use it:
197
+
198
+ - From a file: `--set all=./my_strategy.py:OnePerFile,limit=8`.
199
+ - From a module Python can import: `--set all=my_package.strategies:OnePerFile`.
200
+ - As a plugin. An installed package declares the strategy under the
201
+ entry-point group `split_dex.strategies` in its `pyproject.toml`; it is then
202
+ used by name, and `--list-strategies` shows it:
203
+
204
+ ```toml
205
+ [project.entry-points."split_dex.strategies"]
206
+ one-per-file = "my_package.strategies:OnePerFile"
207
+ ```
208
+
209
+ - Built into split-dex: put the module in `split_dex/strategies/` and add the
210
+ class to `STRATEGIES` in `split_dex/registry.py`.
211
+
212
+ A strategy is ordinary Python code that runs inside split-dex, so only use
213
+ files, modules and plugins you trust.
214
+
215
+ From Python, `split_dex.splitter.split()` takes `split_dex.splitter.Options`,
216
+ whose sets can use any object with a `plan` method.
217
+
218
+ ## Development
219
+
220
+ ```sh
221
+ uv sync # .venv with split-dex, dexpp and the dev tools
222
+ uv run pytest # the tests
223
+ uv run ruff format --check # formatting
224
+ uv run ruff check # lint
225
+ uv run mypy # types
226
+ ```
227
+
228
+ The tests build small APKs in memory. Their classes are copies of the template
229
+ classes in `tests/data/templates.dex`, renamed for each test. The templates are
230
+ compiled from `tests/data/Templates.java`; `tests/data/make_templates.sh`
231
+ rebuilds them (it needs `javac` and the Android SDK's `d8`).
232
+
233
+ To move to a newer dexpp, update its version range in `dependencies` in
234
+ `pyproject.toml` and run `uv lock`.
235
+
236
+ To release: set `version` in `pyproject.toml`, turn the `## Unreleased`
237
+ section of `CHANGELOG.md` into `## <version>`, and push a `v<version>` tag. The
238
+ release workflow builds the wheel and the sdist, checks that the wheel installs
239
+ and runs, creates the GitHub release with that changelog section as notes, and
240
+ uploads the same files to PyPI.
241
+
242
+ ## Layout
243
+
244
+ - `split_dex/cli.py` — the command-line interface.
245
+ - `split_dex/splitter.py` — reads the APK, sorts the classes into sets, runs
246
+ each set's strategy, checks its groups, and writes the DEX files and maps.
247
+ - `split_dex/class_sets.py` — sets, patterns, and how classes are assigned.
248
+ - `split_dex/strategy.py` — the interface between the splitter and strategies.
249
+ - `split_dex/registry.py` — finds strategies (built-in, plugin, module or file)
250
+ and creates them from their settings.
251
+ - `split_dex/strategies/` — the built-in strategies.
252
+ - `split_dex/partition.py` — the package-affinity grouping algorithm.
253
+ - `tests/` — the unit tests (pytest).
@@ -0,0 +1,18 @@
1
+ split_dex/__init__.py,sha256=yDE--EYxkoM-n19N4EXVitZtitbDlvpHBHu2dbVcdBs,139
2
+ split_dex/class_sets.py,sha256=Ud6qt8p4epXDeUYyoaWTN6AsNYa8Jyaoa1ft5DV3Y8o,4450
3
+ split_dex/cli.py,sha256=ZlvD4usAoGMMADdfV2wEeM6S6moP1Y2gyB1E9STJhog,8468
4
+ split_dex/partition.py,sha256=wlLfSS08Pu92uXgJuwCQfFPoSBWUdxtpyVugYL4mPr4,8677
5
+ split_dex/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ split_dex/registry.py,sha256=hDF5z50G4jjEJshC92OEeiAmk4mp8_MQwdIwDGQRJ5o,8195
7
+ split_dex/splitter.py,sha256=z6AjipuSbwZLuAuHtwZ7_IzxabC84m6hCa4S17kcPew,22462
8
+ split_dex/strategy.py,sha256=-QeGECv1G20QVQuDM_I08bncWyv4x4TcU3Gz-zAUC5I,7801
9
+ split_dex/strategies/__init__.py,sha256=Uo3u3Zt2TKBn_cwXSXPeHx4nDJAs9UUARdgJHPO7Q6w,84
10
+ split_dex/strategies/original.py,sha256=ND9DK9EbYDfuylnd0cs42RxS-n0hi85Bmi2757LOZCI,2252
11
+ split_dex/strategies/package_affinity.py,sha256=dWycvxMUwflDnTwD4043V06gxJa2j52d39mCaCSzDLI,1296
12
+ split_dex/strategies/package_order.py,sha256=KiI0cXbFfR1St846JTiDIqdITL5hRHDuVbuv1mrejZg,2022
13
+ split_dex/strategies/single.py,sha256=6Ih8Jn28BnpO-VcLd8o5Fy1yuNJbavf0fg36gIyln4w,745
14
+ split_dex-0.1.0.dist-info/METADATA,sha256=DFHwfYJCDUPeynDNoWbW04121mZVfq2ZSujMuRdwCXc,10556
15
+ split_dex-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
16
+ split_dex-0.1.0.dist-info/entry_points.txt,sha256=qiaGTLQ0TUu5ts-cKQBGIe7XZDyylMP6XsCmFNl6Zbo,49
17
+ split_dex-0.1.0.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
18
+ split_dex-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ split-dex = split_dex.cli:main