win32verstamp.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. """ Stamp a Win32 binary with version information.
  2. """
  3. from win32api import BeginUpdateResource, UpdateResource, EndUpdateResource
  4. import os
  5. import struct
  6. import glob
  7. import sys
  8. import optparse
  9. VS_FFI_SIGNATURE = -17890115 # 0xFEEF04BD
  10. VS_FFI_STRUCVERSION = 0x00010000
  11. VS_FFI_FILEFLAGSMASK = 0x0000003f
  12. VOS_NT_WINDOWS32 = 0x00040004
  13. null_byte = "\0".encode("ascii") # str in py2k, bytes in py3k
  14. #
  15. # Set VS_FF_PRERELEASE and DEBUG if Debug
  16. #
  17. def file_flags(debug):
  18. if debug:
  19. return 3 # VS_FF_DEBUG | VS_FF_PRERELEASE
  20. return 0
  21. def file_type(is_dll):
  22. if is_dll:
  23. return 2 # VFT_DLL
  24. return 1 # VFT_APP
  25. def VS_FIXEDFILEINFO(maj, min, sub, build, debug=0, is_dll=1):
  26. return struct.pack('lllllllllllll',
  27. VS_FFI_SIGNATURE, # dwSignature
  28. VS_FFI_STRUCVERSION, # dwStrucVersion
  29. (maj << 16) | min, # dwFileVersionMS
  30. (sub << 16) | build,# dwFileVersionLS
  31. (maj << 16) | min, # dwProductVersionMS
  32. (sub << 16) | build, # dwProductVersionLS
  33. VS_FFI_FILEFLAGSMASK, # dwFileFlagsMask
  34. file_flags(debug), # dwFileFlags
  35. VOS_NT_WINDOWS32, # dwFileOS
  36. file_type(is_dll), # dwFileType
  37. 0x00000000, # dwFileSubtype
  38. 0x00000000, # dwFileDateMS
  39. 0x00000000, # dwFileDateLS
  40. )
  41. def nullterm(s):
  42. # get raw bytes for a NULL terminated unicode string.
  43. if sys.version_info[:2] < (3, 7):
  44. return (str(s) + '\0').encode('unicode-internal')
  45. else:
  46. return (str(s) + '\0').encode('utf-16le')
  47. def pad32(s, extra=2):
  48. # extra is normally 2 to deal with wLength
  49. l = 4 - ((len(s) + extra) & 3)
  50. if l < 4:
  51. return s + (null_byte * l)
  52. return s
  53. def addlen(s):
  54. return struct.pack('h', len(s) + 2) + s
  55. def String(key, value):
  56. key = nullterm(key)
  57. value = nullterm(value)
  58. result = struct.pack('hh', len(value)//2, 1) # wValueLength, wType
  59. result = result + key
  60. result = pad32(result) + value
  61. return addlen(result)
  62. def StringTable(key, data):
  63. key = nullterm(key)
  64. result = struct.pack('hh', 0, 1) # wValueLength, wType
  65. result = result + key
  66. for k, v in data.items():
  67. result = result + String(k, v)
  68. result = pad32(result)
  69. return addlen(result)
  70. def StringFileInfo(data):
  71. result = struct.pack('hh', 0, 1) # wValueLength, wType
  72. result = result + nullterm('StringFileInfo')
  73. # result = pad32(result) + StringTable('040904b0', data)
  74. result = pad32(result) + StringTable('040904E4', data)
  75. return addlen(result)
  76. def Var(key, value):
  77. result = struct.pack('hh', len(value), 0) # wValueLength, wType
  78. result = result + nullterm(key)
  79. result = pad32(result) + value
  80. return addlen(result)
  81. def VarFileInfo(data):
  82. result = struct.pack('hh', 0, 1) # wValueLength, wType
  83. result = result + nullterm('VarFileInfo')
  84. result = pad32(result)
  85. for k, v in data.items():
  86. result = result + Var(k, v)
  87. return addlen(result)
  88. def VS_VERSION_INFO(maj, min, sub, build, sdata, vdata, debug=0, is_dll=1):
  89. ffi = VS_FIXEDFILEINFO(maj, min, sub, build, debug, is_dll)
  90. result = struct.pack('hh', len(ffi), 0) # wValueLength, wType
  91. result = result + nullterm('VS_VERSION_INFO')
  92. result = pad32(result) + ffi
  93. result = pad32(result) + StringFileInfo(sdata) + VarFileInfo(vdata)
  94. return addlen(result)
  95. def stamp(pathname, options):
  96. # For some reason, the API functions report success if the file is open
  97. # but doesnt work! Try and open the file for writing, just to see if it is
  98. # likely the stamp will work!
  99. try:
  100. f = open(pathname, "a+b")
  101. f.close()
  102. except IOError as why:
  103. print("WARNING: File %s could not be opened - %s" % (pathname, why))
  104. ver = options.version
  105. try:
  106. bits = [int(i) for i in ver.split(".")]
  107. vmaj, vmin, vsub, vbuild = bits
  108. except (IndexError, TypeError, ValueError):
  109. raise ValueError("--version must be a.b.c.d (all integers) - got %r" % ver)
  110. ifn = options.internal_name
  111. if not ifn:
  112. ifn = os.path.basename(pathname)
  113. ofn = options.original_filename
  114. if ofn is None:
  115. ofn = os.path.basename(pathname)
  116. sdata = {
  117. 'Comments' : options.comments,
  118. 'CompanyName' : options.company,
  119. 'FileDescription' : options.description,
  120. 'FileVersion' : ver,
  121. 'InternalName' : ifn,
  122. 'LegalCopyright' : options.copyright,
  123. 'LegalTrademarks' : options.trademarks,
  124. 'OriginalFilename' : ofn,
  125. 'ProductName' : options.product,
  126. 'ProductVersion' : ver,
  127. }
  128. vdata = {
  129. 'Translation' : struct.pack('hh', 0x409,1252),
  130. }
  131. is_dll = options.dll
  132. if is_dll is None:
  133. is_dll = os.path.splitext(pathname)[1].lower() in '.dll .pyd'.split()
  134. is_debug = options.debug
  135. if is_debug is None:
  136. is_debug = os.path.splitext(pathname)[0].lower().endswith("_d")
  137. # convert None to blank strings
  138. for k, v in list(sdata.items()):
  139. if v is None:
  140. sdata[k] = ""
  141. vs = VS_VERSION_INFO(vmaj, vmin, vsub, vbuild, sdata, vdata, is_debug, is_dll)
  142. h = BeginUpdateResource(pathname, 0)
  143. UpdateResource(h, 16, 1, vs)
  144. EndUpdateResource(h, 0)
  145. if options.verbose:
  146. print("Stamped:", pathname)
  147. if __name__ == '__main__':
  148. parser = optparse.OptionParser("%prog [options] filespec ...",
  149. description=__doc__)
  150. parser.add_option("-q", "--quiet",
  151. action="store_false", dest="verbose", default=True,
  152. help="don't print status messages to stdout")
  153. parser.add_option("", "--version", default="0.0.0.0",
  154. help="The version number as m.n.s.b")
  155. parser.add_option("", "--dll",
  156. help="""Stamp the file as a DLL. Default is to look at the
  157. file extension for .dll or .pyd.""")
  158. parser.add_option("", "--debug", help="""Stamp the file as a debug binary.""")
  159. parser.add_option("", "--product", help="""The product name to embed.""")
  160. parser.add_option("", "--company", help="""The company name to embed.""")
  161. parser.add_option("", "--trademarks", help="The trademark string to embed.")
  162. parser.add_option("", "--comments", help="The comments string to embed.")
  163. parser.add_option("", "--copyright",
  164. help="""The copyright message string to embed.""")
  165. parser.add_option("", "--description", metavar="DESC",
  166. help="The description to embed.")
  167. parser.add_option("", "--internal-name", metavar="NAME",
  168. help="""The internal filename to embed. If not specified
  169. the base filename is used.""")
  170. parser.add_option("", "--original-filename",
  171. help="""The original filename to embed. If not specified
  172. the base filename is used.""")
  173. options, args = parser.parse_args()
  174. if not args:
  175. parser.error("You must supply a file to stamp. Use --help for details.")
  176. for g in args:
  177. for f in glob.glob(g):
  178. stamp(f, options)