diff options
| author | ruki <[email protected]> | 2024-05-27 22:27:12 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-05-27 22:27:12 +0800 |
| commit | 1dfee59408ad9b09feeaaa72fbc4d66fbfd4486e (patch) | |
| tree | 7a8176d6943b49b00eb14e92703ce550f3e0b6d8 /xmake | |
| parent | d1c2095855bcf6633aeffac98c1b03bca0978f8a (diff) | |
| parent | 1f81152c5bc78bab1cc460e8b3fcba174312a22b (diff) | |
Merge pull request #4788 from A2va/wixtoolset
Xpack: wix toolset support
Diffstat (limited to 'xmake')
| -rw-r--r-- | xmake/plugins/pack/wix/main.lua | 313 | ||||
| -rw-r--r-- | xmake/plugins/pack/xmake.lua | 2 | ||||
| -rw-r--r-- | xmake/plugins/pack/xpack.lua | 3 | ||||
| -rw-r--r-- | xmake/scripts/xpack/wix/msi.wxs | 39 |
4 files changed, 356 insertions, 1 deletions
diff --git a/xmake/plugins/pack/wix/main.lua b/xmake/plugins/pack/wix/main.lua new file mode 100644 index 000000000..8fce6a2dc --- /dev/null +++ b/xmake/plugins/pack/wix/main.lua @@ -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
\ No newline at end of file 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..ab48a99e6 100644 --- a/xmake/plugins/pack/xpack.lua +++ b/xmake/plugins/pack/xpack.lua @@ -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", @@ -321,6 +322,7 @@ end -- get the specfile path function xpack:specfile() local extensions = { + wix = ".wxs", nsis = ".nsi", srpm = ".spec", rpm = ".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..dda4978a7 --- /dev/null +++ b/xmake/scripts/xpack/wix/msi.wxs @@ -0,0 +1,39 @@ +<!-- 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="CompanyFolder" Name="$(PackageCompany)"> + <Directory Id="INSTALLFOLDER" Name="$(PackageName)"/> + </Directory> + </StandardDirectory> + ${PACKAGE_WIX_CMDS} + </Package> +</Wix>
\ No newline at end of file |
