Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix CMake import's linker args sorting algorithm mangling -framework arguments #14099

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

2xsaiko
Copy link

@2xsaiko 2xsaiko commented Jan 8, 2025

Previous sorting algorithm mangles -framework args. Seems like it was already thought of in resolve_cmake_trace_targets (

# Do not sort flags here -- this breaks
# semantics of eg. `-framework CoreAudio`
# or `-Lpath/to/root -llibrary`
# see eg. #11113
) but not here.

Previous output:

Libraries:            ['-F/Library/Developer/CommandLineTools/SDKs/MacOSX15.sdk/System/Library/Frameworks', '-framework', '/opt/homebrew/lib/libvsg.a', '/opt/homebrew/lib/libvulkan.dylib', 'Cocoa', 'QuartzCore']

New output:

Libraries:            ['-F/Library/Developer/CommandLineTools/SDKs/MacOSX15.sdk/System/Library/Frameworks', '-framework', 'Cocoa', '-framework', 'QuartzCore', '/opt/homebrew/lib/libvsg.a', '/opt/homebrew/lib/libvulkan.dylib']

@2xsaiko 2xsaiko requested a review from jpakkane as a code owner January 8, 2025 22:30
Comment on lines 550 to 565
iter = libraries.__iter__()
_libraries = set()

while True:
try:
next = iter.__next__()
except StopIteration:
break

# Frameworks '-framework ...' are two arguments that need to stay together
if next == '-framework':
_libraries.add((next, iter.__next__()))
else:
_libraries.add((next,))

libraries = [x for xs in sorted(_libraries) for x in xs]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're reaching in to touch "dunder" methods, your' missing something. In this case you want iter(libraries) and next(iter). Of course, there's a hand helper that we could move from cargo/cfg.py to utils/universal.py called lookahead, which would simplify things, you'd just do:

_libraries: T.List[T.List[str]] = []
for v1, v2 in mesonlib.lookahead(libraries):
    if v1 == '-framework':
       _libraries.append([v1])
   else:
      if v2 is None:
          raise MesonException(...)add((v1, ))
      _libraries.append([v1, v2])
libraries = ...

I'm pointing this out because there's a potential that -framework doesn't have the required argument after it, and now there will be an unhandled python exception.

The other thing that concerns me here is that using set() here is that it may lead to invalid de-duplication of arguments, we run into this from time time that arguments cannot be safely de-duplicated. The sorting itself makes me slightly nervous in fact...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks, iter() and next() are much better. Also handled that extra StopIteration (by throwing a MesonException, the other way would be just to log a warning and add it on its own).

I don't think using lookahead here will improve much, since it still needs to actually consume that second argument; the way you wrote it, after adding the -framework argument plus name, the next run of the loop will get the same framework name argument on its own so it will be handled twice, leading to the same issue again, i.e. effectively

_libraries.append(['-framework', 'Foo'])
_libraries.append(['Foo'])

(also note that my using tuples here instead of lists is intentianal, as lists don't seem to have a defined ordering)


Of course as you say, the alternative here is to do nothing at all, I wanted to keep this as close to doing what it was doing before, just handling this extra case. How is this handled for the normal case, passing a framework to build_target(dependencies: [..])?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you're correct about lookahead returning [(0, 1), (1, 2), (2, 3), (3, None)], which isn't what we want.

Lists do have a defined order, the only containers in Python that don't have ordering guarantees are sets(), everything else has a guaranteed order, each item in a list will be in insertion order, and you can index into the list to get elements by index, which requires ordering guarantees.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, not defined order, sorry, defined hash:

TypeError: unhashable type: 'list'

@2xsaiko 2xsaiko force-pushed the push-mxprwpqxvzmr branch from f0aa271 to e46227a Compare January 9, 2025 13:00

def sort_link_args(args: T.List[str]) -> T.List[str]:
itr = iter(args)
result = set()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll need to help the type checker out here:

Suggested change
result = set()
result: T.Set[T.Union[T.Tuple[str], T.Tuple[str, str]]] = set()

@2xsaiko 2xsaiko force-pushed the push-mxprwpqxvzmr branch from e46227a to 80b16db Compare January 9, 2025 19:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants