Commit 0286e3d8 authored by Seth Chisamore's avatar Seth Chisamore

Merge pull request #565 from chef/salam/fastmsi-redux

Add fastmsi infra to omnibus
parents a6e003e0 10479b41
......@@ -2,6 +2,7 @@ Omnibus CHANGELOG
=================
## Unreleased
- Add description to signed package (Windows) so UAC prompt shows correct package name
- Add new Windows 'Fast MSI' installer option to the msi.rb packager
v4.1.0 (September 1, 2015)
-------------------------
......
......@@ -47,48 +47,29 @@ module Omnibus
FileSyncer.glob("#{resources_path}/assets/*").each do |file|
copy_file(file, "#{resources_dir}/assets/#{File.basename(file)}")
end
# Source for the custom action is at https://github.com/chef/fastmsi-custom-action
# The dll will be built separately as part of the custom action build process
# and made available as a binary for the Omnibus projects to use.
copy_file(resource_path('CustomActionFastMsi.CA.dll'), staging_dir) if fast_msi
end
build do
# If fastmsi, zip up the contents of the install directory
shellout!(zip_command) if fast_msi
# Harvest the files with heat.exe, recursively generate fragment for
# project directory
Dir.chdir(staging_dir) do
shellout! <<-EOH.split.join(' ').squeeze(' ').strip
heat.exe dir "#{windows_safe_path(project.install_dir)}"
-nologo -srd -sreg -gg -cg ProjectDir
-dr PROJECTLOCATION
-var "var.ProjectSourceDir"
-out "project-files.wxs"
EOH
shellout!(heat_command)
# Compile with candle.exe
log.debug(log_key) { "wix_candle_flags: #{wix_candle_flags}" }
shellout! <<-EOH.split.join(' ').squeeze(' ').strip
candle.exe
-nologo
#{wix_candle_flags}
#{wix_extension_switches(wix_candle_extensions)}
-dProjectSourceDir="#{windows_safe_path(project.install_dir)}" "project-files.wxs"
"#{windows_safe_path(staging_dir, 'source.wxs')}"
EOH
shellout!(candle_command)
# Create the msi, ignoring the 204 return code from light.exe since it is
# about some expected warnings
msi_file = windows_safe_path(Config.package_dir, msi_name)
light_command = <<-EOH.split.join(' ').squeeze(' ').strip
light.exe
-nologo
-ext WixUIExtension
#{wix_extension_switches(wix_light_extensions)}
-cultures:en-us
-loc "#{windows_safe_path(staging_dir, 'localization-en-us.wxl')}"
project-files.wixobj source.wixobj
-out "#{msi_file}"
EOH
shellout!(light_command, returns: [0, 204])
shellout!(light_command(msi_file), returns: [0, 204])
if signing_identity
sign_package(msi_file)
......@@ -96,32 +77,11 @@ module Omnibus
# This assumes, rightly or wrongly, that any installers we want to bundle
# into our installer will be downloaded by omnibus and put in the cache dir
if bundle_msi
shellout! <<-EOH.split.join(' ').squeeze(' ').strip
candle.exe
-nologo
#{wix_candle_flags}
-ext WixBalExtension
#{wix_extension_switches(wix_candle_extensions)}
-dOmnibusCacheDir="#{windows_safe_path(File.expand_path(Config.cache_dir))}"
"#{windows_safe_path(staging_dir, 'bundle.wxs')}"
EOH
shellout!(candle_command(is_bundle: true))
bundle_file = windows_safe_path(Config.package_dir, bundle_name)
bundle_light_command = <<-EOH.split.join(' ').squeeze(' ').strip
light.exe
-nologo
-ext WixUIExtension
-ext WixBalExtension
#{wix_extension_switches(wix_light_extensions)}
-cultures:en-us
-loc "#{windows_safe_path(staging_dir, 'localization-en-us.wxl')}"
bundle.wixobj
-out "#{bundle_file}"
EOH
shellout!(bundle_light_command, returns: [0, 204])
shellout!(light_command(bundle_file, is_bundle: true), returns: [0, 204])
if signing_identity
sign_package(bundle_file)
......@@ -151,7 +111,7 @@ module Omnibus
@upgrade_code || raise(MissingRequiredAttribute.new(self, :upgrade_code, '2CD7259C-776D-4DDB-A4C8-6E544E580AA1'))
else
unless val.is_a?(String)
raise InvalidValue.new(:parameters, 'be a String')
raise InvalidValue.new(:upgrade_code, 'be a String')
end
@upgrade_code = val
......@@ -247,6 +207,25 @@ module Omnibus
end
expose :bundle_msi
#
# Signal that we're building a zip-based MSI
#
# @example
# fast_msi true
#
# @param [TrueClass, FalseClass] value
# whether we're building a zip-based MSI or not
#
# @return [TrueClass, FalseClass]
# whether we're building a zip-based MSI or not
def fast_msi(val = false)
unless (val.is_a?(TrueClass) || val.is_a?(FalseClass))
raise InvalidValue.new(:fast_msi, 'be TrueClass or FalseClass')
end
@fast_msi ||= val
end
expose :fast_msi
#
# Set the signing certificate name
#
......@@ -448,7 +427,7 @@ module Omnibus
friendly_name: project.friendly_name,
maintainer: project.maintainer,
hierarchy: hierarchy,
fastmsi: fast_msi,
wix_install_dir: wix_install_dir,
}
)
......@@ -495,6 +474,106 @@ module Omnibus
"#{versions[0]}.#{versions[1]}.#{versions[2]}.#{project.build_iteration}"
end
#
# Get the shell command to create a zip file that contains
# the contents of the project install directory
#
# @return [String]
#
def zip_command
<<-EOH.split.join(' ').squeeze(' ').strip
7z a -r
#{windows_safe_path(staging_dir)}\\#{project.name}.zip
#{windows_safe_path(project.install_dir)}\\*
EOH
end
#
# Get the shell command to run heat in order to create a
# a WIX manifest of project files to be packaged into the MSI
#
# @return [String]
#
def heat_command
if fast_msi
<<-EOH.split.join(' ').squeeze(' ').strip
heat.exe file "#{project.name}.zip"
-cg ProjectDir
-dr INSTALLLOCATION
-nologo -sfrag -srd -sreg -gg
-out "project-files.wxs"
EOH
else
<<-EOH.split.join(' ').squeeze(' ').strip
heat.exe dir "#{windows_safe_path(project.install_dir)}"
-nologo -srd -sreg -gg -cg ProjectDir
-dr PROJECTLOCATION
-var "var.ProjectSourceDir"
-out "project-files.wxs"
EOH
end
end
#
# Get the shell command to complie the project WIX files
#
# @return [String]
#
def candle_command(is_bundle: false)
if is_bundle
<<-EOH.split.join(' ').squeeze(' ').strip
candle.exe
-nologo
#{wix_candle_flags}
-ext WixBalExtension
#{wix_extension_switches(wix_candle_extensions)}
-dOmnibusCacheDir="#{windows_safe_path(File.expand_path(Config.cache_dir))}"
"#{windows_safe_path(staging_dir, 'bundle.wxs')}"
EOH
else
<<-EOH.split.join(' ').squeeze(' ').strip
candle.exe
-nologo
#{wix_candle_flags}
#{wix_extension_switches(wix_candle_extensions)}
-dProjectSourceDir="#{windows_safe_path(project.install_dir)}" "project-files.wxs"
"#{windows_safe_path(staging_dir, 'source.wxs')}"
EOH
end
end
#
# Get the shell command to link the project WIX object files
#
# @return [String]
#
def light_command(out_file, is_bundle: false)
if is_bundle
<<-EOH.split.join(' ').squeeze(' ').strip
light.exe
-nologo
-ext WixUIExtension
-ext WixBalExtension
#{wix_extension_switches(wix_light_extensions)}
-cultures:en-us
-loc "#{windows_safe_path(staging_dir, 'localization-en-us.wxl')}"
bundle.wixobj
-out "#{out_file}"
EOH
else
<<-EOH.split.join(' ').squeeze(' ').strip
light.exe
-nologo
-ext WixUIExtension
#{wix_extension_switches(wix_light_extensions)}
-cultures:en-us
-loc "#{windows_safe_path(staging_dir, 'localization-en-us.wxl')}"
project-files.wixobj source.wixobj
-out "#{out_file}"
EOH
end
end
#
# The display version calculated from the {Project#build_version}.
#
......
......@@ -16,4 +16,8 @@
<String Id="VerifyReadyDlgInstallTitle">{\WixUI_Font_Title_White}Ready to install [ProductName]</String>
<String Id="FeatureMainName"><%= friendly_name %></String>
<String Id="MinimumOSVersionMessage">This package requires minimum OS version: Windows 7/Windows Server 2008 R2 or greater.</String>
<String Id="DowngradeErrorMessage">A newer version of [ProductName] is already installed.</String>
<String Id="FileExtractionProgress">Extracting files, please wait...</String>
</WixLocalization>
......@@ -12,23 +12,60 @@
Version="$(var.VersionNumber)" Manufacturer="!(loc.ManufacturerName)" UpgradeCode="$(var.UpgradeCode)">
<!--
Define the minimum supported installer version (2.0).
The install should be done for the whole machine not just the current user
Minimum installer version (2.0) - Window XP and above.
The install scope is per machine, not the current user
-->
<Package InstallerVersion="200" InstallPrivileges="elevated" Compressed="yes" InstallScope="perMachine" />
<Package InstallerVersion="200" InstallPrivileges="elevated"
Compressed="yes" InstallScope="perMachine" />
<Media Id="1" Cabinet="Project.cab" EmbedCab="yes" CompressionLevel="high" />
<!-- Major upgrade -->
<Upgrade Id="$(var.UpgradeCode)">
<UpgradeVersion OnlyDetect="yes" Minimum="$(var.VersionNumber)" IncludeMinimum="no" Property="NEWERVERSIONDETECTED" />
<UpgradeVersion Minimum="0.0.0.0" IncludeMinimum="yes" Maximum="$(var.VersionNumber)" IncludeMaximum="yes" Property="OLDERVERSIONBEINGUPGRADED" />
</Upgrade>
<!--
Uncomment launch condition below to check for minimum OS
601 = Windows 7/Server 2008R2.
-->
<!-- Condition Message="!(loc.MinimumOSVersionMessage)">
<![CDATA[Installed OR VersionNT >= 601]]>
</Condition -->
<!-- We always do Major upgrades -->
<MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeErrorMessage)" />
<!--
If fastmsi is set, custom actions will be invoked during install to unzip
project files, and during uninstall to remove the project folder
-->
<% if fastmsi %>
<SetProperty Id="FastUnzip"
Value="FASTZIPDIR=[INSTALLLOCATION];FASTZIPAPPNAME=chefdk"
Sequence="execute"
Before="FastUnzip" />
<CustomAction Id="FastUnzip"
BinaryKey="CustomActionFastMsiDLL"
DllEntry="FastUnzip"
Execute="deferred"
Return="check" />
<Binary Id="CustomActionFastMsiDLL"
SourceFile="CustomActionFastMsi.CA.dll" />
<CustomAction Id="Cleanup"
Directory="INSTALLLOCATION"
ExeCommand="cmd /C &quot;rd /S /Q chefdk&quot;"
Execute="deferred"
Return="ignore" />
<InstallExecuteSequence>
<RemoveExistingProducts After="InstallValidate" />
<Custom Action="FastUnzip" After="InstallFiles">NOT Installed</Custom>
<Custom Action="Cleanup" After="RemoveFiles">REMOVE~="ALL"</Custom>
</InstallExecuteSequence>
<UI>
<ProgressText Action="FastUnzip">!(loc.FileExtractionProgress)</ProgressText>
</UI>
<% end %>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="WINDOWSVOLUME">
<% hierarchy.each.with_index do |(name, id), index| -%>
......
......@@ -180,6 +180,26 @@ module Omnibus
expect(contents).to include('<?include "parameters.wxi" ?>')
expect(contents).to include('<Property Id="WIXUI_INSTALLDIR" Value="WINDOWSVOLUME" />')
end
context 'when fastmsi is not specified' do
it 'does not include a reference to the fast msi custom action' do
subject.write_source_file
contents = File.read("#{staging_dir}/source.wxs")
expect(contents).not_to include("<Binary Id=\"CustomActionFastMsiDLL\"")
end
end
context 'when fastmsi is specified' do
before do
subject.fast_msi(true)
end
it 'includes a reference to the fast msi custom action' do
subject.write_source_file
contents = File.read("#{staging_dir}/source.wxs")
expect(contents).to include("<Binary Id=\"CustomActionFastMsiDLL\"")
end
end
end
describe '#write_bundle_file' do
......@@ -316,6 +336,123 @@ module Omnibus
end
end
describe '#fast_msi' do
it 'is a DSL method' do
expect(subject).to have_exposed_method(:fast_msi)
end
it 'requires the value to be a TrueClass or a FalseClass' do
expect {
subject.fast_msi(Object.new)
}.to raise_error(InvalidValue)
end
it 'returns the given value' do
subject.fast_msi(true)
expect(subject.fast_msi).to be_truthy
end
end
describe '#zip_command' do
it 'returns a String' do
expect(subject.zip_command).to be_a(String)
end
it 'sets zip file location to the staging directory' do
expect(subject.zip_command).to include("#{subject.windows_safe_path(staging_dir)}\\#{project.name}.zip")
end
end
describe '#candle_command' do
it 'returns a String' do
expect(subject.candle_command).to be_a(String)
end
context 'default behavior' do
it 'defines the ProjectSourceDir property' do
expect(subject.candle_command).to include("-dProjectSourceDir=")
end
it 'outputs a source.wxs file to the staging directory' do
expect(subject.candle_command).to include("#{subject.windows_safe_path(staging_dir, 'source.wxs')}")
end
end
context 'when is_bundle is true' do
it 'uses the WIX Bootstrapper/Burn extension' do
expect(subject.candle_command(is_bundle: true)).to include("-ext WixBalExtension")
end
it 'defines the OmnibusCacheDir property' do
expect(subject.candle_command(is_bundle: true)).to include("-dOmnibusCacheDir=")
end
it 'outputs a bundle.wxs file to the staging directory' do
expect(subject.candle_command(is_bundle: true)).to include("#{subject.windows_safe_path(staging_dir, 'bundle.wxs')}")
end
end
end
describe '#heat_command' do
it 'returns a String' do
expect(subject.heat_command).to be_a(String)
end
context 'when fast_msi is not set' do
it 'operates in directory mode' do
expect(subject.heat_command).to include("dir \"#{subject.windows_safe_path(project.install_dir)}\"")
end
it 'sets destination to the project location' do
expect(subject.heat_command).to include("-dr PROJECTLOCATION")
end
end
context 'when fast_msi is set' do
before do
subject.fast_msi(true)
end
it 'operates in file mode' do
expect(subject.heat_command).to include("file \"#{project.name}.zip\"")
end
it 'sets destination to the install location' do
expect(subject.heat_command).to include("-dr INSTALLLOCATION")
end
end
end
describe '#light_command' do
it 'returns a String' do
expect(subject.light_command("foo")).to be_a(String)
end
context 'default behavior' do
let (:command) { subject.light_command("foo") }
it 'uses the WIX UI extension' do
expect(command).to include("-ext WixUIExtension")
end
it 'includes the project-files and source wixobj files' do
expect(command).to include("project-files.wixobj source.wixobj")
end
end
context 'when is_bundle is true' do
let (:command) { subject.light_command("foo", is_bundle: true) }
it 'uses the WIX Bootstrapper/Burn extension' do
expect(command).to include("-ext WixBalExtension")
end
it 'includes the bundle wixobj file' do
expect(command).to include("bundle.wixobj")
end
end
end
describe '#gem_path' do
let(:install_dir) { File.join(tmp_path, 'install_dir') }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment