Skip to content

Commit

Permalink
Merge pull request #4788 from A2va/wixtoolset
Browse files Browse the repository at this point in the history
Xpack: wix toolset support
  • Loading branch information
waruqi authored May 27, 2024
2 parents d1c2095 + 1f81152 commit 1dfee59
Show file tree
Hide file tree
Showing 5 changed files with 362 additions and 2 deletions.
7 changes: 6 additions & 1 deletion tests/plugins/pack/xmake.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ target("foo")
add_packages("zlib")

xpack("test")
set_formats("nsis", "srpm", "rpm", "zip", "targz", "srczip", "srctargz", "runself")
set_formats("nsis", "srpm", "rpm", "zip", "targz", "srczip", "srctargz", "runself", "wix")
set_title("hello")
set_author("ruki")
set_description("A test installer.")
Expand Down Expand Up @@ -61,6 +61,11 @@ xpack_component("LongPath")
set_title("Enable Long Path")
set_description("Increases the maximum path length limit, up to 32,767 characters (before 256).")
on_installcmd(function (component, batchcmds)
batchcmds:rawcmd("wix", [[
<RegistryKey Root="HKLM" Key="SYSTEM\CurrentControlSet\Control\FileSystem">
<RegistryValue Type="integer" Name="LongPathsEnabled" Value="1" KeyPath="yes"/>
</RegistryKey>
]])
batchcmds:rawcmd("nsis", [[
${If} $NoAdmin == "false"
; Enable long path
Expand Down
313 changes: 313 additions & 0 deletions xmake/plugins/pack/wix/main.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
--!A cross-platform build utility based on Lua
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- Copyright (C) 2015-present, TBOOX Open Source Group.
--
-- @author A2va
-- @file main.lua
--

import("lib.detect.find_tool")
import("private.action.require.impl.packagenv")
import("private.action.require.impl.install_packages")

import(".batchcmds")

-- get the wixtoolset
function _get_wix()

-- enter the environments of wix
local oldenvs = packagenv.enter("wixtoolset")

-- find makensis
local packages = {}
local wix = find_tool("wix", {require_version = ">=4.0.0"})
if not wix then
table.join2(packages, install_packages("wixtoolset"))
end

-- enter the environments of installed packages
for _, instance in ipairs(packages) do
instance:envs_enter()
end

-- we need to force detect and flush detect cache after loading all environments
if not wix then
wix = find_tool("wix", {force = true})
end
assert(wix, "wix not found (ensure that wix is up to date)!")
return wix, oldenvs
end

-- translate the file path
function _translate_filepath(package, filepath)
return path.relative(filepath, package:install_rootdir())
end

function _to_rtf_string(str)
if str == "" then
return str
end

local escape_text = str:gsub("\\", "\\\\")
escape_text = escape_text:gsub("{", "\\{")
escape_text = escape_text:gsub("}", "\\}")

local rtf = "{\\rtf1\\ansi{\\fonttbl\\f0\\fswiss Helvetica;}\\f0\\pard ";
rtf = rtf .. escape_text:gsub("\r\n", " \\par ") .. "}"
return rtf
end

-- get a table where the key is a directory and the value a list of files
-- used to regroup all files that are placed in the same directory under the same component.
function _get_cp_kind_table(package, cmds, opt)

local result = {}
for _, cmd in ipairs(cmds) do
if cmd.kind ~= "cp" then
goto continue
end

local option = table.join(cmd.opt or {}, opt)
local srcfiles = os.files(cmd.srcpath)
for _, srcfile in ipairs(srcfiles) do
-- the destination is directory? append the filename
local dstfile = cmd.dstpath
if #srcfiles > 1 or path.islastsep(dstfile) then
if option.rootdir then
dstfile = path.join(dstfile, path.relative(srcfile, option.rootdir))
else
dstfile = path.join(dstfile, path.filename(srcfile))
end
end
srcfile = path.normalize(srcfile)
local dstname = path.filename(dstfile)
local dstdir = path.normalize(path.directory(dstfile))
dstdir = _translate_filepath(package, dstdir)

if result[dstdir] then
table.insert(result[dstdir], {srcfile, dstname})
else
result[dstdir] = {{srcfile, dstname}}
end
end
::continue::
end
return result
end

function _get_other_commands(package, cmd, opt)
opt = table.join(cmd.opt or {}, opt)
local result = ""
local kind = cmd.kind

if kind == "rm" then
local subdirectory = _translate_filepath(package, path.directory(cmd.filepath))
subdirectory = subdirectory ~= "." and string.format([[Subdirectory="%s"]], subdirectory) or ""
local on = opt.install and [[On="install"]] or [[On="uninstall"]]
local filename = path.filename(cmd.filepath)

result = string.format([[<RemoveFile Directory="INSTALLFOLDER" Name="%s" %s %s/>]], filename, subdirectory, on)
elseif kind == "rmdir" then
local dir = _translate_filepath(package, cmd.dir)
local subdirectory = dir ~= "." and string.format([[Subdirectory="%s"]], dir) or ""
local on = opt.install and [[On="install"]] or [[On="uninstall"]]

result = string.format([[<RemoveFolder Directory="INSTALLFOLDER" %s %s/>]], subdirectory, on)
elseif kind == "mkdir" then
local dir = _translate_filepath(package, cmd.dir)
local subdirectory = dir ~= "." and string.format([[Subdirectory="%s"]], dir) or ""
result = string.format([[<CreateFolder Directory="INSTALLFOLDER" %s/>]], subdirectory)
elseif kind == "wix" then
result = cmd.rawstr
end
return result
end

-- get the string of a wix feature
function _get_feature_string(name, title, opt)
local level = opt.default and 1 or 2
local description = opt.description or ""
local allow_absent = opt.force and "false" or "true"
local allow_advertise = opt.force and "false" or "true"
local typical_default = [[TypicalDefault="install"]]
local directory = opt.config_dir and [[ConfigurableDirectory="INSTALLFOLDER"]] or ""
local feature = string.format([[<Feature Id="%s" Title="%s" Description="%s" Level="%d" AllowAdvertise="%s" AllowAbsent="%s" %s %s>]], name:gsub(" ", ""), title, description, level, allow_advertise, allow_absent, typical_default, directory)
return feature
end

function _get_component_string(id, subdirectory)
local subdirectory = (subdirectory ~= "." and subdirectory ~= nil) and string.format([[Subdirectory="%s"]], subdirectory) or ""
return string.format([[<Component Id="%s" Guid="%s" Directory="INSTALLFOLDER" %s>]], id:gsub(" ", ""), hash.uuid(id), subdirectory)
end

-- for each id/guid in the file wix want them to be unique
-- so compute a hash for each directory based on the file that are inside
function _get_dir_id(cp_table)
local hashes = {}
for dir, files in pairs(cp_table) do
local s = ""
for _, file in ipairs(files) do
s = s .. table.concat(file, "")
end
-- wix required id to start with a letter and without any hyphen
hashes[dir] = "A".. hash.uuid(s):gsub("-", ".")
end
return hashes
end

-- build a feature from batchcmds
function _build_feature(package, opt)
opt = opt or {}
local default = opt.default or package:get("default")

local result = {}
local name = opt.name or package:title()
table.insert(result, _get_feature_string(name, package:title(), table.join(opt, {default = default, description = package:description()})))

local installcmds = batchcmds.get_installcmds(package):cmds()
local uninstallcmds = batchcmds.get_uninstallcmds(package):cmds()

local cp_table = _get_cp_kind_table(package, installcmds, opt)
table.remove_if(installcmds, function (_, cmd) return cmd.kind == "cp" end)

local dir_id = _get_dir_id(cp_table)

for dir, files in pairs(cp_table) do
table.insert(result, _get_component_string(dir_id[dir], dir))
for _, file in ipairs(files) do
local srcfile = file[1]
local dstname = file[2]
table.insert(result, string.format([[<File Source="%s" Name="%s"/>]], srcfile, dstname))
end
table.insert(result, "</Component>")
end

table.insert(result, _get_component_string(name.. "Cmds"))
for _, cmd in ipairs(installcmds) do
table.insert(result, _get_other_commands(package, cmd, {install = true}))
end
for _, cmd in ipairs(uninstallcmds) do
table.insert(result, _get_other_commands(package, cmd, {install = false}))
end

table.insert(result, "</Component>")
table.insert(result, "</Feature>")
return result
end

-- add to path feature
function _add_to_path(package)
local result = {}
table.insert(result, _get_feature_string("PATH", "Add to PATH", {default = false, force = false, description = "Add to PATH"}))
table.insert(result, _get_component_string("PATH"))
table.insert(result, [[<Environment Id="PATH" Name="PATH" Value="[INSTALLFOLDER]bin" Permanent="false" Part="last" Action="set" System="true" />]])
table.insert(result, "</Component>")
table.insert(result, "</Feature>")
return result
end

-- get specvars
function _get_specvars(package)

local installcmds = batchcmds.get_installcmds(package):cmds()
local specvars = table.clone(package:specvars())

local features = {}
table.join2(features, _build_feature(package, {default = true, force = true, config_dir = true}))
table.join2(features, _add_to_path(package))

for name, component in table.orderpairs(package:components()) do
table.join2(features, _build_feature(component, {name = "Install " .. name}))
end

specvars.PACKAGE_LICENSEFILE = function ()
local rtf_string = ""
local licensefile = package:get("licensefile")
if licensefile then
rtf_string = _to_rtf_string(io.readfile(licensefile))
end

local rtf_file = path.join(package:buildir(), "license.rtf")
io.writefile(rtf_file, rtf_string)
return rtf_file
end

specvars.PACKAGE_WIX_CMDS = table.concat(features, "\n ")
specvars.PACKAGE_WIX_UPGRADECODE = hash.uuid(package:name())

-- company cannot be empty with wix
if package:get("company") == nil or package:get("company") == "" then
specvars.PACKAGE_COMPANY = package:name()
end
return specvars
end

function _pack_wix(wix, package)

-- install the initial specfile
local specfile = package:specfile()
if not os.isfile(specfile) then
local specfile_template = path.join(os.programdir(), "scripts", "xpack", "wix", "msi.wxs")
os.cp(specfile_template, specfile)
end

-- replace variables in specfile
local specvars = _get_specvars(package)
local pattern = package:extraconf("specfile", "pattern") or "%${([^\n]-)}"
io.gsub(specfile, "(" .. pattern .. ")", function(_, name)
name = name:trim()
local value = specvars[name]
if type(value) == "function" then
value = value()
end
if value ~= nil then
dprint(" > replace %s -> %s", name, value)
end
if type(value) == "table" then
dprint("invalid variable value", value)
end
return value
end)

local argv = {"build", specfile}
table.join2(argv, {"-ext", "WixToolset.UI.wixext"})
table.join2(argv, {"-o", package:outputfile()})

if package:arch() == "x64" then
table.join2(argv, {"-arch", "x64"})
elseif package:arch() == "x86" then
table.join2(argv, {"-arch", "x86"})
end

-- make package
os.vrunv(wix, argv)
end

function main(package)
-- only for windows
if not is_host("windows") then
return
end

cprint("packing %s", package:outputfile())
-- get wix
local wix, oldenvs = _get_wix()

-- pack nsis package
_pack_wix(wix.program, package)

-- done
os.setenvs(oldenvs)
end
2 changes: 1 addition & 1 deletion xmake/plugins/pack/xmake.lua
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ task("pack")
"e.g.",
" - xmake pack -f nsis,deb,rpm",
"values:",
values = {"nsis", "deb", "srpm", "rpm", "runself", "targz", "zip", "srctargz", "srczip"}},
values = {"nsis", "wix", "deb", "srpm", "rpm", "runself", "targz", "zip", "srctargz", "srczip"}},
{},
{nil, "packages", "vs", nil, "The package names."}
}
Expand Down
3 changes: 3 additions & 0 deletions xmake/plugins/pack/xpack.lua
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ function xpack:inputkind()
local inputkind = self:get("inputkind")
if inputkind == nil then
local inputkinds = {
wix = "binary",
nsis = "binary",
zip = "binary",
targz = "binary",
Expand Down Expand Up @@ -321,6 +322,7 @@ end
-- get the specfile path
function xpack:specfile()
local extensions = {
wix = ".wxs",
nsis = ".nsi",
srpm = ".spec",
rpm = ".spec",
Expand All @@ -335,6 +337,7 @@ function xpack:extension()
local extension = self:get("extension")
if extension == nil then
local extensions = {
wix = ".msi",
nsis = ".exe",
zip = ".zip",
targz = ".tar.gz",
Expand Down
Loading

0 comments on commit 1dfee59

Please sign in to comment.