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

Updated matplotlib hook to handle matplotlib.libs direcory (added in … #182

Merged
merged 2 commits into from
Oct 7, 2023

Conversation

zobac
Copy link

@zobac zobac commented Apr 19, 2023

…mpl >= 3.7.0

Added a hook for mpl_toolkits for the same reason.

Not sure if I handled mpl_toolkits < 3.7.0 properly (or if it needs to be handled for those versions)

…mpl >= 3.7.0

Added a hook for mpl_toolkits for the same reason.
Copy link
Member

@albertosottile albertosottile left a comment

Choose a reason for hiding this comment

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

Thank you for this contribution! I just have a few minor change requests.

py2exe/hooks.py Outdated Show resolved Hide resolved
py2exe/hooks.py Outdated Show resolved Hide resolved
py2exe/hooks.py Outdated Show resolved Hide resolved
py2exe/hooks.py Show resolved Hide resolved
py2exe/hooks.py Outdated Show resolved Hide resolved
py2exe/hooks.py Outdated Show resolved Hide resolved
@SeaHOH
Copy link
Contributor

SeaHOH commented May 23, 2023

How about if add special functions to handle packages distributed by delvewheel?

class TheFinderClass:
    def add_dlls(self, path, suffixes=(".dll", ".pyd")):
        path = os.path.realpath(path)
        if os.path.isdir(path):
            dlls = [os.path.join(path, fn)
                    for fn in os.listdir(path)
                    if fn.endswith(suffixes)]
            for dll in dlls:
                self.add_dll(dll)

def hook_delvewheel_dist(finder, module, need_ast=False):
    """Patches which distributed by delvewheel are not needed, here remove them
    and move all the .dll files to the root of sys.executable.
    If need_ast is False, module.__code_object__ will be replaced, return None.
    If need_ast is True, return the AST object for post-use, don't forget
    replace the code at the end.
    """
    import ast

    # we don't need ast.NodeTransformer to replace the whole subnode
    class NodeVisitor(ast.NodeVisitor):
        filter_assign = False
        filter_constant = False
        def visit_FunctionDef(self, node: ast.FunctionDef):
            if not node.name.startswith("_delvewheel_init_patch_"):
                return
            try:
                self.filter_assign = True
                self.generic_visit(node)
            except UserWarning:
                pass
            else:
                import warnings
                warnings.warn(f"{node.name} be matched, but no libs name be found",
                              RuntimeWarning, 3)
            finally:
                node.body = ast.parse("pass").body
                # all done, early cancel
                raise UserWarning
        def visit_Assign(self, node: ast.Assign):
            if not self.filter_assign:
                return
            target = node.targets[0]
            if isinstance(target, ast.Name) and target.id == "libs_dir":
                self.filter_constant = True
                self.generic_visit(node)
                self.filter_constant = False
        def visit_Constant(self, node: ast.Constant):
            self.add_dlls(node.value)
        if hasattr(ast, "Str")
            # py37 and below
            def visit_Str(self, node: ast.Str):
                self.add_dlls(node.s)
        def add_dlls(self, name):
            if self.filter_constant and isinstance(name, str):
                finder.add_dlls(finder, os.path.join(
                        os.path.dirname(module.__file__), os.pardir, name))
                # done, raise a signal
                raise UserWarning

    tree = ast.parse(module.__source__)
    try:
        NodeVisitor().visit(tree)
    except UserWarning:
        pass
    if need_ast:
        return tree
    module.__code_object__ = compile(
            tree, module.__file__, "exec", optimize=module.__optimize__)

@zobac
Copy link
Author

zobac commented May 24, 2023

Ultimately, I think @SeaHOH is onto a better solution, but I didn't know where to put/instanciate TheFinderClass so I left it out for now.

@SeaHOH
Copy link
Contributor

SeaHOH commented May 25, 2023

but I didn't know where to put/instanciate TheFinderClass

It imply py2exe.dllfinder.Scanner.

@albertosottile
Copy link
Member

@SeaHOH Would you like to submit a PR with your solution?

@albertosottile
Copy link
Member

I tried to run this branch on Actions and now it fails on numpy -> https://github.com/py2exe/py2exe/actions/runs/6436872258, I guess they started to use delvewheels as well.

@SeaHOH Let me know if you want to prepare a PR, otherwise I can implement this feature based on the code that you provided up here and credit you in a comment. What is not entirely clear to me is how to hook this general purpose code in the specific hooks (e.g. hook_matplotlib or hook_numpy without duplication. I guess we need to provide this in a separate class or even module.

@SeaHOH
Copy link
Contributor

SeaHOH commented Oct 7, 2023

Let me know if you want to prepare a PR

I'd like to do, but actually will not, I have no time to test a lot of packages.

I guess we need to provide this in a separate class or even module.

Yes, I think so, just a call, all works can be done in the function.

@albertosottile
Copy link
Member

@SeaHOH Fine for me, then I am going to merge this PR as it is, as I also do not have the time to implement a general solution right now.

@zobac Thank you for this contribution!

@albertosottile albertosottile merged commit 745fd7e into py2exe:master Oct 7, 2023
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.

3 participants