Fallback to /tmp.
[packages/rpm-build-tools.git] / rediff-patches.py
1 #!/usr/bin/python3
2
3 # rediff-patches.py name.spec
4
5 import argparse
6 import collections
7 import logging
8 import os
9 import re
10 import rpm
11 import shutil
12 import subprocess
13 import sys
14 import tempfile
15
16 RPMBUILD_ISPATCH = (1<<1)
17
18 def prepare_spec(r, patch_nr, before=False):
19     tempspec = tempfile.NamedTemporaryFile()
20     re_patch = re.compile(r'^%patch(?P<patch_number>\d+)\w*')
21     for line in r.parsed.split('\n'):
22         m = re_patch.match(line)
23         if m:
24             patch_number = int(m.group('patch_number'))
25             if patch_nr == patch_number:
26                 if before:
27                     tempspec.write(b"exit 0\n# here was patch%d\n" % patch_nr)
28                 else:
29                     line = re.sub(r'#.*', "", line)
30                     tempspec.write(b"%s\nexit 0\n" % line.encode('utf-8'))
31                 continue
32         tempspec.write(b"%s\n" % line.encode('utf-8'))
33     tempspec.flush()
34     return tempspec
35
36 def unpack(spec, appsourcedir, builddir):
37     cmd = [ 'rpmbuild', '-bp',
38            '--define',  '_builddir %s' % builddir,
39            '--define', '_specdir %s' % appsourcedir,
40            '--define', '_sourcedir %s' % appsourcedir,
41            '--define', '_enable_debug_packages 0',
42            '--define', '_default_patch_fuzz 2',
43            spec ]
44     logging.debug("running %s" % repr(cmd))
45     try:
46         res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=True,
47                               env={'LC_ALL': 'C.UTF-8'}, timeout=600)
48     except subprocess.CalledProcessError as err:
49         logging.error("unpacking exited with status code %d." % err.returncode)
50         logging.error("STDOUT:")
51         if err.stdout:
52             for line in err.stdout.decode('utf-8').split("\n"):
53                 logging.error(line)
54         logging.error("STDERR:")
55         if err.stderr:
56             for line in err.stderr.decode('utf-8').split("\n"):
57                 logging.error(line)
58         raise
59     else:
60         logging.debug("unpacking exited with status code %d." % res.returncode)
61         logging.debug("STDOUT/STDERR:")
62         if res.stdout:
63             for line in res.stdout.decode('utf-8').split("\n"):
64                 logging.debug(line)
65
66
67 def patch_comment_get(patch):
68     patch_comment = ""
69     patch_got = False
70     with open(patch, 'rt') as f:
71         for line in f:
72             if line.startswith('diff ') or line.startswith('--- '):
73                 patch_got = True
74                 break
75             patch_comment += line
76     return patch_comment if patch_got else ""
77
78 def diff(diffdir_org, diffdir, builddir, patch_comment, output):
79     diffdir_org = os.path.basename(diffdir_org)
80     diffdir = os.path.basename(diffdir)
81
82     with open(output, 'wt') as f:
83         if patch_comment:
84             f.write(patch_comment)
85             f.flush()
86         cmd = [ 'diff', '-urNp', '-x', '*.orig', diffdir_org, diffdir ]
87         logging.debug("running %s" % repr(cmd))
88         try:
89             subprocess.check_call(cmd, cwd=builddir, stdout=f, stderr=sys.stderr,
90                                   env={'LC_ALL': 'C.UTF-8'}, timeout=600)
91         except subprocess.CalledProcessError as err:
92             if err.returncode != 1:
93                 raise
94     logging.info("rediff generated as %s" % output)
95
96 def diffstat(patch):
97     cmd = [ 'diffstat', patch ]
98     logging.info("running diffstat for: %s" % patch)
99     try:
100         subprocess.check_call(cmd, stdout=sys.stdout, stderr=sys.stderr,
101                               env={'LC_ALL': 'C.UTF-8'}, timeout=60)
102     except subprocess.CalledProcessError as err:
103         logging.error("running diffstat failed: %s" % err)
104     except FileNotFoundError as err:
105         logging.error("running diffstat failed: %s, install diffstat package?" % err)
106
107 def main():
108     parser = parser = argparse.ArgumentParser(description='rediff patches to avoid fuzzy hunks')
109     parser.add_argument('spec', type=str, help='spec file name')
110     parser.add_argument('-p', '--patches', type=str, help='comma separated list of patch numbers to rediff')
111     parser.add_argument('-s', '--skip-patches', type=str, help='comma separated list of patch numbers to skip rediff')
112     parser.add_argument('-v', '--verbose', help='increase output verbosity', action='store_true')
113     args = parser.parse_args()
114
115     logging.basicConfig(level=logging.INFO)
116     rpm.setVerbosity(rpm.RPMLOG_ERR)
117
118     if args.verbose:
119             logging.basicConfig(level=logging.DEBUG)
120             rpm.setVerbosity(rpm.RPMLOG_DEBUG)
121
122     if args.patches:
123         args.patches = [int(x) for x in args.patches.split(',')]
124
125     if args.skip_patches:
126         args.skip_patches = [int(x) for x in args.skip_patches.split(',')]
127
128     specfile = args.spec
129     appsourcedir = os.path.dirname(os.path.abspath(specfile))
130
131     try:
132         tempdir = tempfile.TemporaryDirectory(dir="/dev/shm")
133     except FileNotFoundError as e:
134         tempdir = tempfile.TemporaryDirectory(dir="/tmp")
135     topdir = tempdir.name
136     builddir = os.path.join(topdir, 'BUILD')
137
138     rpm.addMacro("_builddir", builddir)
139
140     r = rpm.spec(specfile)
141
142     patches = {}
143
144     for (name, nr, flags) in r.sources:
145         if flags & RPMBUILD_ISPATCH:
146             patches[nr] = name
147
148     applied_patches = collections.OrderedDict()
149     re_patch = re.compile(r'^%patch(?P<patch_number>\d+)\w*(?P<patch_args>.*)')
150     for line in r.parsed.split('\n'):
151         m = re_patch.match(line)
152         if not m:
153             continue
154         patch_nr = int(m.group('patch_number'))
155         patch_args = m.group('patch_args')
156         applied_patches[patch_nr] = patch_args
157
158     appbuilddir = rpm.expandMacro("%{_builddir}/%{?buildsubdir}")
159
160     for patch_nr in applied_patches.keys():
161         if args.patches and patch_nr not in args.patches:
162             continue
163         if args.skip_patches and patch_nr in args.skip_patches:
164             continue
165         patch_name = patches[patch_nr]
166         logging.info("*** patch %d: %s" % (patch_nr, patch_name))
167
168         tempspec = prepare_spec(r, patch_nr, before=True)
169         unpack(tempspec.name, appsourcedir, builddir)
170         tempspec.close()
171         os.rename(appbuilddir, appbuilddir + ".org")
172
173         tempspec = prepare_spec(r, patch_nr, before=False)
174         unpack(tempspec.name, appsourcedir, builddir)
175         tempspec.close()
176
177         patch_comment = patch_comment_get(patch_name)
178         diff(appbuilddir + ".org",
179              appbuilddir,
180              builddir,
181              patch_comment,
182              os.path.join(topdir, os.path.join(appsourcedir, patch_name + ".rediff")))
183
184         diffstat(os.path.join(topdir, os.path.join(appsourcedir, patch_name)))
185         diffstat(os.path.join(topdir, os.path.join(appsourcedir, patch_name + ".rediff")))
186
187         shutil.rmtree(builddir)
188     tempdir.cleanup()
189
190 if __name__ == '__main__':
191     main()
This page took 0.069322 seconds and 3 git commands to generate.