diff --git a/tools/README b/tools/README index 1648412..c92eedd 100644 --- a/tools/README +++ b/tools/README @@ -19,3 +19,14 @@ This directory contains a set of tools for preparing auto-updates. This is the simplest possible package configuration, where all files for a release are placed in a single package. This means that the whole package will need to be downloaded to install the update. + + * auto-create-packages.rb + + -v [-o ] + + This tool generates a zip file from difference of old version and new version of an application files. + The zip file name contains versions like this: OldVersion_NewVersion.zip + The zip file contains generated package (out.zip) an file_list.xml and updater.exe and libbz2.dll + this file needed in user system for update. + Note: updater.exe is build in Windows 10 with Visual Studio 2015 + \ No newline at end of file diff --git a/tools/auto-create-packages.rb b/tools/auto-create-packages.rb new file mode 100644 index 0000000..1649133 --- /dev/null +++ b/tools/auto-create-packages.rb @@ -0,0 +1,173 @@ +#!/usr/bin/ruby +require 'fileutils.rb' +require 'find' +require 'optparse' +######################################## +# copy 'src' to 'dest', preserving the attributes +# of 'src' +def copy_file(src, dest) + FileUtils.cp src, dest, :preserve => true +end + +# Returns true if |src_file| and |dest_file| have the same contents, type +# and permissions or false otherwise +def compare_files(src_file, dest_file) + if File.ftype(src_file) != File.ftype(dest_file) + $stderr.puts "Type of file #{src_file} and #{dest_file} differ" + return false + end + + if File.file?(src_file) && !FileUtils.identical?(src_file, dest_file) + $stderr.puts "Contents of file #{src_file} and #{dest_file} differ" + return false + end + + src_stat = File.stat(src_file) + dest_stat = File.stat(dest_file) + + if src_stat.mode != dest_stat.mode + $stderr.puts "Permissions of #{src_file} and #{dest_file} differ" + return false + end + + return true +end + + +# Compares the contents of two directories and returns a map of (file path => change type) +# for files and directories which differ between the two +def compare_dirs(src_dir, dest_dir) + src_dir += '/' if !src_dir.end_with?('/') + dest_dir += '/' if !dest_dir.end_with?('/') + + src_file_map = {} + Find.find(src_dir) do |src_file| + src_file = src_file[src_dir.length..-1] + src_file_map[src_file] = nil + end + + change_map = {} + Find.find(dest_dir) do |dest_file| + dest_file = dest_file[dest_dir.length..-1] + + if !src_file_map.include?(dest_file) + change_map[dest_file] = :deleted + elsif !compare_files("#{src_dir}/#{dest_file}", "#{dest_dir}/#{dest_file}") + change_map[dest_file] = :updated + end + + src_file_map.delete(dest_file) + end + + src_file_map.each do |file, val| + change_map[file] = :added + end + + return change_map +end + +def create_new_package(src_dir, dest_dir, package_dir) + deleted_files = [] + change_map = compare_dirs(src_dir, dest_dir) + change_map.each do |file, type| + puts "#{file}, #{type}" + if(type == :added || type == :updated) + #puts "added...........updated" + copy_file("#{src_dir}/#{file}", package_dir) + elsif(type == :deleted) + deleted_files << file; + end + end + return deleted_files +end + +def create_file(name, content) + File.open(name, 'w') do |file| + file.puts content + end + return name +end +puts "*********************" + +########### +target_version = nil +old_version = "x.x.x" +OptionParser.new do |parser| + parser.banner = "#{$0} -v [-o ]" + parser.on("-v","--version [version]","Specifies the target version string for this update") do |version| + target_version = version + end + parser.on("-o","--oldversion [version]","Specifies the old version string for this update") do |oldversion| + old_version = oldversion + end +end.parse! + +raise "Target version not specified (use -v option)" if !target_version + +if ARGV.length < 3 + raise "Missing arguments ( #{$0} -v version ) " +end + +input_old_dir = ARGV[0] +input_new_dir = ARGV[1] +output_dir = ARGV[2] + + +dif_version_files = "temp" + +# Remove the temp package dirs if they +# already exist +FileUtils.rm_rf(dif_version_files) +FileUtils.rm_rf(output_dir) +# Create the install directory with the old app +Dir.mkdir(dif_version_files) +#Dir.mkdir(output_dir) + +# Create Version file +create_file("#{dif_version_files}/version.ini", target_version) + +deleted_files = create_new_package(input_new_dir, input_old_dir, dif_version_files) + +########### +puts "*********************" +########### + +ENV["updater"] = "updater.exe" +ENV["dependency"] = "libbz2.dll" + +ENV["platform"] = "MsWin32" +ENV["version"] = target_version + +ENV["input"] = dif_version_files +ENV["package"] = "config.js" +ENV["output"] = output_dir +ENV["uninstall"] = deleted_files.join(" *spliter* ") + +require_relative ("create-packages.rb") + +# Remove the temp package dir +FileUtils.rm_rf(dif_version_files) + +# get the all of each output files path +output_file_list = [] +output_file_list_for_delete = [] +output_zip_file = "#{old_version}_#{target_version}.zip" + +Find.find(output_dir) do |path| + next if (File.directory?(path) && !File.symlink?(path)) + output_file_list << "\"#{strip_prefix_slash(strip_prefix(path, output_dir))}\"" + output_file_list_for_delete << path +end + +currentDir = Dir.pwd +Dir.chdir(output_dir) do + if (!system("#{currentDir}/zip #{output_zip_file} #{output_file_list.join(" ")}")) + raise "Failed to generate package zip file" + else + puts "Generated package zip file : #{output_zip_file}" + end +end + +output_file_list_for_delete.each do |file| + FileUtils.rm_rf(file) +end diff --git a/tools/create-packages.rb b/tools/create-packages.rb index 583dce6..f4634b9 100755 --- a/tools/create-packages.rb +++ b/tools/create-packages.rb @@ -82,6 +82,14 @@ def strip_prefix(string,prefix) return string.sub(prefix,"") end +def strip_prefix_slash(string) + prefix = "/" + if (string.start_with?(prefix)) + return string.sub(prefix,"") + end + return string +end + def file_sha1(path) Digest::SHA1.file(path).to_s end @@ -95,6 +103,7 @@ class UpdateScriptGenerator # package_config - The PackageConfig specifying the file -> package map # for the application and other config options # file_list - A list of all files in 'input_dir' which make up the install + # uninstall_file_list - A list of all files which make up the uninstall # package_file_map - A map of (package name -> [paths of files in this package]) def initialize(target_version, @@ -103,18 +112,22 @@ def initialize(target_version, output_dir, package_config, file_list, + uninstall_file_list, package_file_map) @target_version = target_version @platform = platform @config = package_config + # List of files to uninstall in this version + @files_to_uninstall = uninstall_file_list + # List of files to install in this version @files_to_install = [] file_list.each do |path| file = UpdateScriptFile.new file.path = strip_prefix(path,input_dir) - + #puts file.path if (File.basename(path) == @config.updater_binary) # for the updater binary, use the possibly substituted # version in the output directory @@ -173,7 +186,8 @@ def to_xml() update_elem.add_element deps_to_xml() update_elem.add_element packages_to_xml() update_elem.add_element install_to_xml() - + update_elem.add_element uninstall_to_xml() + output = "" doc.write output return output @@ -227,6 +241,16 @@ def install_to_xml() return install_elem end + def uninstall_to_xml() + uninstall_elem = REXML::Element.new("uninstall") + @files_to_uninstall.each do |file| + file_elem = REXML::Element.new("file") + uninstall_elem.add_element(file_elem) + file_elem.text = file + end + return uninstall_elem + end + def file_mode_string(mode) mode_octal = (mode & 0777).to_s(8) return "0#{mode_octal}" @@ -234,7 +258,7 @@ def file_mode_string(mode) end class PackageConfig - attr_reader :main_binary, :updater_binary + attr_reader :main_binary, :updater_binary, :updater_binary_dependency def initialize(map_file) @rule_map = {} @@ -248,10 +272,11 @@ def initialize(map_file) @main_binary = config_json["main-binary"] @updater_binary = config_json["updater-binary"] + @updater_binary_dependency = config_json["updater-binary-dependency"] end def is_updater(file) - return File.basename(file) == @updater_binary + return (File.basename(file) == @updater_binary && File.basename(file) == @updater_binary_dependency) end def package_for_file(file) @@ -269,11 +294,28 @@ def package_for_file(file) end updater_binary_input_path = nil +updater_dependency_input_path = nil target_version = nil target_platform = nil zip_flags = nil -OptionParser.new do |parser| +updater_binary_input_path = ENV.fetch("updater", nil) +updater_dependency_input_path = ENV.fetch("dependency", nil) +target_version = ENV.fetch("version", nil) +target_platform = ENV.fetch("platform", nil) + +input_dir = ENV.fetch("input", nil) +package_map_file = ENV.fetch("package", nil) +output_dir = ENV.fetch("output", nil) +uninstall_file_string = ENV.fetch("uninstall", []) +uninstall_file_list = [] +if(uninstall_file_string != nil && !uninstall_file_string.empty?) + uninstall_file_list = uninstall_file_string.split(" *spliter* ") +end + +if (updater_binary_input_path == nil || target_version == nil || target_platform == nil) + + OptionParser.new do |parser| parser.banner = "#{$0} [options] " parser.on("-u","--updater [updater binary]","Specifies the updater binary to use") do |updater| updater_binary_input_path = updater @@ -287,23 +329,29 @@ def package_for_file(file) parser.on(nil,"--bzip2","Use bzip2 compression (requires that 'zip' supports the -Z bz2 argument)") do zip_flags = "-Z bzip2" end -end.parse! + end.parse! + + raise "Platform not specified (use -p option)" if !target_platform + raise "Target version not specified (use -v option)" if !target_version + + if ARGV.length < 3 + raise "Missing arguments" + end -raise "Platform not specified (use -p option)" if !target_platform -raise "Target version not specified (use -v option)" if !target_version + input_dir = ARGV[0] + package_map_file = ARGV[1] + output_dir = ARGV[2] -if ARGV.length < 3 - raise "Missing arguments" end -input_dir = ARGV[0] -package_map_file = ARGV[1] -output_dir = ARGV[2] + + FileUtils.mkpath(output_dir) # get the details of each input file input_file_list = [] + Find.find(input_dir) do |path| next if (File.directory?(path) && !File.symlink?(path)) input_file_list << path @@ -328,11 +376,13 @@ def package_for_file(file) package_file_map[package] = [] if !package_file_map[package] package_file_map[package] << file end - +def copy_file(src, dest) + FileUtils.cp src, dest, :preserve => true +end # generate each package package_file_map.each do |package,files| puts "Generating package #{package}" - + quoted_files = [] files.each do |file| # do not package the updater binary into a zip file - @@ -343,7 +393,12 @@ def package_for_file(file) end next end - quoted_files << "\"#{strip_prefix(file,input_dir)}\"" + quoted_files << "\"#{strip_prefix_slash(strip_prefix(file,input_dir))}\"" + ###################################################### + #puts "\".#{strip_prefix(file,input_dir)}\"" + #output_path = File.expand_path(input_dir) + #quoted_files << "#{output_path}#{strip_prefix(file,input_dir)}" + ###################################################### end if (quoted_files.empty?) @@ -369,9 +424,23 @@ def package_for_file(file) output_file = "#{output_path}/#{package}.zip" File.unlink(output_file) if File.exist?(output_file) - +####################################### +# Dir.chdir(input_dir) do +# puts "zip #{zip_flags} #{output_file} #{quoted_file_list}" +# +# if (!system("zip #{zip_flags} #{output_file} #{quoted_file_list}")) +# raise "Failed to generate package #{package}" +# else +# puts "Generated package #{package} : #{file_sha1(output_file)}" +# end +# end +#puts Dir.pwd #current dir +#copy_file("zip.exe", "#{File.expand_path(input_dir)}/zip.exe") +#copy_file("zlib1.dll", "#{File.expand_path(input_dir)}/zlib1.dll") +#################################### + currentDir = Dir.pwd Dir.chdir(input_dir) do - if (!system("zip #{zip_flags} #{output_file} #{quoted_file_list}")) + if (!system("#{currentDir}/zip #{zip_flags} #{output_file} #{quoted_file_list}")) raise "Failed to generate package #{package}" else puts "Generated package #{package} : #{file_sha1(output_file)}" @@ -380,17 +449,18 @@ def package_for_file(file) end # copy the updater to the output directory -puts "Using updater binary: #{updater_binary_input_path}" -if !updater_binary_input_path +puts "Using updater binary: #{updater_binary_input_path} and #{updater_dependency_input_path} " +if (!updater_binary_input_path || !updater_dependency_input_path) puts "Updater binary not found in input directory: #{input_dir}" exit(1) end FileUtils.cp updater_binary_input_path, "#{output_dir}/#{File.basename(updater_binary_input_path)}", :preserve => true +FileUtils.cp updater_dependency_input_path, "#{output_dir}/#{File.basename(updater_dependency_input_path)}", :preserve => true # output the file_list.xml file update_script = UpdateScriptGenerator.new(target_version,target_platform,input_dir, - output_dir,package_config,input_file_list,package_file_map) + output_dir,package_config,input_file_list, uninstall_file_list,package_file_map) output_xml_file = "#{output_dir}/file_list.unformatted.xml" File.open(output_xml_file,'w') do |file| file.write update_script.to_xml() diff --git a/tools/tests/README.txt b/tools/tests/README.txt new file mode 100644 index 0000000..a31540d --- /dev/null +++ b/tools/tests/README.txt @@ -0,0 +1 @@ +This directory contains a set of tests for auto-create-packages.rb tool. diff --git a/tools/tests/auto-create-packages.rb b/tools/tests/auto-create-packages.rb new file mode 100644 index 0000000..1649133 --- /dev/null +++ b/tools/tests/auto-create-packages.rb @@ -0,0 +1,173 @@ +#!/usr/bin/ruby +require 'fileutils.rb' +require 'find' +require 'optparse' +######################################## +# copy 'src' to 'dest', preserving the attributes +# of 'src' +def copy_file(src, dest) + FileUtils.cp src, dest, :preserve => true +end + +# Returns true if |src_file| and |dest_file| have the same contents, type +# and permissions or false otherwise +def compare_files(src_file, dest_file) + if File.ftype(src_file) != File.ftype(dest_file) + $stderr.puts "Type of file #{src_file} and #{dest_file} differ" + return false + end + + if File.file?(src_file) && !FileUtils.identical?(src_file, dest_file) + $stderr.puts "Contents of file #{src_file} and #{dest_file} differ" + return false + end + + src_stat = File.stat(src_file) + dest_stat = File.stat(dest_file) + + if src_stat.mode != dest_stat.mode + $stderr.puts "Permissions of #{src_file} and #{dest_file} differ" + return false + end + + return true +end + + +# Compares the contents of two directories and returns a map of (file path => change type) +# for files and directories which differ between the two +def compare_dirs(src_dir, dest_dir) + src_dir += '/' if !src_dir.end_with?('/') + dest_dir += '/' if !dest_dir.end_with?('/') + + src_file_map = {} + Find.find(src_dir) do |src_file| + src_file = src_file[src_dir.length..-1] + src_file_map[src_file] = nil + end + + change_map = {} + Find.find(dest_dir) do |dest_file| + dest_file = dest_file[dest_dir.length..-1] + + if !src_file_map.include?(dest_file) + change_map[dest_file] = :deleted + elsif !compare_files("#{src_dir}/#{dest_file}", "#{dest_dir}/#{dest_file}") + change_map[dest_file] = :updated + end + + src_file_map.delete(dest_file) + end + + src_file_map.each do |file, val| + change_map[file] = :added + end + + return change_map +end + +def create_new_package(src_dir, dest_dir, package_dir) + deleted_files = [] + change_map = compare_dirs(src_dir, dest_dir) + change_map.each do |file, type| + puts "#{file}, #{type}" + if(type == :added || type == :updated) + #puts "added...........updated" + copy_file("#{src_dir}/#{file}", package_dir) + elsif(type == :deleted) + deleted_files << file; + end + end + return deleted_files +end + +def create_file(name, content) + File.open(name, 'w') do |file| + file.puts content + end + return name +end +puts "*********************" + +########### +target_version = nil +old_version = "x.x.x" +OptionParser.new do |parser| + parser.banner = "#{$0} -v [-o ]" + parser.on("-v","--version [version]","Specifies the target version string for this update") do |version| + target_version = version + end + parser.on("-o","--oldversion [version]","Specifies the old version string for this update") do |oldversion| + old_version = oldversion + end +end.parse! + +raise "Target version not specified (use -v option)" if !target_version + +if ARGV.length < 3 + raise "Missing arguments ( #{$0} -v version ) " +end + +input_old_dir = ARGV[0] +input_new_dir = ARGV[1] +output_dir = ARGV[2] + + +dif_version_files = "temp" + +# Remove the temp package dirs if they +# already exist +FileUtils.rm_rf(dif_version_files) +FileUtils.rm_rf(output_dir) +# Create the install directory with the old app +Dir.mkdir(dif_version_files) +#Dir.mkdir(output_dir) + +# Create Version file +create_file("#{dif_version_files}/version.ini", target_version) + +deleted_files = create_new_package(input_new_dir, input_old_dir, dif_version_files) + +########### +puts "*********************" +########### + +ENV["updater"] = "updater.exe" +ENV["dependency"] = "libbz2.dll" + +ENV["platform"] = "MsWin32" +ENV["version"] = target_version + +ENV["input"] = dif_version_files +ENV["package"] = "config.js" +ENV["output"] = output_dir +ENV["uninstall"] = deleted_files.join(" *spliter* ") + +require_relative ("create-packages.rb") + +# Remove the temp package dir +FileUtils.rm_rf(dif_version_files) + +# get the all of each output files path +output_file_list = [] +output_file_list_for_delete = [] +output_zip_file = "#{old_version}_#{target_version}.zip" + +Find.find(output_dir) do |path| + next if (File.directory?(path) && !File.symlink?(path)) + output_file_list << "\"#{strip_prefix_slash(strip_prefix(path, output_dir))}\"" + output_file_list_for_delete << path +end + +currentDir = Dir.pwd +Dir.chdir(output_dir) do + if (!system("#{currentDir}/zip #{output_zip_file} #{output_file_list.join(" ")}")) + raise "Failed to generate package zip file" + else + puts "Generated package zip file : #{output_zip_file}" + end +end + +output_file_list_for_delete.each do |file| + FileUtils.rm_rf(file) +end diff --git a/tools/tests/config.js b/tools/tests/config.js new file mode 100644 index 0000000..a885a34 --- /dev/null +++ b/tools/tests/config.js @@ -0,0 +1,13 @@ +{ + "packages" : { + "out" : [ + ".*" + ] + }, + + + "updater-binary" : "updater.exe", + "updater-binary-dependency" : "libbz2.dll", + + "main-binary" : "out.zip" +} diff --git a/tools/tests/create-packages.rb b/tools/tests/create-packages.rb new file mode 100644 index 0000000..f4634b9 --- /dev/null +++ b/tools/tests/create-packages.rb @@ -0,0 +1,474 @@ +#!/usr/bin/ruby + +require 'digest/sha1' +require 'fileutils' +require 'rubygems' +require 'find' +require 'json' +require 'rexml/document' +require 'optparse' + +# syntax: +# +# create-packages.rb +# +# Takes the set of files that make up a release and splits them up into +# a set of .zip packages along with a file_list.xml file listing all +# the files in the release and mapping them to their respective +# packages. +# +# Outputs: +# +# /$PACKAGE_NAME.zip +# These are packages containing the files in this version of the software. +# +# /file_list.xml +# This file lists all the files contained in this version of the software and +# the packages which they are contained in. +# +# /$UPDATER_BINARY +# The standalone auto-update installer binary. This is not compressed so that +# it can be downloaded and executed directly. +# + +# Represents a group of updates in a release +class UpdateScriptPackage + # name - The name of the package (without any extension) + # hash - The SHA-1 hash of the package + # size - The size of the package in bytes + attr_reader :name,:hash,:size + attr_writer :name,:hash,:size +end + +# Represents a single file in a release +class UpdateScriptFile + # path - The path of the file relative to the installation directory + # hash - The SHA-1 hash of the file + # permissions - The permissions of the file (See File::stat) + # size - The size of the file in bytes + # package - The name of the package containing this file + attr_reader :path,:hash,:permissions,:size,:package,:target,:is_main_binary + attr_writer :path,:hash,:permissions,:size,:package,:target,:is_main_binary +end + +# Utility method - convert a hash map to an REXML element +# +# Hash keys are converted to elements and hash values either +# to text contents or child elements (if the value is a Hash) +# +# 'root' - the root REXML::Element +# 'map' - a hash mapping element names to text contents or +# hash maps +# +def hash_to_xml(root,map) + map.each do |key,value| + element = REXML::Element.new(key) + if value.instance_of?(String) + element.text = value.dup + elsif value.instance_of?(Hash) + hash_to_xml(element,value) + elsif !value.nil? + raise "Unsupported value type #{value.class}" + end + root.add_element element + end + return root +end + +def strip_prefix(string,prefix) + if (!string.start_with?(prefix)) + raise "String does not start with prefix" + end + return string.sub(prefix,"") +end + +def strip_prefix_slash(string) + prefix = "/" + if (string.start_with?(prefix)) + return string.sub(prefix,"") + end + return string +end + +def file_sha1(path) + Digest::SHA1.file(path).to_s +end + +class UpdateScriptGenerator + + # target_version - The version string for the build in this update + # platform - The platform which this build is for + # input_dir - The directory containing files that make up the install + # output_dir - The directory containing the generated packages + # package_config - The PackageConfig specifying the file -> package map + # for the application and other config options + # file_list - A list of all files in 'input_dir' which make up the install + # uninstall_file_list - A list of all files which make up the uninstall + # package_file_map - A map of (package name -> [paths of files in this package]) + + def initialize(target_version, + platform, + input_dir, + output_dir, + package_config, + file_list, + uninstall_file_list, + package_file_map) + + @target_version = target_version + @platform = platform + @config = package_config + + # List of files to uninstall in this version + @files_to_uninstall = uninstall_file_list + + # List of files to install in this version + @files_to_install = [] + file_list.each do |path| + file = UpdateScriptFile.new + file.path = strip_prefix(path,input_dir) + #puts file.path + if (File.basename(path) == @config.updater_binary) + # for the updater binary, use the possibly substituted + # version in the output directory + path = "#{output_dir}/#{@config.updater_binary}" + end + + file.is_main_binary = (file.path == package_config.main_binary) + + if (File.symlink?(path)) + file.target = File.readlink(path) + else + file.hash = file_sha1(path) + file.permissions = File.stat(path).mode + file.size = File.size(path) + file.package = @config.package_for_file(file.path) + + if (!file.package) + raise "Could not find package for file #{file.path}" + end + end + + @files_to_install << file + end + + # List of packages containing files for this version + @packages = [] + package_file_map.each do |package_name,files| + if (package_name == @config.updater_binary) + path = "#{output_dir}/#{package_name}" + else + path = "#{output_dir}/#{package_name}.zip" + end + + package = UpdateScriptPackage.new + package.name = package_name + package.size = File.size(path) + package.hash = file_sha1(path) + @packages << package + end + end + + def to_xml() + doc = REXML::Document.new + update_elem = REXML::Element.new("update") + doc.add_element update_elem + + version_elem = REXML::Element.new("targetVersion") + version_elem.text = @target_version + + platform_elem = REXML::Element.new("platform") + platform_elem.text = @platform + + update_elem.add_attribute("version","3") + update_elem.add_element version_elem + update_elem.add_element platform_elem + update_elem.add_element deps_to_xml() + update_elem.add_element packages_to_xml() + update_elem.add_element install_to_xml() + update_elem.add_element uninstall_to_xml() + + output = "" + doc.write output + return output + end + + def deps_to_xml() + deps_elem = REXML::Element.new("dependencies") + dependency = @config.updater_binary + dep_elem = REXML::Element.new("file") + dep_elem.text = dependency + deps_elem.add_element dep_elem + return deps_elem + end + + def packages_to_xml() + packages_elem = REXML::Element.new("packages") + @packages.each do |package| + package_elem = REXML::Element.new("package") + packages_elem.add_element package_elem + hash_to_xml(package_elem,{ + "name" => package.name, + "size" => package.size.to_s, + "hash" => package.hash + }) + end + return packages_elem + end + + def install_to_xml() + install_elem = REXML::Element.new("install") + @files_to_install.each do |file| + file_elem = REXML::Element.new("file") + install_elem.add_element(file_elem) + + attributes = {"name" => file.path} + if (file.target) + attributes["target"] = file.target + else + attributes["size"] = file.size.to_s + attributes["permissions"] = file_mode_string(file.permissions) + attributes["hash"] = file.hash + attributes["package"] = file.package + end + + if (file.is_main_binary) + attributes["is-main-binary"] = "true" + end + + hash_to_xml(file_elem,attributes) + end + return install_elem + end + + def uninstall_to_xml() + uninstall_elem = REXML::Element.new("uninstall") + @files_to_uninstall.each do |file| + file_elem = REXML::Element.new("file") + uninstall_elem.add_element(file_elem) + file_elem.text = file + end + return uninstall_elem + end + + def file_mode_string(mode) + mode_octal = (mode & 0777).to_s(8) + return "0#{mode_octal}" + end +end + +class PackageConfig + attr_reader :main_binary, :updater_binary, :updater_binary_dependency + + def initialize(map_file) + @rule_map = {} + config_json = JSON.parse(File.read(map_file)) + config_json["packages"].each do |package,rules| + rules.each do |rule| + rule_regex = Regexp.new("^#{rule}") + @rule_map[rule_regex] = package + end + end + + @main_binary = config_json["main-binary"] + @updater_binary = config_json["updater-binary"] + @updater_binary_dependency = config_json["updater-binary-dependency"] + end + + def is_updater(file) + return (File.basename(file) == @updater_binary && File.basename(file) == @updater_binary_dependency) + end + + def package_for_file(file) + match = nil + @rule_map.each do |rule,package| + if (file =~ rule) + if match && match != package + raise "Multiple packages match file #{file} - found '#{match}' and '#{package}'" + end + match = package + end + end + return match + end +end + +updater_binary_input_path = nil +updater_dependency_input_path = nil +target_version = nil +target_platform = nil +zip_flags = nil + +updater_binary_input_path = ENV.fetch("updater", nil) +updater_dependency_input_path = ENV.fetch("dependency", nil) +target_version = ENV.fetch("version", nil) +target_platform = ENV.fetch("platform", nil) + +input_dir = ENV.fetch("input", nil) +package_map_file = ENV.fetch("package", nil) +output_dir = ENV.fetch("output", nil) +uninstall_file_string = ENV.fetch("uninstall", []) +uninstall_file_list = [] +if(uninstall_file_string != nil && !uninstall_file_string.empty?) + uninstall_file_list = uninstall_file_string.split(" *spliter* ") +end + +if (updater_binary_input_path == nil || target_version == nil || target_platform == nil) + + OptionParser.new do |parser| + parser.banner = "#{$0} [options] " + parser.on("-u","--updater [updater binary]","Specifies the updater binary to use") do |updater| + updater_binary_input_path = updater + end + parser.on("-v","--version [version]","Specifies the target version string for this update") do |version| + target_version = version + end + parser.on("-p","--platform [platform]","Specifies the target platform for this update") do |platform| + target_platform = platform + end + parser.on(nil,"--bzip2","Use bzip2 compression (requires that 'zip' supports the -Z bz2 argument)") do + zip_flags = "-Z bzip2" + end + end.parse! + + raise "Platform not specified (use -p option)" if !target_platform + raise "Target version not specified (use -v option)" if !target_version + + if ARGV.length < 3 + raise "Missing arguments" + end + + input_dir = ARGV[0] + package_map_file = ARGV[1] + output_dir = ARGV[2] + +end + + + + +FileUtils.mkpath(output_dir) + +# get the details of each input file +input_file_list = [] + +Find.find(input_dir) do |path| + next if (File.directory?(path) && !File.symlink?(path)) + input_file_list << path +end + +# map each input file to a corresponding package + +# read the package map +package_config = PackageConfig.new(package_map_file) + +# map of package name -> array of files +package_file_map = {} + +input_file_list.each do |file| + next if File.symlink?(file) + + relative_file = strip_prefix(file,input_dir) + package = package_config.package_for_file(relative_file) + if (!package) + raise "Unable to find package for file #{file}" + end + package_file_map[package] = [] if !package_file_map[package] + package_file_map[package] << file +end +def copy_file(src, dest) + FileUtils.cp src, dest, :preserve => true +end +# generate each package +package_file_map.each do |package,files| + puts "Generating package #{package}" + + quoted_files = [] + files.each do |file| + # do not package the updater binary into a zip file - + # it must be downloaded uncompressed + if package_config.is_updater(file) + if (!updater_binary_input_path) + updater_binary_input_path = file + end + next + end + quoted_files << "\"#{strip_prefix_slash(strip_prefix(file,input_dir))}\"" + ###################################################### + #puts "\".#{strip_prefix(file,input_dir)}\"" + #output_path = File.expand_path(input_dir) + #quoted_files << "#{output_path}#{strip_prefix(file,input_dir)}" + ###################################################### + end + + if (quoted_files.empty?) + puts "Skipping generation of empty package #{package}" + next + end + + # sort the files in alphabetical order to ensure + # that if the input files for a package have the same + # name and content, the resulting package will have the + # same SHA-1 + # + # This means that repeated runs of the package creation tool + # on the same set of files should generated packages with the + # same SHA-1 + quoted_files.sort! do |a,b| + a <=> b + end + + quoted_file_list = quoted_files.join(" ") + + output_path = File.expand_path(output_dir) + output_file = "#{output_path}/#{package}.zip" + + File.unlink(output_file) if File.exist?(output_file) +####################################### +# Dir.chdir(input_dir) do +# puts "zip #{zip_flags} #{output_file} #{quoted_file_list}" +# +# if (!system("zip #{zip_flags} #{output_file} #{quoted_file_list}")) +# raise "Failed to generate package #{package}" +# else +# puts "Generated package #{package} : #{file_sha1(output_file)}" +# end +# end +#puts Dir.pwd #current dir +#copy_file("zip.exe", "#{File.expand_path(input_dir)}/zip.exe") +#copy_file("zlib1.dll", "#{File.expand_path(input_dir)}/zlib1.dll") +#################################### + currentDir = Dir.pwd + Dir.chdir(input_dir) do + if (!system("#{currentDir}/zip #{zip_flags} #{output_file} #{quoted_file_list}")) + raise "Failed to generate package #{package}" + else + puts "Generated package #{package} : #{file_sha1(output_file)}" + end + end +end + +# copy the updater to the output directory +puts "Using updater binary: #{updater_binary_input_path} and #{updater_dependency_input_path} " +if (!updater_binary_input_path || !updater_dependency_input_path) + puts "Updater binary not found in input directory: #{input_dir}" + exit(1) +end + +FileUtils.cp updater_binary_input_path, "#{output_dir}/#{File.basename(updater_binary_input_path)}", :preserve => true +FileUtils.cp updater_dependency_input_path, "#{output_dir}/#{File.basename(updater_dependency_input_path)}", :preserve => true + +# output the file_list.xml file +update_script = UpdateScriptGenerator.new(target_version,target_platform,input_dir, + output_dir,package_config,input_file_list, uninstall_file_list,package_file_map) +output_xml_file = "#{output_dir}/file_list.unformatted.xml" +File.open(output_xml_file,'w') do |file| + file.write update_script.to_xml() +end + +# xmllint generates more readable formatted XML than REXML, so write unformatted +# XML first and then format it with xmllint. +system("xmllint --format #{output_xml_file} > #{output_dir}/file_list.xml") +File.delete(output_xml_file) + + diff --git a/tools/tests/iconv.dll b/tools/tests/iconv.dll new file mode 100644 index 0000000..18c109d Binary files /dev/null and b/tools/tests/iconv.dll differ diff --git a/tools/tests/libbz2.dll b/tools/tests/libbz2.dll new file mode 100644 index 0000000..2a69485 Binary files /dev/null and b/tools/tests/libbz2.dll differ diff --git a/tools/tests/libxml2.dll b/tools/tests/libxml2.dll new file mode 100644 index 0000000..8171d1b Binary files /dev/null and b/tools/tests/libxml2.dll differ diff --git a/tools/tests/test_p1.bat b/tools/tests/test_p1.bat new file mode 100644 index 0000000..6c634a5 --- /dev/null +++ b/tools/tests/test_p1.bat @@ -0,0 +1 @@ +auto-create-packages.rb test_v1 test_v2 test_p1 -v 2.0.1 -o 2.0.0 \ No newline at end of file diff --git a/tools/tests/test_p2.bat b/tools/tests/test_p2.bat new file mode 100644 index 0000000..8153b74 --- /dev/null +++ b/tools/tests/test_p2.bat @@ -0,0 +1 @@ +auto-create-packages.rb test_v2 test_v3 test_p2 -v 2.0.2 -o 2.0.1 \ No newline at end of file diff --git a/tools/tests/test_p3.bat b/tools/tests/test_p3.bat new file mode 100644 index 0000000..adcd79a --- /dev/null +++ b/tools/tests/test_p3.bat @@ -0,0 +1 @@ +auto-create-packages.rb test_v3 test_v4 test_p3 -v 2.0.3 -o 2.0.2 \ No newline at end of file diff --git a/tools/tests/test_v1/test0.txt b/tools/tests/test_v1/test0.txt new file mode 100644 index 0000000..fa76637 --- /dev/null +++ b/tools/tests/test_v1/test0.txt @@ -0,0 +1 @@ +test file 0 \ No newline at end of file diff --git a/tools/tests/test_v1/test1.txt b/tools/tests/test_v1/test1.txt new file mode 100644 index 0000000..337dfff --- /dev/null +++ b/tools/tests/test_v1/test1.txt @@ -0,0 +1 @@ +test file 1 \ No newline at end of file diff --git a/tools/tests/test_v2/test0.txt b/tools/tests/test_v2/test0.txt new file mode 100644 index 0000000..fa76637 --- /dev/null +++ b/tools/tests/test_v2/test0.txt @@ -0,0 +1 @@ +test file 0 \ No newline at end of file diff --git a/tools/tests/test_v2/test1.txt b/tools/tests/test_v2/test1.txt new file mode 100644 index 0000000..337dfff --- /dev/null +++ b/tools/tests/test_v2/test1.txt @@ -0,0 +1 @@ +test file 1 \ No newline at end of file diff --git a/tools/tests/test_v2/test3.txt b/tools/tests/test_v2/test3.txt new file mode 100644 index 0000000..1fdd994 --- /dev/null +++ b/tools/tests/test_v2/test3.txt @@ -0,0 +1 @@ +test file 3 \ No newline at end of file diff --git a/tools/tests/test_v3/test0.txt b/tools/tests/test_v3/test0.txt new file mode 100644 index 0000000..fa76637 --- /dev/null +++ b/tools/tests/test_v3/test0.txt @@ -0,0 +1 @@ +test file 0 \ No newline at end of file diff --git a/tools/tests/test_v3/test1.txt b/tools/tests/test_v3/test1.txt new file mode 100644 index 0000000..337dfff --- /dev/null +++ b/tools/tests/test_v3/test1.txt @@ -0,0 +1 @@ +test file 1 \ No newline at end of file diff --git a/tools/tests/test_v3/test3.txt b/tools/tests/test_v3/test3.txt new file mode 100644 index 0000000..ef9cf10 --- /dev/null +++ b/tools/tests/test_v3/test3.txt @@ -0,0 +1 @@ +test file 3 - modified \ No newline at end of file diff --git a/tools/tests/test_v3/test4.txt b/tools/tests/test_v3/test4.txt new file mode 100644 index 0000000..947aec5 --- /dev/null +++ b/tools/tests/test_v3/test4.txt @@ -0,0 +1 @@ +test file 4 \ No newline at end of file diff --git a/tools/tests/test_v3/test5.txt b/tools/tests/test_v3/test5.txt new file mode 100644 index 0000000..90619bf --- /dev/null +++ b/tools/tests/test_v3/test5.txt @@ -0,0 +1 @@ +test file 5 \ No newline at end of file diff --git a/tools/tests/test_v4/test0.txt b/tools/tests/test_v4/test0.txt new file mode 100644 index 0000000..fa76637 --- /dev/null +++ b/tools/tests/test_v4/test0.txt @@ -0,0 +1 @@ +test file 0 \ No newline at end of file diff --git a/tools/tests/test_v4/test1.txt b/tools/tests/test_v4/test1.txt new file mode 100644 index 0000000..337dfff --- /dev/null +++ b/tools/tests/test_v4/test1.txt @@ -0,0 +1 @@ +test file 1 \ No newline at end of file diff --git a/tools/tests/test_v4/test3.txt b/tools/tests/test_v4/test3.txt new file mode 100644 index 0000000..5f6dfd5 --- /dev/null +++ b/tools/tests/test_v4/test3.txt @@ -0,0 +1 @@ +test file 3 - modified 2 \ No newline at end of file diff --git a/tools/tests/updater.exe b/tools/tests/updater.exe new file mode 100644 index 0000000..11fb4a9 Binary files /dev/null and b/tools/tests/updater.exe differ diff --git a/tools/tests/xmlcatalog.exe b/tools/tests/xmlcatalog.exe new file mode 100644 index 0000000..e256289 Binary files /dev/null and b/tools/tests/xmlcatalog.exe differ diff --git a/tools/tests/xmllint.exe b/tools/tests/xmllint.exe new file mode 100644 index 0000000..1cc18e6 Binary files /dev/null and b/tools/tests/xmllint.exe differ diff --git a/tools/tests/zip.exe b/tools/tests/zip.exe new file mode 100644 index 0000000..53bd3bc Binary files /dev/null and b/tools/tests/zip.exe differ