OSDN Git Service

BuildScript: Build Innosetup installer.
[winmerge-jp/winmerge-jp.git] / Tools / Scripts / create_release.py
1 #
2 # The MIT License
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.
20
21 # $Id$
22
23 # This script prepares a WinMerge release
24 # Tasks it does:
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
36
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
42
43 # Tools needed:
44 # - Python 2.5 :)
45 # - Subversion command line binaries
46
47 # CONFIGURATION:
48 # Set these variables to match your environment and folders you want to use
49
50 # Subversion binary - set this to absolute path to svn.exe
51 svn_binary = 'C:\\Program Files\\Subversion\\bin\\svn.exe'
52 # Visual Studio path
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'
58
59 # END CONFIGURATION - you don't need to edit anything below...
60
61 from subprocess import *
62 import os
63 import os.path
64 import sys
65 import getopt
66 import shutil
67
68 def get_vs_ide_bin():
69     """Gets a full path to the Visual Studio IDE executable to run."""
70
71     # These are identical for VS2003.Net, VS2005 and VS2008
72     rel_path = 'Common7/IDE'
73     vs_bin = 'devenv.com'
74
75     vs_ide_path = os.path.join(vs_path, rel_path)
76     vs_cmd_path = os.path.join(vs_ide_path, vs_bin)
77     return vs_cmd_path
78
79 def cleanup_build():
80     """Deletes all build files around folder structure"""
81
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)
87     else:
88         print 'Skipping folder %s' % winmerge_temp
89     
90     try:
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')
98
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')
106
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')
114
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')
122
123         if os.path.exists('build/scew'):
124             shutil.rmtree('build/scew', True)
125
126         if os.path.exists('build/heksedit'):
127             shutil.rmtree('build/heksedit', True)
128
129         if os.path.exists('build/Manual'):
130             shutil.rmtree('build/Manual',True)
131
132     except EnvironmentError, einst:
133         print 'Error deleting files: '
134         print einst
135         return False;
136     except:
137         print 'Error deleting files: '
138         print sys.exc_info()[0]
139         return False
140     return True
141
142 def set_resource_version(version):
143     """Sets the version number to the resource."""
144
145     print 'Update version number to resource(s)...'
146     call(['cscript', 'Src/SetResourceVersions.wsf', version])
147
148 def setup_translations():
149     """Updates translation files by running scripts in Src/Languages."""
150
151     # Scripts must be run from the directory where they reside
152     curdir = os.getcwd()
153     os.chdir('Src/Languages')
154     call(['cscript', '/nologo', 'CreateMasterPotFile.vbs'])
155     call(['cscript', '/nologo', 'UpdatePoFilesFromPotFile.vbs'])
156     os.chdir(curdir)
157
158 def get_and_create_dist_folder(folder):
159     """Formats a folder name for version-specific distribution folder
160     and creates the folder."""
161
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!'
167         return ''
168     else:
169         print 'Create distribution folder: ' + dist_folder
170         os.mkdir(dist_folder)
171         return dist_folder
172
173 def get_src_dist_folder(dist_folder, folder):
174     """Format a source distribution folder path."""
175
176     dist_src = os.path.join(dist_folder, folder + '-src')
177     return dist_src
178
179 def svn_export(dist_src_folder):
180     """Exports sources to distribution folder."""
181
182     print 'Exporting sources to ' + dist_src_folder
183     call([svn_binary, 'export', '--non-interactive', '.', dist_src_folder])
184
185 def cleanup_dlls_from_plugins(dist_src_folder):
186     """Remove compiled plugin dll files from source distribution folders."""
187
188     dll_folder = os.path.join(dist_src_folder, 'Plugins/dlls')
189     files = os.listdir(dll_folder)
190
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'):
197                 os.remove(fullpath)
198
199 def build_libraries():
200     """Builds library targets: expat, scew and pcre."""
201
202     vs_cmd = get_vs_ide_bin()
203     cur_path = os.getcwd()
204
205     print 'Build expat library...'
206     solution_path = os.path.join(cur_path, 'Externals/expat/lib/expat.vcproj')
207     #print solution_path
208     call([vs_cmd, solution_path, '/rebuild', 'Release'], shell=True)
209
210     print 'Build scew library...'
211     solution_path = os.path.join(cur_path, 'Externals/scew/win32/scew.vcproj')
212     #print solution_path
213     call([vs_cmd, solution_path, '/rebuild', 'Release'], shell=True)
214
215     print 'Build pcre library...'
216     solution_path = os.path.join(cur_path, 'Externals/pcre/Win32/pcre.vcproj')
217     #print solution_path
218     call([vs_cmd, solution_path, '/rebuild', 'MinSizeRel'], shell=True)
219
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)
223
224 def build_targets():
225     """Builds all WinMerge targets."""
226
227     build_libraries()
228
229     vs_cmd = get_vs_ide_bin()
230
231     build_winmerge(vs_cmd)
232     build_shellext(vs_cmd)
233
234 def build_winmerge(vs_cmd):
235     """Builds WinMerge executable targets."""
236
237     cur_path = os.getcwd()
238     solution_path = os.path.join(cur_path, 'Src\\Merge.vcproj')
239     #print sol_path
240
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)
245
246 def build_shellext(vs_cmd):
247     """Builds 32-bit ShellExtension."""
248
249     cur_path = os.getcwd()
250     solution_path = os.path.join(cur_path, 'ShellExtension\\ShellExtension.vcproj')
251
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'])
256
257 def build_manual():
258     """Builds manual's HTML Help (CHM) version for user install and
259     HTML version for the Web. HTML version is created with ads."""
260
261     curdir = os.getcwd()
262     os.chdir('Docs/Users/Manual/build')
263     print 'Build HTML Help (CHM) manual...' 
264     call(['build_htmlhelp.bat'])
265     
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.'
270     os.chdir(curdir)
271
272 def build_innosetup_installer(target_folder):
273     """Builds the InnoSetup installer for the WinMerge."""
274
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 + '"'
279
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])
284
285 def get_and_create_bin_folder(dist_folder, folder):
286     """Formats and creates binary distribution folder."""
287
288     bin_folder = os.path.join(dist_folder, folder + '-exe')
289     print 'Create binary distribution folder: ' + bin_folder
290     os.mkdir(bin_folder)
291     return bin_folder
292
293 def create_bin_folders(bin_folder, dist_src_folder):
294     """Creates binary distribution folders."""
295
296     cur_path = os.getcwd()
297     os.chdir(bin_folder)
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')
302     os.mkdir(doc_folder)
303     filters_folder = os.path.join(bin_folder, 'Filters')
304     plugins_folder = os.path.join(bin_folder, 'MergePlugins')
305     os.chdir(cur_path)
306
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)
310
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)
317
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)
321
322     copy_po_files(lang_folder)
323     filter_orig = os.path.join(dist_src_folder, 'Filters')
324     shutil.copytree(filter_orig, filters_folder, False)
325
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)
331
332     shutil.copy('build/Manual/htmlhelp/WinMerge.chm', doc_folder)
333
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)
339
340 def copy_po_files(dest_folder):
341     """Copies all PO files to destination folder."""
342
343     lang_folder = 'Src/Languages'
344     files = os.listdir(lang_folder)
345
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)
353
354 def get_and_create_runtimes_folder(dist_folder, version):
355     """Formats and creates runtimes distribution folder."""
356
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
361
362 def create_runtime_folder(runtimes_folder):
363     """Copy runtime files to distribution folder."""
364
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)
369
370 def find_winmerge_root():
371     """Find WinMerge tree root folder from where to run rest of the script."""
372     
373     # If we find Src and Filters -subfolders we are in root 
374     if os.path.exists('Src') and os.path.exists('Filters'):
375         return True
376     
377     # Check if we are in /Tools/Scripts
378     if os.path.exists('../../Src') and os.path.exists('../../Filters'):
379         os.chdir('../../')
380         return True
381     
382     return False
383
384 def check_tools():
385     """Check that needed external tools can be found."""
386
387     if not os.path.exists(svn_binary):
388         print 'Subversion binary could not be found from:'
389         print svn_binary
390         print 'Please check script configuration and/or make sure Subversion is installed.'
391         return False
392
393     vs_cmd = get_vs_ide_bin()
394     if not os.path.exists(vs_cmd):
395         print 'Cannot find Visual Studio IDE binary from:'
396         print vs_cmd
397         print 'Please check script configuration.'
398         return False
399     return True
400
401 def check_x64shellext():
402     """Checks that 64-bit ShellExtension is compiled prior to running this
403     script.
404
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.
408     """
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!'
413         return False
414     else:
415         return True
416
417 def usage():
418     print 'WinMerge release script.'
419     print 'Usage: create_release [-h] [-v: n] [-c] [-l]'
420     print '  where:'
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'
426
427 def main(argv):
428     version = '0.0.0.0'
429     if len(argv) > 0:
430         opts, args = getopt.getopt(argv, "hclv:", [ "help", "cleanup", "libraries",
431                                                     "version="])
432         
433         for opt, arg in opts:
434             if opt in ("-h", "--help"):
435                 usage()
436                 sys.exit()
437             if opt in ("-v", "--version"):
438                 version = arg
439                 print "Start building WinMerge release version " + version
440             if opt in ("-c", "--cleanup"):
441                 if cleanup_build() == True:
442                     print 'Cleanup done.'
443                 sys.exit()
444             if opt in ("-l", "--libraries"):
445                 build_libraries()
446                 sys.exit()
447
448     # Check all required tools are found (script configuration)
449     if check_tools() == False:
450         sys.exit()
451
452     # Check 64-bit ShellExtension is compiled
453     if check_x64shellext() == False:
454         sys.exit()
455
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).'
462         sys.exit()
463
464     # Create the distribution folder if it doesn't exist
465     try:
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
470         print einst
471         sys.exit()
472
473     # Remove old build's files
474     if cleanup_build() == False:
475         sys.exit()
476
477     version_folder = 'WinMerge-' + version
478     dist_folder = get_and_create_dist_folder(version_folder)
479     if dist_folder == '':
480         sys.exit(1)
481     dist_src_folder = get_src_dist_folder(dist_folder, version_folder)
482     svn_export(dist_src_folder)
483
484     set_resource_version(version)
485     setup_translations()
486
487     build_targets()
488     build_manual()
489     build_innosetup_installer(dist_folder)
490
491     dist_bin_folder = get_and_create_bin_folder(dist_folder, version_folder)
492     create_bin_folders(dist_bin_folder, dist_src_folder)
493
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)
497
498     runtimes_folder = get_and_create_runtimes_folder(dist_folder, version)
499     create_runtime_folder(runtimes_folder)
500
501     print 'WinMerge release script ready!'
502
503
504 ### MAIN ###
505 if __name__ == "__main__":
506     main(sys.argv[1:])