Skip to content

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

Merged
albertosottile merged 2 commits into
py2exe:masterfrom
zobac:master
Oct 7, 2023
Merged

Updated matplotlib hook to handle matplotlib.libs direcory (added in …#182
albertosottile merged 2 commits into
py2exe:masterfrom
zobac:master

Conversation

@zobac

@zobac zobac commented Apr 19, 2023

Copy link
Copy Markdown

…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.

@albertosottile albertosottile left a comment

Copy link
Copy Markdown
Member

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.

Comment thread py2exe/hooks.py Outdated
Comment thread py2exe/hooks.py Outdated
Comment thread py2exe/hooks.py Outdated
Comment thread py2exe/hooks.py
Comment thread py2exe/hooks.py Outdated
Comment thread py2exe/hooks.py Outdated
@SeaHOH

SeaHOH commented May 23, 2023

Copy link
Copy Markdown
Contributor

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

zobac commented May 24, 2023

Copy link
Copy Markdown
Author

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

SeaHOH commented May 25, 2023

Copy link
Copy Markdown
Contributor

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

It imply py2exe.dllfinder.Scanner.

@albertosottile

Copy link
Copy Markdown
Member

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

@albertosottile

Copy link
Copy Markdown
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

SeaHOH commented Oct 7, 2023

Copy link
Copy Markdown
Contributor

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
Copy Markdown
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