File: module.py

package info (click to toggle)
dh-fortran 0.57
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 816 kB
  • sloc: f90: 5,705; python: 1,485; perl: 610; makefile: 80; sh: 7
file content (210 lines) | stat: -rw-r--r-- 7,657 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#!/usr/bin/python3
"""
Module file handling for Fortran

Copyright (C) 2025 Alastair McKinstry <mckinstry@debian.org>
Released under the GPL-3 Gnu Public License.
"""

import click
import magic
import os
import dhfortran.debhelper as dh
import dhfortran.cli as cli
import dhfortran.compilers as cmplrs
import gzip




def which_compiler_flavor_and_mod_version(modfile: str):
    """For a modfile (path), return a tuple of strings for the compiler and modversion"""

    compiler = ""
    mod_version = ""

    # Helper
    def get_first_line(modfile):
        try:
            magick = magic.from_file(modfile)
        except Exception as ex:
            raise Exception(f"Can't open modfile {modfile}: {ex}")
        if magick.startswith("gzip compressed"):
            with gzip.open(modfile, "rt") as f:
                return f.readline()
        # TODO: Other case(s)

    match get_first_line(modfile):
        case line if line.startswith("GFORTRAN"):
            mod_version = line.split()[3][1:-1]
            if line.find("created from flang") != -1:
                compiler = "flangext"
            else:
                compiler = "gfortran"
        case line if line.startswith("LCompilers Modfile"):
            version = line[18:]  # eg '0.45.0'
            compiler, mod_version = "lfortran", "0"
        case line if line.startswith("V35"):
            compiler, mod_version = "nvhpc", "35"
        case line if line.startswith("V"):
            # could also be PGI, but we don't ship PGI on Debian ... its an old flang
            compiler = "flang"
            mod_version = line.split()[0][1:]
        case line if line.startswith("!mod$"):
            compiler = ("flang",)
            mod_version = line.split()[1][1:]
        case _:
            compiler, mod_version = "unknown", "0"

        # mime = magick.split(";")[0]

        # if (mime == "test/plain") or (mime == "text/x-c++"):
        #     with open(modfile) as mf:
        #         strings = mf.readstrings()
        # else:
        #     if mime == "application/gzip":
        #         pass

    return (compiler, mod_version)


##
## Modfile-specific interface for debhelper
##
class ModFileHelper(dh.DhFortranHelper):

    def __init__(self, options):
        super().__init__(options, "fortran-mod", "dh_fortran_mod")
        # For tracking which compilers we need
        self.modversions = {}

    def compute_dest(self, modfile, target_dest=None):
        """Where does modfile go ?"""
        # cli.verbose_print("compute_dest [mod]  {modfile} {target_dest}")
        # Should be called by base DebHelper() to move files
        cmplr, version = which_compiler_flavor_and_mod_version(modfile)
        fmoddir = f"/usr/lib/{cmplrs.multiarch}/fortran/{cmplr}-mod-{version}"
        if target_dest is None:
            return fmoddir
        if target_dest.startswith("/"):
            return target_dest
        else:
            return f"{fmoddir}/{target_dest}"

    def process_file(self, pkg, oldfile, target_pkg, target_dest=None):
        """Called by DebHelper
        Module file gets copied; metadata gets stored"""
        cli.verbose_print(
            f"DEBUG: process_file [module]  name {oldfile} target_pkg {target_pkg} dest {target_dest}"
        )
        compiler, version = which_compiler_flavor_and_mod_version(oldfile)
        if pkg not in self.modversions:
            self.modversions[pkg] = set()
        self.modversions[pkg].add(f"{compiler}-mod-{version}")
        dest = self.compute_dest(oldfile, target_dest)
        self.install_dir(f"{target_pkg}/{dest}")
        self.doit(["cp", oldfile, f"{target_pkg}/{dest}"])


@click.command(
    context_settings=dict(
        ignore_unknown_options=True,
    )
)
@click.argument("files", nargs=-1, type=click.UNPROCESSED)
@cli.debhelper_common_args
def dh_fortran_mod(files, *args, **kwargs):
    """
        B<dh_fortran_mod> is a debhelper program that finds Fortran module and submodule files and
    adds dependencies to B<gfortran-$version> as required to the package using
    via the variable B<${fortran:Depends}>.

    B<dh_fortran_mod> is expected to be automatically added using the debhelper "addon" B<fortran_mod>
    ie. either automatically, by build-depending on 'dh-sequence-fortran-mod', or explicitly:

        dh $@ --with fortran


    B<dh_fortran_mod>Searches the debian/ directory for files B<debian/pkg.fortran-mod>$ which list
    module files to include, with the same syntax as debhelper install files.

    =head1 OPTIONS

    =over 4

    =item B<--sourcedir=>I<dir>

    Look in the specified directory for files to be installed.

    Typically Fortran module files are included in library development packages.

    =back

    =head1 TODO

    Add dh-fortran-mod support for generic fortran compilers (ifx, etc).

    =over 4

    B<dh_fortran_mod> will be expanded to find mod files automatically from the I<debian/tmp> directory.
    It will enable the installation of mod files in parallel for multiple compilers.
    It will install .smod files for Fortran 2018.

    The fortran-mod file syntax follows dh_install: pairs of sources and optional target directories.
    The default directory will be $fmoddir ( /usr/lib/$multiarch/fortran/$compiler_mod_directory/)
    If the target directory is absolute (starts with a  '/'), this directory is used in the target package.
    If the target  does not absolute, it will be treated as a subdirectory of $fmoddir.

    Currently four flavours of Fortran compiler are supported: gfortran-*  ('gfortran'),
    flang-new-* ('flang') , flang-to-external-fc-* ('flangext') and lfortran ('lfortran').

    $compiler_mod_directory is based on the compiler module version: currently gfortran-mod-15 for
    gfortran-14 (and older), flang-mod-15 for flang-new-15+ and lfortran-mod-0 for lfortran.

    For flang-to-external-fc-* the version is flangext-mod-15 (assuming gfortran-14 as the external compiler);
    in principle flang-to-external-fc ('flangext' flavour) and gfortran are intercompatible but
    intermixing is avoided.

    These will be updated for incompatible compiler versions.

    Support for Makefiles and debian/rules is given in /usr/share/debhelper/dh-fortran/fortran-support.mk

    This enables, for example:

            /usr/share/debhelper/dh-fortran/fortran-support.mk
            FMODDIR:= $(call get_fmoddir, gfortran)
            FC_EXE:= $(call get_fc_exe, $(FC_DEFAULT))

    """
    cli.verbose_print(f"dh_fortran_mod called with files {files} kwargs {kwargs}")
    d = ModFileHelper(dh.build_options(**kwargs))
    d.process_and_move_files(*files)
    depends = []
    for package in d.packages:
        if package in d.modversions:
            for m in d.modversions[package]:
                comps = set()
                for c in cmplrs.compilers:
                    if cmplrs.compilers[c]["mod"] == m:
                        comps.add(c)
                depends.append("|".join(comps) + " | " + m)
            d.addsubstvar(package, "fortran:Depends", ",".join(depends))
        if d.packages[package]["config_file"] and package in d.modversions:
            # config file defined, so create cleanup script
            for modversion in d.modversions[package]:
                d.autoscript(
                    package,
                    "postrm",
                    "postrm-fortran-mod",
                    {
                        "MULTIARCH": cmplrs.multiarch,
                        "FCOMPILERMOD": modversion,
                    },
                )
    d.save_substvars()


if __name__ == "__main__":
    import pytest

    pytest.main(["tests/module.py"])