summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2024-05-28 10:03:02 +0800
committerGitHub <[email protected]>2024-05-28 10:03:02 +0800
commit6975cf5ff7c120bd537211ea167c157b45780d0a (patch)
tree22f1aa0d71a7a3d0a23f78756667c90d3cd1d879
parent707bfc36f9b4a14c42bde2cb115d86b33218bcc9 (diff)
parenteb4ad9b4c028807d8bfddff1a7834c0069ba49b3 (diff)
Merge pull request #5153 from xmake-io/wix
Xpack: wix toolset support
-rw-r--r--core/xpack.lua9
-rw-r--r--tests/plugins/pack/xmake.lua7
-rw-r--r--xmake/core/package/package.lua1
-rw-r--r--xmake/modules/private/action/require/impl/packagenv.lua12
-rw-r--r--xmake/plugins/pack/wix/main.lua316
-rw-r--r--xmake/plugins/pack/xmake.lua2
-rw-r--r--xmake/plugins/pack/xpack.lua25
-rw-r--r--xmake/scripts/xpack/wix/msi.wxs37
8 files changed, 390 insertions, 19 deletions
diff --git a/core/xpack.lua b/core/xpack.lua
index 4262ef1b5..1a204e6b6 100644
--- a/core/xpack.lua
+++ b/core/xpack.lua
@@ -5,7 +5,7 @@ xpack("xmake")
set_copyright("Copyright (C) 2015-present, TBOOX Open Source Group")
set_author("[email protected]")
set_licensefile("../LICENSE.md")
- set_formats("nsis", "zip")
+ set_formats("nsis", "wix", "zip")
add_targets("demo")
set_bindir(".")
set_iconfile("src/demo/xmake.ico")
@@ -33,7 +33,7 @@ xpack("xmake")
import("utils.archive")
import("core.base.global")
local format = package:format()
- if package:is_plat("windows") and (format == "nsis" or format == "zip") then
+ if package:is_plat("windows") and (format == "nsis" or format == "wix" or format == "zip") then
local winenv = path.join(os.programdir(), "winenv")
if os.isdir(winenv) then
package:add("installfiles", path.join(winenv, "**"), {rootdir = path.directory(winenv)})
@@ -69,6 +69,11 @@ xpack_component("LongPath")
; Enable long path
WriteRegDWORD ${HKLM} "SYSTEM\CurrentControlSet\Control\FileSystem" "LongPathsEnabled" 1
${EndIf}]])
+ batchcmds:rawcmd("wix", [[
+ <RegistryKey Root="HKLM" Key="SYSTEM\CurrentControlSet\Control\FileSystem">
+ <RegistryValue Type="integer" Name="LongPathsEnabled" Value="1" KeyPath="yes"/>
+ </RegistryKey>
+ ]])
end)
xpack("xmakesrc")
diff --git a/tests/plugins/pack/xmake.lua b/tests/plugins/pack/xmake.lua
index 1ce179b33..437ae8a37 100644
--- a/tests/plugins/pack/xmake.lua
+++ b/tests/plugins/pack/xmake.lua
@@ -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.")
@@ -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
diff --git a/xmake/core/package/package.lua b/xmake/core/package/package.lua
index 00c89f241..c65f72afe 100644
--- a/xmake/core/package/package.lua
+++ b/xmake/core/package/package.lua
@@ -889,6 +889,7 @@ function _instance:manifest_save()
manifest.mode = self:mode()
manifest.configs = self:configs()
manifest.envs = self:_rawenvs()
+ manifest.pathenvs = self:_pathenvs():to_array()
-- save enabled library deps
if self:librarydeps() then
diff --git a/xmake/modules/private/action/require/impl/packagenv.lua b/xmake/modules/private/action/require/impl/packagenv.lua
index 06a70df91..01eb20d2a 100644
--- a/xmake/modules/private/action/require/impl/packagenv.lua
+++ b/xmake/modules/private/action/require/impl/packagenv.lua
@@ -19,17 +19,21 @@
--
-- imports
+import("core.base.hashset")
import("core.package.package", {alias = "core_package"})
-- enter the package environments
-function _enter_package(package_name, envs, installdir)
+function _enter_package(package_name, envs, pathenvs, installdir)
+ if pathenvs then
+ pathenvs = hashset.from(pathenvs)
+ end
for name, values in pairs(envs) do
- if name == "PATH" or name == "LD_LIBRARY_PATH" or name == "DYLD_LIBRARY_PATH" then
+ if pathenvs and pathenvs:has(name) then
for _, value in ipairs(values) do
if path.is_absolute(value) then
os.addenv(name, value)
else
- os.addenv(name, path.join(installdir, value))
+ os.addenv(name, path.normalize(path.join(installdir, value)))
end
end
else
@@ -45,7 +49,7 @@ function enter(...)
for _, manifest_file in ipairs(os.files(path.join(core_package.installdir(), name:sub(1, 1), name, "*", "*", "manifest.txt"))) do
local manifest = io.load(manifest_file)
if manifest and manifest.plat == os.host() and manifest.arch == os.arch() then
- _enter_package(name, manifest.envs, path.directory(manifest_file))
+ _enter_package(name, manifest.envs, manifest.pathenvs, path.directory(manifest_file))
end
end
end
diff --git a/xmake/plugins/pack/wix/main.lua b/xmake/plugins/pack/wix/main.lua
new file mode 100644
index 000000000..95b6a4a7f
--- /dev/null
+++ b/xmake/plugins/pack/wix/main.lua
@@ -0,0 +1,316 @@
+--!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")
+ 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
+
+-- get id
+function _get_id(name)
+ return "A" .. hash.uuid(name):gsub("-", ".")
+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] = _get_id(s)
+ end
+ return hashes
+end
+
+-- get custom commands
+function _get_other_commands(package, cmd, opt)
+ opt = table.join(cmd.opt or {}, opt)
+ local result = ""
+ local kind = cmd.kind
+ local id = _get_id()
+ 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 Id="%s" Directory="INSTALLFOLDER" Name="%s" %s %s/>]], id, 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 Id="%s" Directory="INSTALLFOLDER" %s %s/>]], id, 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 Id="%s" Directory="INSTALLFOLDER" %s/>]], id, 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
+
+-- 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" Id="%s"/>]], srcfile, dstname, _get_id()))
+ 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
diff --git a/xmake/plugins/pack/xmake.lua b/xmake/plugins/pack/xmake.lua
index 0afa83cff..fa4408d78 100644
--- a/xmake/plugins/pack/xmake.lua
+++ b/xmake/plugins/pack/xmake.lua
@@ -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."}
}
diff --git a/xmake/plugins/pack/xpack.lua b/xmake/plugins/pack/xpack.lua
index 0afc62868..8af36cdd9 100644
--- a/xmake/plugins/pack/xpack.lua
+++ b/xmake/plugins/pack/xpack.lua
@@ -190,15 +190,16 @@ function xpack:inputkind()
local inputkind = self:get("inputkind")
if inputkind == nil then
local inputkinds = {
- nsis = "binary",
- zip = "binary",
- targz = "binary",
- srczip = "source",
+ wix = "binary",
+ nsis = "binary",
+ zip = "binary",
+ targz = "binary",
+ srczip = "source",
srctargz = "source",
- runself = "source",
- deb = "source",
- srpm = "source",
- rpm = "source"
+ runself = "source",
+ deb = "source",
+ srpm = "source",
+ rpm = "source"
}
inputkind = inputkinds[self:format()] or "binary"
end
@@ -321,9 +322,10 @@ end
-- get the specfile path
function xpack:specfile()
local extensions = {
- nsis = ".nsi",
- srpm = ".spec",
- rpm = ".spec",
+ wix = ".wxs",
+ nsis = ".nsi",
+ srpm = ".spec",
+ rpm = ".spec",
runself = ".lsm"
}
local extension = extensions[self:format()] or ".spec"
@@ -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",
diff --git a/xmake/scripts/xpack/wix/msi.wxs b/xmake/scripts/xpack/wix/msi.wxs
new file mode 100644
index 000000000..f907ee33e
--- /dev/null
+++ b/xmake/scripts/xpack/wix/msi.wxs
@@ -0,0 +1,37 @@
+<!-- this is a WIX spec file -->
+<!-- it is autogenerated by the xmake build system. -->
+<!-- do not edit by hand. -->
+<?define PackageName = "${PACKAGE_NAME}"?>
+<?define PackageTitle = "${PACKAGE_TITLE}"?>
+<?define PackageDescription = "${PACKAGE_DESCRIPTION}"?>
+<?define PackageAuthor = "${PACKAGE_AUTHOR}"?>
+<?define PackageMaintainer = "${PACKAGE_MAINTAINER}"?>
+<?define PackageHomepage = "${PACKAGE_HOMEPAGE}"?>
+<?define PackageCoypright = "${PACKAGE_COPYRIGHT}"?>
+<?define PackageCompany = "${PACKAGE_COMPANY}"?>
+<?define PackageIconFile = "${PACKAGE_ICONFILE}"?>
+<?define PackageLicense = "${PACKAGE_LICENSE}"?>
+<?define PackageLicenseFile = "${PACKAGE_LICENSEFILE}"?>
+<?define PackageVersion = "${PACKAGE_VERSION}"?>
+
+<?define UpgradeCode = "${PACKAGE_WIX_UPGRADECODE}"?>
+
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui">
+ <Package Language="1033" Manufacturer="$(PackageCompany)" Name="$(PackageName)" UpgradeCode="$(UpgradeCode)" Version="$(PackageVersion)">
+ <MajorUpgrade DowngradeErrorMessage="A later version of $(PackageName) is already installed. Setup will now exit." />
+ <?if $(PackageIconFile) != "" ?>
+ <Icon Id="Icon" SourceFile="$(PackageIconFile)"/>
+ <Property Id="ARPPRODUCTICON" Value="Icon" />
+ <WixVariable Id="WixUIBannerBmp" Value="$(PackageIconFile)" />
+ <!-- Disable for now, because this is not pretty if the icon has a small resolution -->
+ <!-- <WixVariable Id="WixUIDialogBmp" Value="$(PackageIconFile)" /> -->
+ <?endif ?>
+ <MediaTemplate EmbedCab="true" />
+ <WixVariable Id="WixUILicenseRtf" Value="$(PackageLicenseFile)" />
+ <ui:WixUI Id="WixUI_FeatureTree" InstallDirectory="INSTALLFOLDER" />
+ <StandardDirectory Id="ProgramFiles6432Folder">
+ <Directory Id="INSTALLFOLDER" Name="$(PackageName)"/>
+ </StandardDirectory>
+ ${PACKAGE_WIX_CMDS}
+ </Package>
+</Wix>