3 # Copyright (c) 2007-2008 Kimmo Varis
4 # Permission is hereby granted, free of charge, to any person obtaining
5 # a copy of this software and associated documentation files
6 # (the "Software"), to deal in the Software without restriction, including
7 # without limitation the rights to use, copy, modify, merge, publish,
8 # distribute, sublicense, and/or sell copies of the Software, and to
9 # permit persons to whom the Software is furnished to do so, subject to
10 # the following conditions:
11 # The above copyright notice and this permission notice shall be included
12 # in all copies or substantial portions of the Software.
13 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
15 # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
17 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
18 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
19 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 # This script prepares a WinMerge release
25 # - cleans previous build files from folders
26 # - sets version number for resources
27 # - updates POT and PO files
28 # - builds libraries (expat, scew, pcre)
29 # - builds WinMerge.exe and WinMergeU.exe
30 # - builds 32-bit ShellExtension targets
31 # - builds user manual
32 # - builds the InnoSetup installer
33 # - creates per-version distribution folder
34 # - exports SVN sources to distribution folder
35 # - creates binary distribution folder
37 #Tasks not done (TODO?):
38 # - building 64-bit ShellExtension
39 # - creating packages from source and binary folders
40 # - running virus check
41 # - creating SHA-1 hashes for distributed files
45 # - Subversion command line binaries
48 # Set these variables to match your environment and folders you want to use
50 # Subversion binary - set this to absolute path to svn.exe
51 svn_binary = 'C:\\Program Files\\Subversion\\bin\\svn.exe'
53 vs_path = 'C:\\Program Files\\Microsoft Visual Studio .NET 2003'
54 # InnoSetup installation path
55 innosetup_path = 'C:\\Program Files\\Inno Setup 5'
56 # Relative path where to create a release folder
57 dist_root_folder = 'distrib'
59 # END CONFIGURATION - you don't need to edit anything below...
61 from subprocess import *
69 """Gets a full path to the Visual Studio IDE executable to run."""
71 # These are identical for VS2003.Net, VS2005 and VS2008
72 rel_path = 'Common7/IDE'
75 vs_ide_path = os.path.join(vs_path, rel_path)
76 vs_cmd_path = os.path.join(vs_ide_path, vs_bin)
80 """Deletes all build files around folder structure"""
82 print 'Delete old build files...'
83 winmerge_temp = 'BuildTmp'
84 if os.path.exists(winmerge_temp):
85 print 'Remove folder %s' % winmerge_temp
86 shutil.rmtree(winmerge_temp, True)
88 print 'Skipping folder %s' % winmerge_temp
91 print 'Remove ANSI files'
92 if os.path.exists('build/mergerelease/WinMerge.exe'):
93 os.remove('build/mergerelease/WinMerge.exe')
94 if os.path.exists('build/mergerelease/ShellExtension.dll'):
95 os.remove('build/mergerelease/ShellExtension.dll')
96 if os.path.exists('build/mergerelease/MergeLang.dll'):
97 os.remove('build/mergerelease/MergeLang.dll')
99 print 'Remove Unicode files'
100 if os.path.exists('build/mergeunicoderelease/WinMergeU.exe'):
101 os.remove('build/mergeunicoderelease/WinMergeU.exe')
102 if os.path.exists('build/mergeunicoderelease/ShellExtensionU.dll'):
103 os.remove('build/mergeunicoderelease/ShellExtensionU.dll')
104 if os.path.exists('build/mergeunicoderelease/MergeLang.dll'):
105 os.remove('build/mergeunicoderelease/MergeLang.dll')
107 print 'Remove expat files'
108 if os.path.exists('build/expat'):
109 shutil.rmtree('build/expat', True)
110 if os.path.exists('build/mergerelease/libexpat.dll'):
111 os.remove('build/mergerelease/libexpat.dll')
112 if os.path.exists('build/mergeunicoderelease/libexpat.dll'):
113 os.remove('build/mergeunicoderelease/libexpat.dll')
115 print 'Remove pcre files'
116 if os.path.exists('build/pcre'):
117 shutil.rmtree('build/pcre', True)
118 if os.path.exists('build/mergerelease/pcre.dll'):
119 os.remove('build/mergerelease/pcre.dll')
120 if os.path.exists('build/mergeunicoderelease/pcre.dll'):
121 os.remove('build/mergeunicoderelease/pcre.dll')
123 if os.path.exists('build/scew'):
124 shutil.rmtree('build/scew', True)
126 if os.path.exists('build/heksedit'):
127 shutil.rmtree('build/heksedit', True)
129 if os.path.exists('build/Manual'):
130 shutil.rmtree('build/Manual',True)
132 except EnvironmentError, einst:
133 print 'Error deleting files: '
137 print 'Error deleting files: '
138 print sys.exc_info()[0]
142 def set_resource_version(version):
143 """Sets the version number to the resource."""
145 print 'Update version number to resource(s)...'
146 call(['cscript', 'Src/SetResourceVersions.wsf', version])
148 def setup_translations():
149 """Updates translation files by running scripts in Src/Languages."""
151 # Scripts must be run from the directory where they reside
153 os.chdir('Src/Languages')
154 call(['cscript', '/nologo', 'CreateMasterPotFile.vbs'])
155 call(['cscript', '/nologo', 'UpdatePoFilesFromPotFile.vbs'])
158 def get_and_create_dist_folder(folder):
159 """Formats a folder name for version-specific distribution folder
160 and creates the folder."""
162 abs_folder = os.path.realpath(dist_root_folder)
163 dist_folder = os.path.join(abs_folder, folder)
164 if os.path.exists(dist_folder):
165 print 'Folder: ' + dist_folder + ' already exists!'
166 print 'If you want to re-create this version, remove folder first!'
169 print 'Create distribution folder: ' + dist_folder
170 os.mkdir(dist_folder)
173 def get_src_dist_folder(dist_folder, folder):
174 """Format a source distribution folder path."""
176 dist_src = os.path.join(dist_folder, folder + '-src')
179 def svn_export(dist_src_folder):
180 """Exports sources to distribution folder."""
182 print 'Exporting sources to ' + dist_src_folder
183 call([svn_binary, 'export', '--non-interactive', '.', dist_src_folder])
185 def cleanup_dlls_from_plugins(dist_src_folder):
186 """Remove compiled plugin dll files from source distribution folders."""
188 dll_folder = os.path.join(dist_src_folder, 'Plugins/dlls')
189 files = os.listdir(dll_folder)
191 print 'Removing dll files from plugin folder...'
192 for cur_file in files:
193 fullpath = os.path.join(dll_folder, cur_file)
194 if os.path.isfile(fullpath):
195 file_name, file_ext = os.path.splitext(cur_file)
196 if (file_ext == '.dll'):
199 def build_libraries():
200 """Builds library targets: expat, scew and pcre."""
202 vs_cmd = get_vs_ide_bin()
203 cur_path = os.getcwd()
205 print 'Build expat library...'
206 solution_path = os.path.join(cur_path, 'Externals/expat/lib/expat.vcproj')
208 call([vs_cmd, solution_path, '/rebuild', 'Release'], shell=True)
210 print 'Build scew library...'
211 solution_path = os.path.join(cur_path, 'Externals/scew/win32/scew.vcproj')
213 call([vs_cmd, solution_path, '/rebuild', 'Release'], shell=True)
215 print 'Build pcre library...'
216 solution_path = os.path.join(cur_path, 'Externals/pcre/Win32/pcre.vcproj')
218 call([vs_cmd, solution_path, '/rebuild', 'MinSizeRel'], shell=True)
220 print 'Build heksedit library...'
221 solution_path = os.path.join(cur_path, 'Externals/heksedit/heksedit.vcproj')
222 call([vs_cmd, solution_path, '/rebuild', 'Release'], shell=True)
225 """Builds all WinMerge targets."""
229 vs_cmd = get_vs_ide_bin()
231 build_winmerge(vs_cmd)
232 build_shellext(vs_cmd)
234 def build_winmerge(vs_cmd):
235 """Builds WinMerge executable targets."""
237 cur_path = os.getcwd()
238 solution_path = os.path.join(cur_path, 'Src\\Merge.vcproj')
241 # devenv Src\Merge.dsp /rebuild Release
242 print 'Build WinMerge executables...'
243 call([vs_cmd, solution_path, '/rebuild', 'Release'], shell=True)
244 call([vs_cmd, solution_path, '/rebuild', 'UnicodeRelease'], shell=True)
246 def build_shellext(vs_cmd):
247 """Builds 32-bit ShellExtension."""
249 cur_path = os.getcwd()
250 solution_path = os.path.join(cur_path, 'ShellExtension\\ShellExtension.vcproj')
252 # devenv Src\Merge.dsp /rebuild Release
253 print 'Build ShellExtension dlls...'
254 call([vs_cmd, solution_path, '/rebuild', 'Release MinDependency'])
255 call([vs_cmd, solution_path, '/rebuild', 'Unicode Release MinDependency'])
258 """Builds manual's HTML Help (CHM) version for user install and
259 HTML version for the Web. HTML version is created with ads."""
262 os.chdir('Docs/Users/Manual/build')
263 print 'Build HTML Help (CHM) manual...'
264 call(['build_htmlhelp.bat'])
266 # HTML manual not build in trunk.
267 #print 'Build HTML manual for Web with ads...'
268 #call(['build_html.bat', 'withads'])
269 print 'Manual build finished.'
272 def build_innosetup_installer(target_folder):
273 """Builds the InnoSetup installer for the WinMerge."""
275 innosetup_exe = os.path.join(innosetup_path, 'iscc.exe')
276 cur_path = os.getcwd()
277 winmerge_iss = os.path.join(cur_path, 'Installer\\InnoSetup\\WinMerge.iss')
278 #output_switch = '/O"' + target_folder + '"'
280 print 'Build Innosetup installer...'
281 # Should be able to give folder for created file and Q switch to make build quiet
282 #call([innosetup_exe, '/Q', output_switch, winmerge_iss])
283 call([innosetup_exe, winmerge_iss])
285 def get_and_create_bin_folder(dist_folder, folder):
286 """Formats and creates binary distribution folder."""
288 bin_folder = os.path.join(dist_folder, folder + '-exe')
289 print 'Create binary distribution folder: ' + bin_folder
293 def create_bin_folders(bin_folder, dist_src_folder):
294 """Creates binary distribution folders."""
296 cur_path = os.getcwd()
298 print 'Create binary distribution folder structure...'
299 lang_folder = os.path.join(bin_folder, 'Languages')
300 os.mkdir(lang_folder)
301 doc_folder = os.path.join(bin_folder, 'Docs')
303 filters_folder = os.path.join(bin_folder, 'Filters')
304 plugins_folder = os.path.join(bin_folder, 'MergePlugins')
307 print 'Copying files to binary distribution folder...'
308 shutil.copy('build/mergerelease/WinMerge.exe', bin_folder)
309 shutil.copy('build/mergeunicoderelease/WinMergeU.exe', bin_folder)
311 shutil.copy('build/mergerelease/ShellExtension.dll', bin_folder)
312 shutil.copy('build/mergeunicoderelease/ShellExtensionU.dll', bin_folder)
313 shutil.copy('build/mergeunicoderelease/MergeLang.dll', bin_folder)
314 shutil.copy('build/shellextensionx64/ShellExtensionX64.dll', bin_folder)
315 shutil.copy('ShellExtension/Register.bat', bin_folder)
316 shutil.copy('ShellExtension/UnRegister.bat', bin_folder)
318 shutil.copy('build/pcre/pcre.dll', bin_folder)
319 shutil.copy('build/expat/libexpat.dll', bin_folder)
320 shutil.copy('build/heksedit/heksedit.dll', bin_folder)
322 copy_po_files(lang_folder)
323 filter_orig = os.path.join(dist_src_folder, 'Filters')
324 shutil.copytree(filter_orig, filters_folder, False)
326 # Copy compiled plugins dir and rename it
327 plugin_dir = os.path.join(bin_folder, 'dlls')
328 plugin_orig = os.path.join(dist_src_folder, 'Plugins/dlls')
329 shutil.copytree(plugin_orig, plugin_dir, False)
330 os.rename(plugin_dir, plugins_folder)
332 shutil.copy('build/Manual/htmlhelp/WinMerge.chm', doc_folder)
334 shutil.copy('Docs/Users/ReleaseNotes.html', doc_folder)
335 shutil.copy('Docs/Users/ReadMe.txt', bin_folder)
336 shutil.copy('Docs/Users/ChangeLog.txt', doc_folder)
337 shutil.copy('Docs/Users/Contributors.txt', bin_folder)
338 shutil.copy('Docs/Users/Files.txt', bin_folder)
340 def copy_po_files(dest_folder):
341 """Copies all PO files to destination folder."""
343 lang_folder = 'Src/Languages'
344 files = os.listdir(lang_folder)
346 print 'Copying PO files to binary folder...'
347 for cur_file in files:
348 fullpath = os.path.join(lang_folder, cur_file)
349 if os.path.isfile(fullpath):
350 file_name, file_ext = os.path.splitext(cur_file)
351 if (file_ext == '.po'):
352 shutil.copy(fullpath, dest_folder)
354 def get_and_create_runtimes_folder(dist_folder, version):
355 """Formats and creates runtimes distribution folder."""
357 runtimes_folder = os.path.join(dist_folder, 'Runtimes-' + version)
358 print 'Create runtimes distribution folder: ' + runtimes_folder
359 os.mkdir(runtimes_folder)
360 return runtimes_folder
362 def create_runtime_folder(runtimes_folder):
363 """Copy runtime files to distribution folder."""
365 shutil.copy('Installer/Runtimes/mfc71.dll', runtimes_folder)
366 shutil.copy('Installer/Runtimes/mfc71u.dll', runtimes_folder)
367 shutil.copy('Installer/Runtimes/msvcp71.dll', runtimes_folder)
368 shutil.copy('Installer/Runtimes/msvcr71.dll', runtimes_folder)
370 def find_winmerge_root():
371 """Find WinMerge tree root folder from where to run rest of the script."""
373 # If we find Src and Filters -subfolders we are in root
374 if os.path.exists('Src') and os.path.exists('Filters'):
377 # Check if we are in /Tools/Scripts
378 if os.path.exists('../../Src') and os.path.exists('../../Filters'):
385 """Check that needed external tools can be found."""
387 if not os.path.exists(svn_binary):
388 print 'Subversion binary could not be found from:'
390 print 'Please check script configuration and/or make sure Subversion is installed.'
393 vs_cmd = get_vs_ide_bin()
394 if not os.path.exists(vs_cmd):
395 print 'Cannot find Visual Studio IDE binary from:'
397 print 'Please check script configuration.'
401 def check_x64shellext():
402 """Checks that 64-bit ShellExtension is compiled prior to running this
405 This is due to the fact we can't compile 64-bit ShellExtension without some
406 environment tweaks, so it won't work (currently) from this script. And the
407 ShellExtension must be compiled separately.
409 if not os.path.exists('build/shellextensionx64/ShellExtensionX64.dll'):
410 print 'ERROR: cannot create a release:'
411 print 'You must compile 64-bit ShellExtension (ShellExtensionX64.dll)'
412 print 'before running this script!'
418 print 'WinMerge release script.'
419 print 'Usage: create_release [-h] [-v: n] [-c] [-l]'
421 print ' -h, --help print this help'
422 print ' -v: n, --version= n set release version'
423 print ' -c, --cleanup clean up build files (temp files, libraries, executables)'
424 print ' -l, --libraries build libraries (expat, scew, pcre) only'
425 print ' For example: create_release -v: 2.7.7.1'
430 opts, args = getopt.getopt(argv, "hclv:", [ "help", "cleanup", "libraries",
433 for opt, arg in opts:
434 if opt in ("-h", "--help"):
437 if opt in ("-v", "--version"):
439 print "Start building WinMerge release version " + version
440 if opt in ("-c", "--cleanup"):
441 if cleanup_build() == True:
442 print 'Cleanup done.'
444 if opt in ("-l", "--libraries"):
448 # Check all required tools are found (script configuration)
449 if check_tools() == False:
452 # Check 64-bit ShellExtension is compiled
453 if check_x64shellext() == False:
456 # Check we are running from correct folder (and go to root if found)
457 if find_winmerge_root() == False:
458 print 'ERROR: Cannot find WinMerge root folder!'
459 print 'The script must be run from WinMerge tree\'s root folder'
460 print '(which has Src- and Filter -folders as subfolders) or from'
461 print 'Tools/Scripts -folder (where this script is located).'
464 # Create the distribution folder if it doesn't exist
466 if not os.path.exists(dist_root_folder):
467 os.mkdir(dist_root_folder)
468 except EnvironmentError, einst:
469 print 'Error creating distribution folder: ' + dist_root_folder
473 # Remove old build's files
474 if cleanup_build() == False:
477 version_folder = 'WinMerge-' + version
478 dist_folder = get_and_create_dist_folder(version_folder)
479 if dist_folder == '':
481 dist_src_folder = get_src_dist_folder(dist_folder, version_folder)
482 svn_export(dist_src_folder)
484 set_resource_version(version)
489 build_innosetup_installer(dist_folder)
491 dist_bin_folder = get_and_create_bin_folder(dist_folder, version_folder)
492 create_bin_folders(dist_bin_folder, dist_src_folder)
494 # Do the cleanup after creating binary distrib folders, as some files
495 # and folders are copied from source folders to binary folders.
496 cleanup_dlls_from_plugins(dist_src_folder)
498 runtimes_folder = get_and_create_runtimes_folder(dist_folder, version)
499 create_runtime_folder(runtimes_folder)
501 print 'WinMerge release script ready!'
505 if __name__ == "__main__":