@@ -115,7 +115,9 @@ def _resolve_extras(self_name, reqs, extras):
115115 # extras The empty string in the set is just a way to make the handling
116116 # of no extras and a single extra easier and having a set of {"", "foo"}
117117 # is equivalent to having {"foo"}.
118- extras = extras or ["" ]
118+ #
119+ # Use a dict as a set here to simplify operations.
120+ extras = {x : None for x in extras or ["" ]}
119121
120122 self_reqs = []
121123 for req in reqs :
@@ -128,26 +130,29 @@ def _resolve_extras(self_name, reqs, extras):
128130 # easy to handle, lets do it.
129131 #
130132 # TODO @aignas 2023-12-08: add a test
131- extras = extras + req .extras
133+ extras = extras | { x : None for x in req .extras }
132134 else :
133135 # process these in a separate loop
134136 self_reqs .append (req )
135137
136- # A double loop is not strictly optimal, but always correct without recursion
137- for req in self_reqs :
138- if _evaluate_any (req , extras ):
139- extras = extras + req .extras
140- else :
141- continue
138+ for _ in range (10000 ):
139+ # handles packages with up to 10000 recursive extras
140+ new_extras = {}
141+ for req in self_reqs :
142+ if _evaluate_any (req , extras ):
143+ new_extras .update ({x : None for x in req .extras })
144+ else :
145+ continue
146+
147+ num_extras_before = len (extras )
148+ extras = extras | new_extras
149+ num_extras_after = len (new_extras )
142150
143- # Iterate through all packages to ensure that we include all of the extras from previously
144- # visited packages.
145- for req_ in self_reqs :
146- if _evaluate_any (req_ , extras ):
147- extras = extras + req_ .extras
151+ if num_extras_before == num_extras_after :
152+ break
148153
149154 # Poor mans set
150- return sorted ({ x : None for x in extras } )
155+ return sorted (extras )
151156
152157def _evaluate_any (req , extras ):
153158 for extra in extras :
0 commit comments