Rakefile 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. require 'tmpdir'
  2. require 'open-uri'
  3. require_relative 'build/TxClient'
  4. require_relative 'build/CsprojResxWriter'
  5. ISCC = ENV['ISCC'] || 'C:\Program Files (x86)\Inno Setup 5\ISCC.exe'
  6. SZIP = ENV['SZIP'] || 'C:\Program Files\7-Zip\7z.exe'
  7. SIGNTOOL = ENV['SIGNTOOL'] || 'C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin\signtool.exe'
  8. VSWHERE = 'build/vswhere.exe'
  9. CONFIG = ENV['CONFIG'] || 'Release'
  10. MSBUILD_VERSION = '15.0'
  11. MSBUILD_LOGGER = ENV['MSBUILD_LOGGER']
  12. SRC_DIR = 'src/SyncTrayzor'
  13. INSTALLER_DIR = 'installer'
  14. DEPLOY_DIR = 'deploy'
  15. SLN = 'src/SyncTrayzor.sln'
  16. CHOCOLATEY_NUSPEC = 'chocolatey/synctrayzor.nuspec'
  17. CHECKSUM_UTIL_CSPROJ = 'src/ChecksumUtil/ChecksumUtil.csproj'
  18. CHECKSUM_UTIL_EXE = 'bin/ChecksumUtil/Release/ChecksumUtil.exe'
  19. SYNCTHING_RELEASES_CERT = 'security/syncthing_releases_cert.asc'
  20. CHECKSUM_FILE_PRIV_KEY = 'security/private_key.asc'
  21. PFX = ENV['PFX'] || File.join(INSTALLER_DIR, 'SyncTrayzorCA.pfx')
  22. PORTABLE_SYNCTHING_VERSION = 'latest'
  23. class ArchDirConfig
  24. attr_reader :arch
  25. attr_reader :bin_dir
  26. attr_reader :installer_dir
  27. attr_reader :installer_output
  28. attr_reader :installer_iss
  29. attr_reader :portable_output_dir
  30. attr_reader :portable_output_file
  31. attr_reader :syncthing_binaries
  32. def initialize(arch, github_arch)
  33. @arch = arch
  34. @github_arch = github_arch
  35. @bin_dir = "bin/#{@arch}/#{CONFIG}"
  36. @installer_dir = File.join(INSTALLER_DIR, @arch)
  37. @installer_output = File.join(@installer_dir, "SyncTrayzorSetup-#{@arch}.exe")
  38. @installer_iss = File.join(@installer_dir, "installer-#{@arch}.iss")
  39. @portable_output_dir = "SyncTrayzorPortable-#{@arch}"
  40. @portable_output_file = File.join(DEPLOY_DIR, "SyncTrayzorPortable-#{@arch}.zip")
  41. @syncthing_binaries = { 'latest' => 'syncthing.exe' }
  42. end
  43. def sha1sum_download_uri(version)
  44. "https://github.com/syncthing/syncthing/releases/download/v#{version}/sha1sum.txt.asc"
  45. end
  46. def download_uri(version)
  47. "https://github.com/syncthing/syncthing/releases/download/v#{version}/syncthing-windows-#{@github_arch}-v#{version}.zip"
  48. end
  49. end
  50. SYNCTHING_VERSIONS_TO_UPDATE = ['latest']
  51. ARCH_CONFIG = [ArchDirConfig.new('x64', 'amd64'), ArchDirConfig.new('x86', '386')]
  52. ASSEMBLY_INFOS = FileList['**/AssemblyInfo.cs']
  53. def ensure_7zip
  54. unless File.exist?(SIGNTOOL)
  55. warn "You must install the Windows SDK"
  56. exit 1
  57. end
  58. end
  59. def build(sln, platform, rebuild = true)
  60. if ENV['MSBUILD']
  61. msbuild = ENV['MSBUILD']
  62. else
  63. path = `#{VSWHERE} -version #{MSBUILD_VERSION} -requires Microsoft.Component.MSBuild -format value -property installationPath`.chomp
  64. msbuild = File.join(path, 'MSBuild', MSBUILD_VERSION, 'Bin', 'MSBuild.exe')
  65. end
  66. puts "MSBuild is at #{msbuild}"
  67. tasks = rebuild ? 'Clean;Rebuild' : 'Build'
  68. cmd = "\"#{msbuild}\" \"#{sln}\" /t:#{tasks} /p:Configuration=#{CONFIG};Platform=#{platform}"
  69. if MSBUILD_LOGGER
  70. cmd << " /logger:\"#{MSBUILD_LOGGER}\" /verbosity:minimal"
  71. else
  72. cmd << " /verbosity:quiet"
  73. end
  74. sh cmd
  75. end
  76. namespace :build do
  77. ARCH_CONFIG.each do |arch_config|
  78. desc "Build the project (#{arch_config.arch})"
  79. task arch_config.arch do
  80. build(SLN, arch_config.arch)
  81. end
  82. end
  83. end
  84. desc 'Build both 64-bit and 32-bit binaries'
  85. task :build => ARCH_CONFIG.map{ |x| :"build:#{x.arch}" }
  86. task :"build-checksum-util" do
  87. build(CHECKSUM_UTIL_CSPROJ, 'AnyCPU', false)
  88. end
  89. namespace :installer do
  90. ARCH_CONFIG.each do |arch_config|
  91. desc "Create the installer (#{arch_config.arch})"
  92. task arch_config.arch do
  93. unless File.exist?(ISCC)
  94. warn "Please install Inno Setup"
  95. exit 1
  96. end
  97. rm arch_config.installer_output if File.exist?(arch_config.installer_output)
  98. sh %Q{"#{ISCC}" #{arch_config.installer_iss}}
  99. mkdir_p DEPLOY_DIR
  100. mv arch_config.installer_output, DEPLOY_DIR
  101. end
  102. end
  103. end
  104. desc 'Build both 64-bit and 32-bit installers'
  105. task :installer => ARCH_CONFIG.map{ |x| :"installer:#{x.arch}" }
  106. namespace :"sign-installer" do
  107. ARCH_CONFIG.each do |arch_config|
  108. desc "Sign the installer (#{arch_config.arch}). Specify PASSWORD if required"
  109. task arch_config.arch do
  110. ensure_7zip
  111. unless File.exist?(PFX)
  112. warn "#{PFX} must exist"
  113. exit 1
  114. end
  115. args = "sign /f #{PFX} /t http://timestamp.verisign.com/scripts/timstamp.dll"
  116. args << " /p #{ENV['PASSWORD']}" if ENV['PASSWORD']
  117. args << " /v #{File.join(DEPLOY_DIR, File.basename(arch_config.installer_output))}"
  118. # Don't want to print out the pasword!
  119. puts "Invoking signtool"
  120. system %Q{"#{SIGNTOOL}" #{args}}
  121. end
  122. end
  123. end
  124. desc 'Sign both 64-bit and 32-bit installers. Specify PASSWORD if required'
  125. task :"sign-installer" => ARCH_CONFIG.map{ |x| :"sign-installer:#{x.arch}" }
  126. def cp_to_portable(output_dir, src, output_filename = nil)
  127. dest = File.join(output_dir, output_filename || src)
  128. raise "Cannot find #{src}" unless File.exist?(src)
  129. # It could be an empty directory - so ignore it
  130. # We'll create it as and when if there are any files in it
  131. if File.file?(src)
  132. mkdir_p File.dirname(dest) unless File.exist?(File.dirname(dest))
  133. cp src, dest
  134. end
  135. end
  136. namespace :portable do
  137. ARCH_CONFIG.each do |arch_config|
  138. desc "Create the portable package (#{arch_config.arch})"
  139. task arch_config.arch do
  140. ensure_7zip
  141. mkdir_p File.dirname(arch_config.portable_output_file)
  142. rm arch_config.portable_output_file if File.exist?(arch_config.portable_output_file)
  143. Dir.mktmpdir do |tmp|
  144. portable_dir = File.join(tmp, arch_config.portable_output_dir)
  145. Dir.chdir(arch_config.bin_dir) do
  146. files = FileList['**/*'].exclude(
  147. '*.xml', '*.vshost.*', '*.log', '*.Installer.config', '*/FluentValidation.resources.dll',
  148. '*/System.Windows.Interactivity.resources.dll', 'syncthing.exe', 'data/*', 'logs',
  149. 'ffmpegsumo.dll', 'd3dcompiler_43.dll', 'd3dcompiler_47.dll', 'libEGL.dll', 'libGLESv2.dll', 'pdf.dll')
  150. files.each do |file|
  151. cp_to_portable(portable_dir, file)
  152. end
  153. end
  154. Dir.chdir(File.join('bin', 'PortableInstaller', CONFIG)) do
  155. cp_to_portable(portable_dir, 'PortableInstaller.exe')
  156. end
  157. cp File.join(SRC_DIR, 'Icons', 'default.ico'), arch_config.portable_output_dir
  158. FileList['*.md', '*.txt'].each do |file|
  159. cp_to_portable(portable_dir, file)
  160. end
  161. Dir.chdir(arch_config.installer_dir) do
  162. FileList['*.dll'].each do |file|
  163. cp_to_portable(portable_dir, file)
  164. end
  165. cp_to_portable(portable_dir, arch_config.syncthing_binaries[PORTABLE_SYNCTHING_VERSION], 'syncthing.exe')
  166. end
  167. sh %Q{"#{SZIP}" a -tzip -mx=7 -y #{arch_config.portable_output_file} #{portable_dir}}
  168. end
  169. end
  170. end
  171. end
  172. def create_checksums(checksum_file, password, algorithm, files)
  173. rm checksum_file if File.exist?(checksum_file)
  174. args = %Q{create "#{checksum_file}" #{algorithm} "#{CHECKSUM_FILE_PRIV_KEY}" "#{password}" } + files.map{ |x| "\"#{x}\"" }.join(' ')
  175. # Don't want to print out the pasword!
  176. puts "Invoking #{CHECKSUM_UTIL_EXE} for #{checksum_file}"
  177. system %Q{"#{CHECKSUM_UTIL_EXE}" #{args}}
  178. end
  179. desc 'Create checksums files'
  180. task :"create-checksums" => [:"build-checksum-util"] do
  181. password = ENV['PASSWORD'] || '""'
  182. checksum_file = File.join(DEPLOY_DIR, 'sha1sum.txt.asc')
  183. files = Dir["#{DEPLOY_DIR}/*.{zip,exe}"]
  184. create_checksums(File.join(DEPLOY_DIR, 'sha1sum.txt.asc'), password, 'sha1', files)
  185. create_checksums(File.join(DEPLOY_DIR, 'sha512sum.txt.asc'), password, 'sha512', files)
  186. end
  187. desc 'Clean portable and installer, all architectures'
  188. task :clean do
  189. rm_rf DEPLOY_DIR if File.exist?(DEPLOY_DIR)
  190. end
  191. namespace :package do
  192. ARCH_CONFIG.each do |arch_config|
  193. desc "Build installer and portable (#{arch_config.arch})"
  194. task arch_config.arch =>
  195. [
  196. :"update-syncthing:#{arch_config.arch}",
  197. :"build:#{arch_config.arch}",
  198. :"installer:#{arch_config.arch}",
  199. :"sign-installer:#{arch_config.arch}",
  200. :"portable:#{arch_config.arch}"
  201. ]
  202. end
  203. end
  204. desc 'Build installer and portable for all architectures'
  205. task :package => [:clean, *ARCH_CONFIG.map{ |x| :"package:#{x.arch}" }, :"create-checksums"]
  206. desc 'Build chocolatey package'
  207. task :chocolatey do
  208. chocolatey_dir = File.dirname(CHOCOLATEY_NUSPEC)
  209. cp Dir[File.join(DEPLOY_DIR, 'SyncTrayzorSetup-*.exe')], File.join(chocolatey_dir, 'tools')
  210. Dir.chdir(chocolatey_dir) do
  211. sh "choco pack"
  212. end
  213. mv Dir[File.join(chocolatey_dir, 'synctrayzor.*.nupkg')], DEPLOY_DIR
  214. end
  215. desc "Bump version number"
  216. task :version, [:version] do |t, args|
  217. parts = args[:version].split('.')
  218. parts4 = parts.dup
  219. parts4 << '0' if parts4.length == 3
  220. version4 = parts4.join('.')
  221. ASSEMBLY_INFOS.each do |info|
  222. content = IO.read(info)
  223. content[/^\[assembly: AssemblyVersion\(\"(.+?)\"\)\]/, 1] = version4
  224. content[/^\[assembly: AssemblyFileVersion\(\"(.+?)\"\)\]/, 1] = version4
  225. File.open(info, 'w'){ |f| f.write(content) }
  226. end
  227. choco_content = IO.read(CHOCOLATEY_NUSPEC)
  228. choco_content[/<version>(.+?)<\/version>/, 1] = args[:version]
  229. File.open(CHOCOLATEY_NUSPEC, 'w'){ |f| f.write(choco_content) }
  230. end
  231. desc 'Create both 64-bit and 32-bit portable packages'
  232. task :portable => ARCH_CONFIG.map{ |x| :"portable:#{x.arch}" }
  233. namespace :syncthing do
  234. namespace :download do
  235. ARCH_CONFIG.each do |arch_config|
  236. desc "Download syncthing (#{arch_config.arch})"
  237. task arch_config.arch, [:version] => [:"build-checksum-util"] do |t, args|
  238. ensure_7zip
  239. Dir.mktmpdir do |tmp|
  240. download_file = File.join(tmp, File.basename(arch_config.download_uri(args[:version])))
  241. File.open(download_file, 'wb') do |outfile|
  242. open(arch_config.download_uri(args[:version])) do |infile|
  243. outfile.write(infile.read)
  244. end
  245. end
  246. File.open(File.join(tmp, 'sha1sum.txt.asc.'), 'w') do |outfile|
  247. open(arch_config.sha1sum_download_uri(args[:version])) do |infile|
  248. outfile.write(infile.read)
  249. end
  250. end
  251. sh CHECKSUM_UTIL_EXE, 'verify', File.join(tmp, 'sha1sum.txt.asc'), 'sha1', SYNCTHING_RELEASES_CERT, download_file
  252. Dir.chdir(tmp) do
  253. sh %Q{"#{SZIP}" e -y #{File.basename(download_file)}}
  254. end
  255. cp File.join(tmp, 'syncthing.exe'), File.join(arch_config.installer_dir, 'syncthing.exe')
  256. end
  257. end
  258. end
  259. end
  260. desc 'Download syncthing for all architectures'
  261. task :download, [:version] => ARCH_CONFIG.map{ |x| :"syncthing:download:#{x.arch}" }
  262. namespace :update do
  263. ARCH_CONFIG.each do |arch_config|
  264. desc "Update syncthing binaries (#{arch_config.arch}"
  265. task arch_config.arch do
  266. arch_config.syncthing_binaries.values_at(*SYNCTHING_VERSIONS_TO_UPDATE).each do |bin|
  267. path = File.join(arch_config.installer_dir, bin)
  268. raise "Could not find #{path}" unless File.exist?(path)
  269. Dir.mktmpdir do |tmp|
  270. sh path, '-upgrade', "-home=#{tmp}" do; end
  271. end
  272. old_bin = "#{path}.old"
  273. rm old_bin if File.exist?(old_bin)
  274. end
  275. end
  276. end
  277. end
  278. desc 'Update syncthing binaries, all architectures'
  279. task :update => ARCH_CONFIG.map{ |x| :"syncthing:update:#{x.arch}" }
  280. end
  281. def create_tx_client
  282. raise "TX_PASSWORD not specified" if ENV['TX_PASSWORD'].nil? || ENV['TX_PASSWORD'].empty?
  283. TxClient.new('synctrayzor', 'strings', 'canton7', ENV['TX_PASSWORD'])
  284. end
  285. def create_csproj_resx_writer
  286. csproj_resx_writer = CsprojResxWriter.new('src/SyncTrayzor/SyncTrayzor.csproj', 'Properties')
  287. csproj_resx_writer.language_exceptions['es_ES'] = 'es'
  288. csproj_resx_writer
  289. end
  290. namespace :tx do
  291. desc "Remove all translations from csproj"
  292. task :clean do
  293. create_csproj_resx_writer().remove_all_resx
  294. puts "Cleaned translations"
  295. end
  296. desc "Fetch all translations"
  297. task :pull => [:"tx:clean"] do
  298. tx_client = create_tx_client()
  299. csproj_resx_writer = create_csproj_resx_writer()
  300. tx_client.list_translations().each do |language|
  301. next if language == 'en'
  302. puts "Fetching #{language}..."
  303. tx_client.download_translation(language, csproj_resx_writer.absolute_resx_path_for_language(language))
  304. csproj_resx_writer.add_resx_to_csproj(language)
  305. end
  306. end
  307. desc "Push source translations"
  308. task :push do
  309. tx_client = create_tx_client()
  310. csproj_resx_writer = create_csproj_resx_writer()
  311. source_resx = csproj_resx_writer.read_and_sort_source_resx
  312. response = tx_client.upload_source(source_resx)
  313. puts "Added: #{response['strings_added']}. Updated: #{response['strings_updated']}. Deleted: #{response['strings_delete']}."
  314. end
  315. end