meson: Generate ChangeLog files for release tarballs on dist
[platform/upstream/gstreamer.git] / subprojects / gstreamer-vaapi / scripts / gen-changelog.py
1 #!/usr/bin/env python3
2 #
3 # Makes a GNU-Style ChangeLog from a git repository
4 import os
5 import sys
6 import subprocess
7 import re
8
9 meson_source_root = os.environ.get('MESON_SOURCE_ROOT')
10
11 meson_dist_root = os.environ.get('MESON_DIST_ROOT')
12 if meson_dist_root:
13     output_fn = os.path.join(meson_dist_root, 'ChangeLog')
14 else:
15     output_fn = sys.stdout.fileno()
16
17 # commit hash => release version tag string
18 release_refs = {}
19
20 # These are the pre-monorepo module beginnings
21 changelog_starts = {
22     'gstreamer': '70521179a75db0c7230cc47c6d7f9d63cf73d351',
23     'gst-plugins-base': '68746a38d5e48e6f7c220663dcc2f175ff55cb3c',
24     'gst-plugins-good': '81f63142d65b62b0971c19ceb79956c49ffc2f06',
25     'gst-plugins-ugly': '7d7c3e478e32b7b66c44cc4442d571fbab534740',
26     'gst-plugins-bad': 'ea6821e2934fe8d356ea89d5610f0630b3446877',
27     'gst-libav': '3c440154c60d1ec0a54186f0fad4aebfd2ecc3ea',
28     'gst-rtsp-server': '5029c85a46a8c366c4bf272d503e22bbcd624ece',
29     'gst-editing-services': 'ee8bf88ebf131cf7c7161356540efc20bf411e14',
30     'gst-python': 'b3e564eff577e2f577d795051bbcca85d47c89dc',
31     'gstreamer-vaapi': 'c89e9afc5d43837c498a55f8f13ddf235442b83b',
32     'gst-omx': 'd2463b017f222e678978582544a9c9a80edfd330',
33     'gst-devtools': 'da962d096af9460502843e41b7d25fdece7ff1c2',
34     'gstreamer-sharp': 'b94528f8e7979df49fedf137dfa228d8fe475e1b',
35 }
36
37
38 def print_help():
39     print('', file=sys.stderr)
40     print('gen-changelog: generate GNU-style changelog from git history',
41           file=sys.stderr)
42     print('', file=sys.stderr)
43     print('Usage: {} [OPTIONS] GSTREAMER-MODULE [START-TAG] [HEAD-TAG]'.format(
44         sys.argv[0]), file=sys.stderr)
45     print('', file=sys.stderr)
46     sys.exit(1)
47
48
49 if len(sys.argv) < 2 or len(sys.argv) > 4 or '--help' in sys.argv:
50     print_help()
51
52 module = sys.argv[1]
53
54 if len(sys.argv) > 2:
55     start_tag = sys.argv[2]
56 else:
57     start_tag = None
58
59 if len(sys.argv) > 3:
60     head_tag = sys.argv[3]
61 else:
62     head_tag = None
63
64 if module not in changelog_starts:
65     print(f'Unknown module {module}', file=sys.stderr)
66     print_help()
67
68
69 def process_commit(lines, files, subtree_path=None):
70     # DATE NAME
71     # BLANK LINE
72     # Subject
73     # BLANK LINE
74     # ...
75     # FILES
76     fileincommit = False
77     lines = [x.strip() for x in lines if x.strip()
78              and not x.startswith('git-svn-id')]
79     files = [x.strip() for x in files if x.strip()]
80     for line in lines:
81         if line.startswith('* ') and ':' in line:
82             fileincommit = True
83             break
84
85     top_line = lines[0]
86     print(top_line.strip())
87     print()
88     if not fileincommit:
89         for f in files:
90             if subtree_path and f.startswith(subtree_path):
91                 # requires Python 3.9
92                 print('\t* %s:' % f.removeprefix(subtree_path))
93             else:
94                 print('\t* %s:' % f)
95     for line in lines[1:]:
96         print('\t ', line)
97     print()
98
99
100 def output_commits(module, start_tag, end_tag, subtree_path=None):
101     # retrieve commit date for start tag so we can filter the log for commits
102     # after that date. That way we don't include commits from merged-in
103     # plugin-move branches that go back to the beginning of time.
104     start_date = get_commit_date_for_ref(start_tag)
105
106     cmd = ['git', 'log',
107            '--pretty=format:--START-COMMIT--%H%n%ai  %an <%ae>%n%n%s%n%b%n--END-COMMIT--',
108            '--date=short',
109            '--name-only',
110            f'--since={start_date}',
111            f'{start_tag}..{end_tag}',
112            ]
113
114     if subtree_path:
115         cmd += ['--', '.']
116
117     p = subprocess.Popen(args=cmd, shell=False,
118                          stdout=subprocess.PIPE, cwd=meson_source_root)
119     buf = []
120     files = []
121     filemode = False
122     for lin in [x.decode('utf8', errors='replace') for x in p.stdout.readlines()]:
123         if lin.startswith("--START-COMMIT--"):
124             commit_hash = lin[16:].strip()
125             if buf != []:
126                 process_commit(buf, files, subtree_path)
127
128             if commit_hash in release_refs:
129                 version_str = release_refs[commit_hash]
130                 print(f'=== release {version_str} ===\n')
131
132             buf = []
133             files = []
134             filemode = False
135         elif lin.startswith("--END-COMMIT--"):
136             filemode = True
137         elif filemode is True:
138             files.append(lin)
139         else:
140             buf.append(lin)
141     if buf != []:
142         process_commit(buf, files, subtree_path)
143
144
145 def get_commit_date_for_ref(ref):
146     cmd = ['git', 'log', '--pretty=format:%cI', '-1', ref]
147     r = subprocess.run(cmd, capture_output=True, text=True,
148                        check=True, cwd=meson_source_root)
149     commit_date = r.stdout.strip()
150     return commit_date
151
152
153 def populate_release_tags_for_premonorepo_module(module_tag_prefix):
154     if module_tag_prefix != '':
155         cmd = ['git', 'tag', '--list', f'{module_tag_prefix}*']
156     else:
157         cmd = ['git', 'tag', '--list', '1.*', 'RELEASE-*']
158
159     p = subprocess.Popen(args=cmd, shell=False,
160                          stdout=subprocess.PIPE, cwd=meson_source_root)
161     for line in [x.decode('utf8') for x in p.stdout.readlines()]:
162         git_tag = line.strip()
163         version_str = git_tag.removeprefix(module_tag_prefix).removeprefix('RELEASE-').split('-')[0].replace('_', '.')
164         # might have been populated with post-monorepo tags already for gstreamer core
165         if version_str not in release_refs:
166             # find last commit before tag in module subdirectory
167             cmd = ['git', 'log', '--pretty=format:%H', '-1', git_tag]
168             r = subprocess.run(cmd, capture_output=True,
169                                text=True, check=True, cwd=meson_source_root)
170             commit_hash = r.stdout.strip()
171             release_refs[commit_hash] = version_str
172
173             # print(f'{git_tag} => {version_str} => {commit_hash}')
174
175
176 def populate_release_tags_for_monorepo_subproject():
177     cmd = ['git', 'tag', '--list', '1.*']
178     p = subprocess.Popen(args=cmd, shell=False,
179                          stdout=subprocess.PIPE, cwd=meson_source_root)
180     for line in [x.decode('utf8') for x in p.stdout.readlines()]:
181         version_str = line.strip()
182         version_arr = version_str.split('.')
183         major = int(version_arr[0])
184         minor = int(version_arr[1])
185         micro = int(version_arr[2])
186         # ignore pre-monorepo versions
187         if major < 1:
188             continue
189         if major == 1 and minor < 19:
190             continue
191         if major == 1 and minor == 19 and micro < 2:
192             continue
193         # find last commit before tag in module subdirectory
194         cmd = ['git', 'log', '--pretty=format:%H',
195                '-1', version_str, '--', '.']
196         r = subprocess.run(cmd, capture_output=True, text=True,
197                            check=True, cwd=meson_source_root)
198         commit_hash = r.stdout.strip()
199         release_refs[commit_hash] = version_str
200
201
202 if __name__ == '__main__':
203     module_tag_prefix = '' if module == 'gstreamer' else f'{module}-'
204
205     populate_release_tags_for_monorepo_subproject()
206
207     with open(output_fn, 'w') as f:
208         sys.stdout = f
209
210         # Force writing of head tag
211         if head_tag and head_tag not in release_refs.values():
212             print(f'=== release {head_tag} ===\n')
213
214         # Output all commits from start_tag onwards, otherwise output full history.
215         # (We assume the start_tag is after the monorepo merge if it's specified.)
216         if start_tag and start_tag != 'start':
217             output_commits(module, start_tag, 'HEAD', f'subprojects/{module}/')
218         else:
219             # First output all post-monorepo commits or commits from start_tag if specified
220             output_commits(module, 'monorepo-start',
221                            'HEAD', f'subprojects/{module}/')
222
223             populate_release_tags_for_premonorepo_module(module_tag_prefix)
224
225             # Next output all pre-monorepo commits (modules have their own root)
226             if not start_tag:
227                 module_start = f'{module_tag_prefix}1.0.0'
228             elif start_tag == 'start':
229                 module_start = changelog_starts[module]
230             else:
231                 module_start = f'{module_tag_prefix}{start_tag}'
232
233             output_commits(module, module_start,
234                            f'{module_tag_prefix}1.19.2', None)
235
236         # Write start tag at end for clarity
237         if not start_tag:
238             print(f'=== release 1.0.0 ===\n')
239         elif start_tag != 'start':
240             print(f'=== release {start_tag} ===\n')