1 import os
, sys
, re
, subprocess
3 # This python3 library provides a few helpful routines that are
4 # used by the latest packaging scripts.
6 default_encoding
= 'utf-8'
8 # Output the msg args to stderr. Accepts all the args that print() accepts.
10 print(*msg
, file=sys
.stderr
)
13 # Output the msg args to stderr and die with a non-zero return-code.
14 # Accepts all the args that print() accepts.
20 # Set this to an encoding name or set it to None to avoid the default encoding idiom.
21 def set_default_encoding(enc
):
22 default_encoding
= enc
25 # Set shell=True if the cmd is a string; sets a default encoding unless raw=True was specified.
26 def _tweak_opts(cmd
, opts
, **maybe_set
):
27 # This sets any maybe_set value that isn't already set AND creates a copy of opts for us.
28 opts
= {**maybe_set
, **opts
}
31 opts
= {'shell': True, **opts
}
33 want_raw
= opts
.pop('raw', False)
34 if default_encoding
and not want_raw
:
35 opts
= {'encoding': default_encoding
, **opts
}
37 capture
= opts
.pop('capture', None)
39 if capture
== 'stdout':
40 opts
= {'stdout': subprocess
.PIPE
, **opts
}
41 elif capture
== 'stderr':
42 opts
= {'stderr': subprocess
.PIPE
, **opts
}
43 elif capture
== 'output':
44 opts
= {'stdout': subprocess
.PIPE
, 'stderr': subprocess
.PIPE
, **opts
}
45 elif capture
== 'combined':
46 opts
= {'stdout': subprocess
.PIPE
, 'stderr': subprocess
.STDOUT
, **opts
}
48 discard
= opts
.pop('discard', None)
50 # We DO want to override any already set stdout|stderr values (unlike above).
51 if discard
== 'stdout' or discard
== 'output':
52 opts
['stdout'] = subprocess
.DEVNULL
53 if discard
== 'stderr' or discard
== 'output':
54 opts
['stderr'] = subprocess
.DEVNULL
59 # This does a normal subprocess.run() with some auto-args added to make life easier.
60 def cmd_run(cmd
, **opts
):
61 return subprocess
.run(cmd
, **_tweak_opts(cmd
, opts
))
64 # Like cmd_run() with a default check=True specified.
65 def cmd_chk(cmd
, **opts
):
66 return subprocess
.run(cmd
, **_tweak_opts(cmd
, opts
, check
=True))
69 # Capture stdout in a string and return the (output, return_code) tuple.
70 # Use capture='combined' opt to get both stdout and stderr together.
71 def cmd_txt_status(cmd
, **opts
):
72 input = opts
.pop('input', None)
74 opts
['stdin'] = subprocess
.PIPE
75 proc
= subprocess
.Popen(cmd
, **_tweak_opts(cmd
, opts
, capture
='stdout'))
76 out
= proc
.communicate(input=input)[0]
77 return (out
, proc
.returncode
)
80 # Like cmd_txt_status() but just return the output.
81 def cmd_txt(cmd
, **opts
):
82 return cmd_txt_status(cmd
, **opts
)[0]
85 # Capture stdout in a string and return the output if the command has a 0 return code.
86 # Otherwise it throws an exception that indicates the return code and the output.
87 def cmd_txt_chk(cmd
, **opts
):
88 out
, rc
= cmd_txt_status(cmd
, **opts
)
90 cmd_err
= f
'Command "{cmd}" returned non-zero exit status "{rc}" and output:\n{out}'
91 raise Exception(cmd_err
)
95 # Starts a piped-output command of stdout (by default) and leaves it up to you to read
96 # the output and call communicate() on the returned object.
97 def cmd_pipe(cmd
, **opts
):
98 return subprocess
.Popen(cmd
, **_tweak_opts(cmd
, opts
, capture
='stdout'))
101 # Runs a "git status" command and dies if the checkout is not clean (the
102 # arg fatal_unless_clean can be used to make that non-fatal. Returns a
103 # tuple of the current branch, the is_clean flag, and the status text.
104 def check_git_status(fatal_unless_clean
=True, subdir
='.'):
105 status_txt
= cmd_txt_chk(f
"cd '{subdir}' && git status")
106 is_clean
= re
.search(r
'\nnothing to commit.+working (directory|tree) clean', status_txt
) != None
108 if not is_clean
and fatal_unless_clean
:
112 subdir
= f
" *{subdir}*"
113 die(f
"The{subdir} checkout is not clean:\n" + status_txt
)
115 m
= re
.match(r
'^(?:# )?On branch (.+)\n', status_txt
)
116 cur_branch
= m
[1] if m
else None
118 return (cur_branch
, is_clean
, status_txt
)
121 # Calls check_git_status() on the current git checkout and (optionally) a subdir path's
122 # checkout. Use fatal_unless_clean to indicate if an unclean checkout is fatal or not.
123 # The master_branch arg indicates what branch we want both checkouts to be using, and
124 # if the branch is wrong the user is given the option of either switching to the right
125 # branch or aborting.
126 def check_git_state(master_branch
, fatal_unless_clean
=True, check_extra_dir
=None):
127 cur_branch
= check_git_status(fatal_unless_clean
)[0]
128 branch
= re
.sub(r
'^patch/([^/]+)/[^/]+$', r
'\1', cur_branch
) # change patch/BRANCH/PATCH_NAME into BRANCH
129 if branch
!= master_branch
:
130 print(f
"The checkout is not on the {master_branch} branch.")
131 if master_branch
!= 'master':
133 ans
= input(f
"Do you want me to continue with --branch={branch}? [n] ")
134 if not ans
or not re
.match(r
'^y', ans
, flags
=re
.I
):
136 master_branch
= branch
138 if check_extra_dir
and os
.path
.isdir(os
.path
.join(check_extra_dir
, '.git')):
139 branch
= check_git_status(fatal_unless_clean
, check_extra_dir
)[0]
140 if branch
!= master_branch
:
141 print(f
"The *{check_extra_dir}* checkout is on branch {branch}, not branch {master_branch}.")
142 ans
= input(f
"Do you want to change it to branch {master_branch}? [n] ")
143 if not ans
or not re
.match(r
'^y', ans
, flags
=re
.I
):
145 subdir
.check_call(f
"cd {check_extra_dir} && git checkout '{master_branch}'", shell
=True)
147 return (cur_branch
, master_branch
)
150 # Return the git hash of the most recent commit.
151 def latest_git_hash(branch
):
152 out
= cmd_txt_chk(['git', 'log', '-1', '--no-color', branch
])
153 m
= re
.search(r
'^commit (\S+)', out
, flags
=re
.M
)
155 die(f
"Unable to determine commit hash for master branch: {branch}")
159 # Return a set of all branch names that have the format "patch/BASE_BRANCH/NAME"
160 # for the given base_branch string. Just the NAME portion is put into the set.
161 def get_patch_branches(base_branch
):
163 proc
= cmd_pipe('git branch -l'.split())
164 for line
in proc
.stdout
:
165 m
= re
.search(r
' patch/([^/]+)/(.+)', line
)
166 if m
and m
[1] == base_branch
:
172 def mandate_gensend_hook():
173 hook
= '.git/hooks/pre-push'
174 if not os
.path
.exists(hook
):
175 print('Creating hook file:', hook
)
176 cmd_chk(['./rsync', '-a', 'packaging/pre-push', hook
])
178 out
, rc
= cmd_txt_status(['fgrep', 'make gensend', hook
], discard
='output')
180 die('Please add a "make gensend" into your', hook
, 'script.')
183 # Snag the GENFILES values out of the Makefile.in file and return them as a list.
185 cont_re
= re
.compile(r
'\\\n')
189 with
open('Makefile.in', 'r', encoding
='utf-8') as fh
:
192 chk
= re
.sub(r
'^GENFILES=', '', line
)
196 m
= re
.search(r
'\\$', line
)
197 line
= re
.sub(r
'^\s+|\s*\\\n?$|\s+$', '', line
)
198 extras
+= line
.split()
205 def get_configure_version():
206 with
open('configure.ac', 'r', encoding
='utf-8') as fh
:
208 m
= re
.match(r
'^AC_INIT\(\[rsync\],\s*\[(\d.+?)\]', line
)
211 die("Unable to find AC_INIT with version in configure.ac")
214 def get_OLDNEWS_version_info():
215 rel_re
= re
.compile(r
'^\| \d{2} \w{3} \d{4}\s+\|\s+(?P<ver>\d+\.\d+\.\d+)\s+\|\s+(?P<pdate>\d{2} \w{3} \d{4}\s+)?\|\s+(?P<pver>\d+)\s+\|')
216 last_version
= last_protocol_version
= None
219 with
open('OLDNEWS.md', 'r', encoding
='utf-8') as fh
:
222 m
= re
.search(r
'(\d+\.\d+\.\d+)', line
)
225 m
= rel_re
.match(line
)
228 pdate
[m
['ver']] = m
['pdate']
229 if m
['ver'] == last_version
:
230 last_protocol_version
= m
['pver']
233 if not last_protocol_version
:
234 die(f
"Unable to determine protocol_version for {last_version}.")
236 return last_version
, last_protocol_version
239 def get_protocol_versions():
240 protocol_version
= subprotocol_version
= None
242 with
open('rsync.h', 'r', encoding
='utf-8') as fh
:
244 m
= re
.match(r
'^#define\s+PROTOCOL_VERSION\s+(\d+)', line
)
246 protocol_version
= m
[1]
248 m
= re
.match(r
'^#define\s+SUBPROTOCOL_VERSION\s+(\d+)', line
)
250 subprotocol_version
= m
[1]
253 if not protocol_version
:
254 die("Unable to determine the current PROTOCOL_VERSION.")
256 if not subprotocol_version
:
257 die("Unable to determine the current SUBPROTOCOL_VERSION.")
259 return protocol_version
, subprotocol_version