diff options
| author | ruki <[email protected]> | 2023-11-15 16:34:08 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-11-15 16:34:08 +0800 |
| commit | 6677fd77ee78b53a1f9bd4ce9665ce8b60083d47 (patch) | |
| tree | 0094dfb752ac8e8816973f0db25b328c844d550f | |
| parent | 4b102cbd2221494395e6e279ed453eee81ba45a4 (diff) | |
| parent | 7c37e9b9b92b53e4384daea5193f826bf8ac9492 (diff) | |
Merge pull request #4373 from xmake-io/xpack
Support `xmake pack`
28 files changed, 2093 insertions, 18 deletions
diff --git a/tests/plugins/pack/.gitignore b/tests/plugins/pack/.gitignore new file mode 100644 index 000000000..152105761 --- /dev/null +++ b/tests/plugins/pack/.gitignore @@ -0,0 +1,8 @@ +# Xmake cache +.xmake/ +build/ + +# MacOS Cache +.DS_Store + + diff --git a/tests/plugins/pack/LICENSE.md b/tests/plugins/pack/LICENSE.md new file mode 100644 index 000000000..d2b373af7 --- /dev/null +++ b/tests/plugins/pack/LICENSE.md @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015-present TBOOX Open Source Group + + 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. + diff --git a/tests/plugins/pack/include/foo/foo.h b/tests/plugins/pack/include/foo/foo.h new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/plugins/pack/include/foo/foo.h diff --git a/tests/plugins/pack/include/test.h b/tests/plugins/pack/include/test.h new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/plugins/pack/include/test.h diff --git a/tests/plugins/pack/src/assets/file1.txt b/tests/plugins/pack/src/assets/file1.txt new file mode 100644 index 000000000..ce0136250 --- /dev/null +++ b/tests/plugins/pack/src/assets/file1.txt @@ -0,0 +1 @@ +hello diff --git a/tests/plugins/pack/src/assets/file2.txt b/tests/plugins/pack/src/assets/file2.txt new file mode 100644 index 000000000..ce0136250 --- /dev/null +++ b/tests/plugins/pack/src/assets/file2.txt @@ -0,0 +1 @@ +hello diff --git a/tests/plugins/pack/src/assets/img1.png b/tests/plugins/pack/src/assets/img1.png new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/plugins/pack/src/assets/img1.png diff --git a/tests/plugins/pack/src/assets/img2.png b/tests/plugins/pack/src/assets/img2.png new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/plugins/pack/src/assets/img2.png diff --git a/tests/plugins/pack/src/assets/xmake.ico b/tests/plugins/pack/src/assets/xmake.ico Binary files differnew file mode 100644 index 000000000..52c8ef389 --- /dev/null +++ b/tests/plugins/pack/src/assets/xmake.ico diff --git a/tests/plugins/pack/src/main.cpp b/tests/plugins/pack/src/main.cpp new file mode 100644 index 000000000..7c435d251 --- /dev/null +++ b/tests/plugins/pack/src/main.cpp @@ -0,0 +1,9 @@ +#include <iostream> + +using namespace std; + +int main(int argc, char** argv) +{ + cout << "hello world!" << endl; + return 0; +} diff --git a/tests/plugins/pack/src/xmake.rc b/tests/plugins/pack/src/xmake.rc new file mode 100644 index 000000000..3a2783607 --- /dev/null +++ b/tests/plugins/pack/src/xmake.rc @@ -0,0 +1 @@ +IDI_APP ICON DISCARDABLE "assets/xmake.ico" diff --git a/tests/plugins/pack/xmake.lua b/tests/plugins/pack/xmake.lua new file mode 100644 index 000000000..fc436f8b7 --- /dev/null +++ b/tests/plugins/pack/xmake.lua @@ -0,0 +1,45 @@ +set_version("1.0.0") +add_rules("mode.debug", "mode.release") + +includes("@builtin/xpack") + +add_requires("zlib", {configs = {shared = true}}) + +target("test") + set_kind("binary") + add_files("src/*.cpp") + if is_plat("windows") then + add_files("src/*.rc") + end + +target("foo") + set_kind("shared") + add_files("src/*.cpp") + add_headerfiles("include/(*.h)") + add_packages("zlib") + +xpack("test") + set_formats("nsis") + set_description("hello") + set_licensefile("LICENSE.md") + add_targets("test", "foo") + set_basename("test-$(plat)-$(arch)-v$(version)") + add_installfiles("src/(assets/*.png)", {prefixdir = "images"}) + set_iconfile("src/assets/xmake.ico") + + after_installcmd(function (package, batchcmds) + batchcmds:cp("src/assets/*.txt", "resources/", {rootdir = "src"}) + batchcmds:mkdir("stub") + end) + + after_uninstallcmd(function (package, batchcmds) + batchcmds:rmdir("resources") + batchcmds:rmdir("stub") + end) + + add_nsis_installcmds("Enable Long Path", [[ + ${If} $NoAdmin == "false" + ; Enable long path + WriteRegDWORD ${HKLM} "SYSTEM\CurrentControlSet\Control\FileSystem" "LongPathsEnabled" 1 + ${EndIf}]], {description = "Increases the maximum path length limit, up to 32,767 characters (before 256)."}) + diff --git a/xmake/core/base/interpreter.lua b/xmake/core/base/interpreter.lua index 7ba677a8f..e4d6c3046 100644 --- a/xmake/core/base/interpreter.lua +++ b/xmake/core/base/interpreter.lua @@ -1897,11 +1897,15 @@ function interpreter:api_interp_add_scopeapis(...) else extra_config = nil end - local kind = "values" - if extra_config and extra_config.kind then - kind = extra_config.kind + if extra_config and #apis == 0 then + self:api_define(extra_config) + else + local kind = "values" + if extra_config and extra_config.kind then + kind = extra_config.kind + end + return self:api_define({[kind] = apis}) end - return self:api_define({[kind] = apis}) end -- get api function diff --git a/xmake/core/project/project.lua b/xmake/core/project/project.lua index 020ca80f4..502ea5f3d 100644 --- a/xmake/core/project/project.lua +++ b/xmake/core/project/project.lua @@ -716,8 +716,6 @@ function project.interpreter() result = platform.tool(variable) end end - - -- ok? return result end) diff --git a/xmake/core/project/rule.lua b/xmake/core/project/rule.lua index 57510b45a..c6f476d61 100644 --- a/xmake/core/project/rule.lua +++ b/xmake/core/project/rule.lua @@ -318,7 +318,9 @@ function rule.apis() , "rule.on_clean" , "rule.on_package" , "rule.on_install" + , "rule.on_installcmd" , "rule.on_uninstall" + , "rule.on_uninstallcmd" , "rule.on_linkcmd" , "rule.on_buildcmd" , "rule.on_buildcmd_file" @@ -334,7 +336,9 @@ function rule.apis() , "rule.before_clean" , "rule.before_package" , "rule.before_install" + , "rule.before_installcmd" , "rule.before_uninstall" + , "rule.before_uninstallcmd" , "rule.before_linkcmd" , "rule.before_buildcmd" , "rule.before_buildcmd_file" @@ -350,7 +354,9 @@ function rule.apis() , "rule.after_clean" , "rule.after_package" , "rule.after_install" + , "rule.after_installcmd" , "rule.after_uninstall" + , "rule.after_uninstallcmd" , "rule.after_linkcmd" , "rule.after_buildcmd" , "rule.after_buildcmd_file" diff --git a/xmake/core/project/target.lua b/xmake/core/project/target.lua index 3f4a4efc0..54e5c03ab 100644 --- a/xmake/core/project/target.lua +++ b/xmake/core/project/target.lua @@ -2768,7 +2768,9 @@ function target.apis() , "target.on_clean" , "target.on_package" , "target.on_install" + , "target.on_installcmd" , "target.on_uninstall" + , "target.on_uninstallcmd" -- target.before_xxx , "target.before_run" , "target.before_test" @@ -2779,7 +2781,9 @@ function target.apis() , "target.before_clean" , "target.before_package" , "target.before_install" + , "target.before_installcmd" , "target.before_uninstall" + , "target.before_uninstallcmd" -- target.after_xxx , "target.after_run" , "target.after_test" @@ -2791,7 +2795,9 @@ function target.apis() , "target.after_clean" , "target.after_package" , "target.after_install" + , "target.after_installcmd" , "target.after_uninstall" + , "target.after_uninstallcmd" } } end diff --git a/xmake/includes/xpack/xmake.lua b/xmake/includes/xpack/xmake.lua new file mode 100644 index 000000000..bb7d03435 --- /dev/null +++ b/xmake/includes/xpack/xmake.lua @@ -0,0 +1,93 @@ +--!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 ruki +-- @file xmake.lua +-- + +-- Define xpack interfaces to generate installation package. e.g. nsis, deb, rpm, ... +-- +-- And we can call `xmake pack` plugin to pack them. +-- +-- @see https://github.com/xmake-io/xmake/issues/1433 +-- + +-- define apis +local apis = { + values = { + -- set package version, we will also get it from project/target + "xpack.set_version", + -- set pacakge homepage url + "xpack.set_homepage", + -- set package description + "xpack.set_description", + -- set package copyright + "xpack.set_copyright", + -- set company name + "xpack.set_company", + -- set package formats, e.g. nsis, deb, rpm, targz, zip, runself, ... + "xpack.set_formats", + -- set the base name of the output file + "xpack.set_basename", + -- add targets to be packaged + "xpack.add_targets", + -- set installed binary directory, e.g. bin + "xpack.set_bindir", + -- set installed library directory, e.g. lib + "xpack.set_libdir", + -- set installed include directory, e.g. include + "xpack.set_includedir" + }, + paths = { + -- set the spec file path, support the custom variable pattern, e.g. set_specfile("", {pattern = "%${([^\n]-)}"}) + "xpack.set_specfile", + -- set icon file path, e.g foo.ico + "xpack.set_iconfile", + -- set package license file, we will also get them from target + "xpack.set_licensefile", + -- add install files, we will also get them from target + "xpack.add_installfiles" + }, + script = { + -- add custom load script + "xpack.on_load", + -- add custom commands script before installing + "xpack.before_installcmd", + -- add custom commands script before uninstalling + "xpack.before_uninstallcmd", + -- add custom install commands script, we will also get it from target/rules + "xpack.on_installcmd", + -- add custom uninstall commands script, we will also get it from target/rules + "xpack.on_uninstallcmd", + -- add custom commands script after installing + "xpack.after_installcmd", + -- add custom commands script after uninstalling + "xpack.after_uninstallcmd" + }, + keyvalues = { + -- set the spec variable + "xpack.set_specvar", + -- add nsis sections, e.g. + -- add NSIS install commands that will be added to the end of the install Section, e.g. + --[[ + add_nsis_installcmds("Enable Long Path", + 'WriteRegDWORD ${HKLM} "SYSTEM\CurrentControlSet\Control\FileSystem" "LongPathsEnabled" 1', + {description = "Increases the maximum path length limit, up to 32,767 characters (before 256)."}) + --]] + "xpack.add_nsis_installcmds" + } +} +interp_add_scopeapis(apis) diff --git a/xmake/modules/private/action/require/impl/utils/filter.lua b/xmake/modules/private/action/require/impl/utils/filter.lua index 75bba1d42..0eee765b2 100644 --- a/xmake/modules/private/action/require/impl/utils/filter.lua +++ b/xmake/modules/private/action/require/impl/utils/filter.lua @@ -28,8 +28,6 @@ import("core.sandbox.sandbox") -- get filter function _filter() - - -- init filter if _g.filter == nil then _g.filter = filter.new() _g.filter:register("common", function (variable) @@ -60,13 +58,9 @@ function _filter() if type(result) == "function" then result = result() end - - -- ok? return result end) end - - -- ok return _g.filter end @@ -75,10 +69,7 @@ function _handler(package, strval) -- @note cannot cache it, because the package instance will be changed return function (variable) - - -- init maps - local maps = - { + local maps = { version = function () if strval then -- set_urls("https://sqlite.org/2018/sqlite-autoconf-$(version)000.tar.gz", @@ -102,8 +93,6 @@ function _handler(package, strval) if type(result) == "function" then result = result() end - - -- ok? return result end end diff --git a/xmake/modules/private/utils/batchcmds.lua b/xmake/modules/private/utils/batchcmds.lua index 6edd1d8f3..fe518cbec 100644 --- a/xmake/modules/private/utils/batchcmds.lua +++ b/xmake/modules/private/utils/batchcmds.lua @@ -144,6 +144,22 @@ function _runcmd_rm(cmd, opt) end end +-- run command: os.tryrm +function _runcmd_tryrm(cmd, opt) + local filepath = cmd.filepath + if not opt.dryrun then + os.tryrm(filepath) + end +end + +-- run command: os.rmdir +function _runcmd_rmdir(cmd, opt) + local dir = cmd.dir + if not opt.dryrun and os.isdir(dir) then + os.tryrm(dir) + end +end + -- run command: os.cp function _runcmd_cp(cmd, opt) if not opt.dryrun then @@ -178,8 +194,10 @@ function _runcmd(cmd, opt) execv = _runcmd_execv, vexecv = _runcmd_vexecv, mkdir = _runcmd_mkdir, + rmdir = _runcmd_rmdir, cd = _runcmd_cd, rm = _runcmd_rm, + tryrm = _runcmd_tryrm, cp = _runcmd_cp, mv = _runcmd_mv, ln = _runcmd_ln @@ -333,11 +351,21 @@ function batchcmds:mkdir(dir) table.insert(self:cmds(), {kind = "mkdir", dir = dir}) end +-- add command: os.rmdir +function batchcmds:rmdir(dir) + table.insert(self:cmds(), {kind = "rmdir", dir = dir}) +end + -- add command: os.rm function batchcmds:rm(filepath) table.insert(self:cmds(), {kind = "rm", filepath = filepath}) end +-- add command: os.tryrm +function batchcmds:tryrm(filepath) + table.insert(self:cmds(), {kind = "tryrm", filepath = filepath}) +end + -- add command: os.cp function batchcmds:cp(srcpath, dstpath, opt) table.insert(self:cmds(), {kind = "cp", srcpath = srcpath, dstpath = dstpath, opt = opt}) diff --git a/xmake/plugins/pack/filter.lua b/xmake/plugins/pack/filter.lua new file mode 100644 index 000000000..269dab2da --- /dev/null +++ b/xmake/plugins/pack/filter.lua @@ -0,0 +1,120 @@ +--!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 ruki +-- @file filter.lua +-- + +-- imports +import("core.base.filter") +import("core.base.option") +import("core.base.global") +import("core.project.config") +import("core.project.project") +import("core.sandbox.sandbox") + +-- get filter +function _filter() + if _g.filter == nil then + _g.filter = filter.new() + _g.filter:register("common", function (variable) + + -- attempt to get it directly from the configure + local result = config.get(variable) + if result == nil then + + -- init maps + _g.common_maps = _g.common_maps or + { + host = os.host() + , subhost = os.subhost() + , tmpdir = function () return os.tmpdir() end + , curdir = function () return os.curdir() end + , scriptdir = function () return os.scriptdir() end + , globaldir = global.directory() + , configdir = config.directory() + , projectdir = project.directory() + , programdir = os.programdir() + } + + -- map it + result = _g.common_maps[variable] + end + + -- is script? call it + if type(result) == "function" then + result = result() + end + return result + end) + end + return _g.filter +end + +-- the package handler +function _handler(package, strval) + + -- @note cannot cache it, because the package instance will be changed + return function (variable) + local maps = { + arch = package:arch(), + plat = package:plat(), + version = package:version() + } + + -- get value + local result = maps[variable] + if type(result) == "function" then + result = result() + end + return result + end +end + +-- attach filter to the given script and call it +function call(script, package, opt) + + -- get sandbox filter and handlers of the given script + local sandbox_filter = sandbox.filter(script) + local sandbox_handlers = sandbox_filter:handlers() + + -- switch to the handlers of the current filter + sandbox_filter:set_handlers(_filter():handlers()) + + -- register package handler + sandbox_filter:register("package", _handler(package)) + + -- call it + script(package, opt) + + -- restore handlers + sandbox_filter:set_handlers(sandbox_handlers) +end + +-- handle the string value of package +function handle(strval, package) + + -- register filter handler + _filter():register("package", _handler(package, strval)) + + -- handle string value + strval = _filter():handle(strval) + + -- register filter handler + _filter():register("package", nil) + return strval +end + diff --git a/xmake/plugins/pack/main.lua b/xmake/plugins/pack/main.lua new file mode 100644 index 000000000..09160fda0 --- /dev/null +++ b/xmake/plugins/pack/main.lua @@ -0,0 +1,97 @@ +--!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 ruki +-- @file main.lua +-- + +-- imports +import("core.base.task") +import("core.base.option") +import("core.project.project") +import("private.service.remote_build.action", {alias = "remote_build_action"}) +import("actions.build.main", {rootdir = os.programdir(), alias = "build_action"}) +import("xpack") + +function _load_package(package, format) + package:format_set(format) + local script = package:script("load") + if script then + script(package) + end +end + +function _pack_package(package) + assert(package:formats(), "xpack(%s): formats not found, please use `set_formats()` to set it.", package:name()) + for _, format in package:formats():keys() do + _load_package(package, format) + import(format)(package) + end +end + +function _pack_packages() + for _, package in pairs(xpack.packages()) do + _pack_package(package) + end +end + +function _build_targets() + local targetnames = {} + for _, package in pairs(xpack.packages()) do + local targets = package:get("targets") + if targets then + table.join2(targetnames, targets) + end + end + if #targetnames > 0 then + build_action.build_targets(targetnames) + end +end + +function main() + + -- do action for remote? + if remote_build_action.enabled() then + return remote_build_action() + end + + -- lock the whole project + project.lock() + + -- load config first + task.run("config", {}, {disable_dump = true}) + + -- load targets + project.load_targets() + + -- enter project directory + local oldir = os.cd(project.directory()) + + -- build targets first + _build_targets() + + -- do pack + _pack_packages() + + -- leave project directory + os.cd(oldir) + + -- unlock the whole project + project.unlock() + cprint("${color.success}pack ok") +end + + diff --git a/xmake/plugins/pack/nsis/main.lua b/xmake/plugins/pack/nsis/main.lua new file mode 100644 index 000000000..23749962a --- /dev/null +++ b/xmake/plugins/pack/nsis/main.lua @@ -0,0 +1,618 @@ +--!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 ruki +-- @file main.lua +-- + +-- imports +import("core.base.option") +import("core.base.semver") +import("lib.detect.find_tool") +import("private.utils.batchcmds") +import("private.action.require.impl.packagenv") +import("private.action.require.impl.install_packages") + +-- get the makensis +function _get_makensis() + + -- enter the environments of nsis + local oldenvs = packagenv.enter("nsis") + + -- find makensis + local packages = {} + local makensis = find_tool("makensis", {check = "/CMDHELP"}) + if not makensis then + table.join2(packages, install_packages("nsis")) + 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 makensis then + makensis = find_tool("makensis", {check = "/CMDHELP", force = true}) + end + assert(makensis, "makensis not found!") + + return makensis, oldenvs +end + +-- get unique tag +function _get_unique_tag(content) + return hash.uuid(content):split("-", {plain = true})[1]:lower() +end + +-- get command string +function _get_command_strings(package, cmd) + local result = {} + local opt = cmd.opt or {} + local kind = cmd.kind + if kind == "cp" then + -- https://nsis.sourceforge.io/Reference/File + local srcfiles = os.files(cmd.srcpath) + for _, srcfile in ipairs(srcfiles) do + -- the destination is directory? append the filename + local dstfile = cmd.dstpath + if path.islastsep(dstfile) then + if opt.rootdir then + dstfile = path.join(dstfile, path.relative(srcfile, opt.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(path.join("$InstDir", dstfile))) + table.insert(result, string.format("SetOutPath \"%s\"", dstdir)) + table.insert(result, string.format("File /oname=%s \"%s\"", dstname, srcfile)) + end + elseif kind == "rm" then + local filepath = path.normalize(path.join("$InstDir", cmd.filepath)) + table.insert(result, string.format("Delete \"%s\"", filepath)) + elseif kind == "tryrm" then + --[[ + IfFileExists "$InstDir\file" file_found file_not_found_or_end + file_found: + Delete "$InstDir\file" + goto file_not_found_or_end + file_not_found_or_end: + --]] + local filepath = path.normalize(path.join("$InstDir", cmd.filepath)) + local tag = _get_unique_tag(filepath) + table.insert(result, string.format("IfFileExists \"%s\" file_found_%s file_not_found_or_end_%s", filepath, tag, tag)) + table.insert(result, string.format("file_found_%s:", tag)) + table.insert(result, string.format(" Delete \"%s\"", filepath)) + table.insert(result, string.format(" goto file_not_found_or_end_%s", tag)) + table.insert(result, string.format("file_not_found_or_end_%s:", tag)) + elseif kind == "rmdir" then + local dir = path.normalize(path.join("$InstDir", cmd.dir)) + table.insert(result, string.format("RMDir /r \"%s\"", dir)) + elseif kind == "mv" then + local srcpath = path.normalize(path.join("$InstDir", cmd.srcpath)) + local dstpath = path.normalize(path.join("$InstDir", cmd.dstpath)) + table.insert(result, string.format("Rename \"%s\" \"%s\"", srcpath, dstpath)) + elseif kind == "cd" then + local dir = path.normalize(path.join("$InstDir", cmd.dir)) + table.insert(result, string.format("SetOutPath \"%s\"", dir)) + elseif kind == "mkdir" then + local dir = path.normalize(path.join("$InstDir", cmd.dir)) + table.insert(result, string.format("CreateDirectory \"%s\"", dir)) + end + return result +end + +-- get commands string +function _get_commands_string(package, cmds) + local cmdstrs = {} + for _, cmd in ipairs(cmds) do + table.join2(cmdstrs, _get_command_strings(package, cmd)) + end + return table.concat(cmdstrs, "\n ") +end + +-- install headers +function _install_headers(target, batchcmds_, includedir) + local srcheaders, dstheaders = target:headerfiles(includedir, {installonly = true}) + if srcheaders and dstheaders then + local i = 1 + for _, srcheader in ipairs(srcheaders) do + local dstheader = dstheaders[i] + if dstheader then + batchcmds_:cp(srcheader, dstheader) + end + i = i + 1 + end + end +end + +-- install shared libraries for package +function _install_shared_for_package(target, pkg, batchcmds_, outputdir) + _g.installed_dllfiles = _g.installed_dllfiles or {} + for _, dllpath in ipairs(table.wrap(pkg:get("libfiles"))) do + if dllpath:endswith(".dll") then + -- prevent packages using the same libfiles from overwriting each other + if not _g.installed_dllfiles[dllpath] then + local dllname = path.filename(dllpath) + batchcmds_:cp(dllpath, path.join(outputdir, dllname)) + _g.installed_dllfiles[dllpath] = true + end + end + end +end + +-- install shared libraries for packages +function _install_shared_for_packages(target, batchcmds_, outputdir) + _g.installed_packages = _g.installed_packages or {} + for _, pkg in ipairs(target:orderpkgs()) do + if not _g.installed_packages[pkg:name()] then + if pkg:enabled() and pkg:get("libfiles") then + _install_shared_for_package(target, pkg, batchcmds_, outputdir) + end + _g.installed_packages[pkg:name()] = true + end + end +end + +-- uninstall headers +function _uninstall_headers(target, batchcmds_, includedir) + local _, dstheaders = target:headerfiles(includedir, {installonly = true}) + for _, dstheader in ipairs(dstheaders) do + batchcmds_:rm(dstheader) + end +end + +-- uninstall shared libraries for package +function _uninstall_shared_for_package(target, pkg, batchcmds_, outputdir) + for _, dllpath in ipairs(table.wrap(pkg:get("libfiles"))) do + if dllpath:endswith(".dll") then + local dllname = path.filename(dllpath) + batchcmds_:rm(path.join(outputdir, dllname)) + end + end +end + +-- uninstall shared libraries for packages +function _uninstall_shared_for_packages(target, batchcmds_, outputdir) + _g.uninstalled_packages = _g.uninstalled_packages or {} + for _, pkg in ipairs(target:orderpkgs()) do + if not _g.uninstalled_packages[pkg:name()] then + if pkg:enabled() and pkg:get("libfiles") then + _uninstall_shared_for_package(target, pkg, batchcmds_, outputdir) + end + _g.uninstalled_packages[pkg:name()] = true + end + end +end + +-- on install binary target command +function _on_target_installcmd_binary(target, batchcmds_, opt) + local package = opt.package + local bindir = package:bindir() + + -- install target file + batchcmds_:cp(target:targetfile(), path.join(bindir, target:filename())) + if os.isfile(target:symbolfile()) then + batchcmds_:cp(target:symbolfile(), path.join(bindir, path.filename(target:symbolfile()))) + end + + -- install the dependent shared/windows (*.dll) target + -- @see https://github.com/xmake-io/xmake/issues/961 + _g.installed_dllfiles = _g.installed_dllfiles or {} + for _, dep in ipairs(target:orderdeps()) do + if dep:kind() == "shared" then + local depfile = dep:targetfile() + if os.isfile(depfile) then + if not _g.installed_dllfiles[depfile] then + batchcmds_:cp(depfile, path.join(bindir, path.filename(depfile))) + _g.installed_dllfiles[depfile] = true + end + end + end + -- install all shared libraries in packages in all deps + _install_shared_for_packages(dep, batchcmds_, bindir) + end + + -- install shared libraries for all packages + _install_shared_for_packages(target, batchcmds_, bindir) +end + +-- on install shared target command +function _on_target_installcmd_shared(target, batchcmds_, opt) + local package = opt.package + local bindir = package:bindir() + local libdir = package:libdir() + local includedir = package:includedir() + + -- install target file + batchcmds_:cp(target:targetfile(), path.join(bindir, target:filename())) + if os.isfile(target:symbolfile()) then + batchcmds_:cp(target:symbolfile(), path.join(bindir, path.filename(target:symbolfile()))) + end + + -- install *.lib for shared/windows (*.dll) target + -- @see https://github.com/xmake-io/xmake/issues/714 + local targetfile = target:targetfile() + local targetfile_lib = path.join(path.directory(targetfile), path.basename(targetfile) .. (target:is_plat("mingw") and ".dll.a" or ".lib")) + if os.isfile(targetfile_lib) then + batchcmds_:mkdir(libdir) + batchcmds_:cp(targetfile_lib, path.join(libdir, path.filename(targetfile_lib))) + end + + -- install shared libraries for all packages + _install_shared_for_packages(target, batchcmds_, bindir) + + -- install headers + _install_headers(target, batchcmds_, includedir) +end + +-- on install static target command +function _on_target_installcmd_static(target, batchcmds_, opt) + local package = opt.package + local libdir = package:libdir() + local includedir = package:includedir() + + -- install target file + batchcmds_:cp(target:targetfile(), path.join(libdir, target:filename())) + if os.isfile(target:symbolfile()) then + batchcmds_:cp(target:symbolfile(), path.join(libdir, path.filename(target:symbolfile()))) + end + + -- install headers + _install_headers(target, batchcmds_, includedir) +end + +-- on install headeronly target command +function _on_target_installcmd_headeronly(target, batchcmds_, opt) + local package = opt.package + local includedir = package:includedir() +end + +-- on install target command +function _on_target_installcmd(target, batchcmds_, opt) + + -- install target binaries + local scripts = { + binary = _on_target_installcmd_binary, + shared = _on_target_installcmd_shared, + static = _on_target_installcmd_static, + headeronly = _on_target_installcmd_headeronly + } + local script = scripts[target:kind()] + if script then + script(target, batchcmds_, opt) + end + + -- install target files + local srcfiles, dstfiles = target:installfiles(".") + for idx, srcfile in ipairs(srcfiles) do + batchcmds_:cp(srcfile, dstfiles[idx]) + end +end + +-- on uninstall binary target command +function _on_target_uninstallcmd_binary(target, batchcmds_, opt) + local package = opt.package + local bindir = package:bindir() + + -- uninstall target file + batchcmds_:rm(path.join(bindir, target:filename())) + batchcmds_:tryrm(path.join(bindir, path.filename(target:symbolfile()))) + + -- remove the dependent shared/windows (*.dll) target + -- @see https://github.com/xmake-io/xmake/issues/961 + for _, dep in ipairs(target:orderdeps()) do + if dep:is_shared() then + batchcmds_:rm(path.join(bindir, path.filename(dep:targetfile()))) + end + _uninstall_shared_for_packages(dep, batchcmds_, bindir) + end + + -- uninstall shared libraries for packages + _uninstall_shared_for_packages(target, batchcmds_, bindir) +end + +-- on uninstall shared target command +function _on_target_uninstallcmd_shared(target, batchcmds_, opt) + local package = opt.package + local bindir = package:bindir() + local libdir = package:libdir() + local includedir = package:includedir() + + -- uninstall target file + batchcmds_:rm(path.join(bindir, target:filename())) + batchcmds_:tryrm(path.join(bindir, path.filename(target:symbolfile()))) + + -- remove *.lib for shared/windows (*.dll) target + -- @see https://github.com/xmake-io/xmake/issues/714 + local targetfile = target:targetfile() + batchcmds_:rm(path.join(libdir, path.basename(targetfile) .. (target:is_plat("mingw") and ".dll.a" or ".lib"))) + + -- remove headers from the include directory + _uninstall_headers(target, batchcmds_, includedir) + + -- uninstall shared libraries for packages + _uninstall_shared_for_packages(target, batchcmds_, bindir) +end + +-- on uninstall static target command +function _on_target_uninstallcmd_static(target, batchcmds_, opt) + local package = opt.package + local libdir = package:libdir() + local includedir = package:includedir() + + -- uninstall target file + batchcmds_:rm(path.join(libdir, target:filename())) + batchcmds_:tryrm(path.join(libdir, path.filename(target:symbolfile()))) + + -- remove headers from the include directory + _uninstall_headers(target, batchcmds_, includedir) +end + +-- on uninstall headeronly target command +function _on_target_uninstallcmd_headeronly(target, batchcmds_, opt) + local package = opt.package + local includedir = package:includedir() + _uninstall_headers(target, batchcmds_, includedir) +end + +-- on uninstall target command +function _on_target_uninstallcmd(target, batchcmds_, opt) + + -- uninstall target binaries + local scripts = { + binary = _on_target_uninstallcmd_binary, + shared = _on_target_uninstallcmd_shared, + static = _on_target_uninstallcmd_static, + headeronly = _on_target_uninstallcmd_headeronly + } + local script = scripts[target:kind()] + if script then + script(target, batchcmds_, opt) + end + + -- uninstall target files + local _, dstfiles = target:installfiles(".") + for _, dstfile in ipairs(dstfiles) do + batchcmds_:rm(dstfile) + end +end + +-- get install commands from targets +function _get_target_installcmds(target, batchcmds_, opt) + + -- call script to get install commands + local scripts = { + target:script("installcmd_before"), + function (target) + for _, r in ipairs(target:orderules()) do + local before_installcmd = r:script("installcmd_before") + if before_installcmd then + before_installcmd(target, batchcmds_, opt) + end + end + end, + target:script("installcmd", _on_target_installcmd), + function (target) + for _, r in ipairs(target:orderules()) do + local after_installcmd = r:script("installcmd_after") + if after_installcmd then + after_installcmd(target, batchcmds_, opt) + end + end + end, + target:script("installcmd_after") + } + for i = 1, 5 do + local script = scripts[i] + if script ~= nil then + script(target, batchcmds_, opt) + end + end +end + +-- get uninstall commands from targets +function _get_target_uninstallcmds(target, batchcmds_, opt) + + -- call script to get uninstall commands + local scripts = { + target:script("uninstallcmd_before"), + function (target) + for _, r in ipairs(target:orderules()) do + local before_uninstallcmd = r:script("uninstallcmd_before") + if before_uninstallcmd then + before_uninstallcmd(target, batchcmds_, opt) + end + end + end, + target:script("uninstallcmd", _on_target_uninstallcmd), + function (target) + for _, r in ipairs(target:orderules()) do + local after_uninstallcmd = r:script("uninstallcmd_after") + if after_uninstallcmd then + after_uninstallcmd(target, batchcmds_, opt) + end + end + end, + target:script("uninstallcmd_after") + } + for i = 1, 5 do + local script = scripts[i] + if script ~= nil then + script(target, batchcmds_, opt) + end + end +end + +-- on install command +function _on_installcmd(package, batchcmds_) + local srcfiles, dstfiles = package:installfiles(".") + for idx, srcfile in ipairs(srcfiles) do + batchcmds_:cp(srcfile, dstfiles[idx]) + end + for _, target in ipairs(package:targets()) do + _get_target_installcmds(target, batchcmds_, {package = package}) + end +end + +-- on uninstall command +function _on_uninstallcmd(package, batchcmds_) + local _, dstfiles = package:installfiles(".") + for _, dstfile in ipairs(dstfiles) do + batchcmds_:rm(dstfile) + end + for _, target in ipairs(package:targets()) do + _get_target_uninstallcmds(target, batchcmds_, {package = package}) + end +end + +-- get install commands +function _get_installcmds(package) + local batchcmds_ = batchcmds.new() + + -- call script to get install commands + local scripts = { + package:script("installcmd_before"), + package:script("installcmd", _on_installcmd), + package:script("installcmd_after") + } + for i = 1, 3 do + local script = scripts[i] + if script ~= nil then + script(package, batchcmds_) + end + end + + -- generate command string + return _get_commands_string(package, batchcmds_:cmds()) +end + +-- get uninstall commands +function _get_uninstallcmds(package) + local batchcmds_ = batchcmds.new() + + -- call script to get uninstall commands + local scripts = { + package:script("uninstallcmd_before"), + package:script("uninstallcmd", _on_uninstallcmd), + package:script("uninstallcmd_after") + } + for i = 1, 3 do + local script = scripts[i] + if script ~= nil then + script(package, batchcmds_) + end + end + + -- generate command string + return _get_commands_string(package, batchcmds_:cmds()) +end + +-- get specvars +function _get_specvars(package) + local specvars = table.clone(package:specvars()) + specvars.PACKAGE_WORKDIR = path.absolute(os.projectdir()) + specvars.PACKAGE_BINDIR = package:bindir() + specvars.PACKAGE_OUTPUTFILE = path.absolute(package:outputfile()) + specvars.PACKAGE_INSTALLCMDS = function () + return _get_installcmds(package) + end + specvars.PACKAGE_UNINSTALLCMDS = function () + return _get_uninstallcmds(package) + end + specvars.PACKAGE_NSIS_INSTALL_SECTIONS = function () + local result = {} + local cmds = package:get("nsis_installcmds") + for name, cmd in pairs(cmds) do + local tag = "Install" .. _get_unique_tag(name) + table.insert(result, string.format('Section "%s" %s', name, tag)) + table.insert(result, cmd) + table.insert(result, "SectionEnd") + end + return table.concat(result, "\n ") + end + specvars.PACKAGE_NSIS_INSTALL_DESCS = function () + local result = {} + local cmds = package:get("nsis_installcmds") + for name, cmd in pairs(cmds) do + local tag = "Install" .. _get_unique_tag(name) + local description = package:extraconf("nsis_installcmds." .. name, cmd, "description") or name + table.insert(result, string.format('LangString DESC_%s ${LANG_ENGLISH} "%s"', tag, description)) + end + return table.concat(result, "\n ") + end + specvars.PACKAGE_NSIS_INSTALL_DESCRIPTION_TEXTS = function () + local result = {} + local cmds = package:get("nsis_installcmds") + for name, _ in pairs(cmds) do + local tag = "Install" .. _get_unique_tag(name) + table.insert(result, string.format('!insertmacro MUI_DESCRIPTION_TEXT ${%s} $(DESC_%s)', tag, tag)) + end + return table.concat(result, "\n ") + end + return specvars +end + +-- pack nsis package +function _pack_nsis(makensis, package, opt) + + -- install the initial specfile + local specfile = package:specfile() + if not os.isfile(specfile) then + local specfile_template = path.join(os.programdir(), "scripts", "xpack", "nsis", "makensis.nsi") + 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) + + -- make package + os.vrunv(makensis, {specfile}) +end + +function main(package) + cprint("packing %s", package:outputfile()) + + -- get makensis + local makensis, oldenvs = _get_makensis() + + -- clean the build directory first + os.tryrm(package:buildir()) + + -- pack nsis package + os.mkdir(package:outputdir()) + _pack_nsis(makensis.program, package, opt) + + -- done + os.setenvs(oldenvs) +end diff --git a/xmake/plugins/pack/xmake.lua b/xmake/plugins/pack/xmake.lua new file mode 100644 index 000000000..4bbd7f4bf --- /dev/null +++ b/xmake/plugins/pack/xmake.lua @@ -0,0 +1,37 @@ +--!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 ruki +-- @file pack.lua +-- + +task("pack") + set_category("plugin") + on_run("main") + set_menu { + usage = "xmake pack [options] [names]", + description = "Pack binary installation packages.", + options = { + {'o', "outputdir", "kv", nil, "The output directory. (default: build/xpack)"}, + {'f', "formats", "kv", "all", "Pack the given package formats.", + "e.g.", + " - xmake pack -f nsis,deb,rpm", + "values:", + values = {"nsis", "deb", "rpm", "runself", "targz", "zip"}}, + {}, + {nil, "packages", "vs", nil, "The package names."} + } + } diff --git a/xmake/plugins/pack/xpack.lua b/xmake/plugins/pack/xpack.lua new file mode 100644 index 000000000..c925a9af1 --- /dev/null +++ b/xmake/plugins/pack/xpack.lua @@ -0,0 +1,495 @@ +--!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 ruki +-- @file xpack.lua +-- + +-- imports +import("core.base.object") +import("core.base.option") +import("core.base.semver") +import("core.base.hashset") +import("core.project.config") +import("core.project.project") +import("lib.detect.find_tool") +import("filter") + +-- define module +local xpack = xpack or object {_init = {"_name", "_info"}} + +-- get name +function xpack:name() + return self._name +end + +-- get value +function xpack:get(name) + if self._info then + return self._info:get(name) + end +end + +-- get the extra configuration +function xpack:extraconf(name, item, key) + if self._info then + return self._info:extraconf(name, item, key) + end +end + +-- get the package license +function xpack:license() + return self:get("license") +end + +-- get the package description +function xpack:description() + return self:get("description") +end + +-- get the platform of package +function xpack:plat() + return config.get("plat") or os.subhost() +end + +-- get the architecture of package +function xpack:arch() + return config.get("arch") or os.subarch() +end + +-- the current platform is belong to the given platforms? +function xpack:is_plat(...) + local plat = self:plat() + for _, v in ipairs(table.pack(...)) do + if v and plat == v then + return true + end + end +end + +-- the current architecture is belong to the given architectures? +function xpack:is_arch(...) + local arch = self:arch() + for _, v in ipairs(table.pack(...)) do + if v and arch:find("^" .. v:gsub("%-", "%%-") .. "$") then + return true + end + end +end + +-- get xxx_script +function xpack:script(name, generic) + + -- get script + local script = self:get(name) + local result = nil + if type(script) == "function" then + result = script + elseif type(script) == "table" then + + -- get plat and arch + local plat = self:plat() + local arch = self:arch() + + -- match pattern + -- + -- `@linux` + -- `@linux|x86_64` + -- `@macosx,linux` + -- `android@macosx,linux` + -- `android|armeabi-v7a@macosx,linux` + -- `android|armeabi-v7a@macosx,linux|x86_64` + -- `android|armeabi-v7a@linux|x86_64` + -- + for _pattern, _script in pairs(script) do + local hosts = {} + local hosts_spec = false + _pattern = _pattern:gsub("@(.+)", function (v) + for _, host in ipairs(v:split(',')) do + hosts[host] = true + hosts_spec = true + end + return "" + end) + if not _pattern:startswith("__") and (not hosts_spec or hosts[os.subhost() .. '|' .. os.subarch()] or hosts[os.subhost()]) + and (_pattern:trim() == "" or (plat .. '|' .. arch):find('^' .. _pattern .. '$') or plat:find('^' .. _pattern .. '$')) then + result = _script + break + end + end + + -- get generic script + result = result or script["__generic__"] or generic + end + + -- only generic script + return result or generic +end + +-- get targets +function xpack:targets() + local targets = self._targets + if not targets then + targets = {} + local targetnames = self:get("targets") + if targetnames then + for _, name in ipairs(targetnames) do + local target = project.target(name) + if target then + table.insert(targets, target) + else + raise("xpack(%s): target(%s) not found!", self:name(), name) + end + end + end + self._targets = targets + end + return targets +end + +-- get formats +function xpack:formats() + local formats = self._formats + if not formats then + formats = hashset.new() + for _, format in ipairs(self:get("formats")) do + formats:insert(format) + end + self._formats = formats + end + return formats +end + +-- has the given format? +function xpack:format_has(...) + local formats = self:formats() + for _, v in ipairs(table.pack(...)) do + if v and formats:has(v) then + return true + end + end +end + +-- set the current format +function xpack:format_set(format) + self._FORMAT = format +end + +-- get the current format +function xpack:format() + return self._FORMAT +end + +-- get the build directory +function xpack:buildir() + return path.join(config.buildir(), ".xpack", self:name()) +end + +-- get the output directory +function xpack:outputdir() + local outputdir = option.get("outputdir") + if outputdir == nil then + outputdir = path.join(config.buildir(), "xpack", self:name()) + end + return outputdir +end + +-- get the basename +function xpack:basename() + local basename = self:get("basename") + if basename == nil then + basename = self:name() + local version = self:version() + if version then + basename = basename .. "-" .. version + end + end + -- we need filter builtin variables, e.g. $(plat), $(arch), $(version) ... + return filter.handle(basename, self) +end + +-- get the spec variables +function xpack:specvars() + local specvars = self._specvars + if specvars == nil then + specvars = { + PACKAGE_ARCH = self:arch(), + PACKAGE_PLAT = self:plat(), + PACKAGE_NAME = self:name(), + PACKAGE_DESCRIPTION = self:description() or "", + PACKAGE_FILENAME = self:filename(), + PACKAGE_HOMEPAGE = self:get("homepage") or "", + PACKAGE_COPYRIGHT = self:get("copyright") or "", + PACKAGE_COMPANY = self:get("company") or "", + PACKAGE_ICONFILE = self:get("iconfile") or "", + PACKAGE_LICENSEFILE = self:get("licensefile") or "" + } + + -- get version + local version, version_build = self:version() + if version then + specvars.PACKAGE_VERSION = version or "0.0.0" + try {function () + local v = semver.new(version) + if v then + specvars.PACKAGE_VERSION_MAJOR = v:major() or "0" + specvars.PACKAGE_VERSION_MINOR = v:minor() or "0" + specvars.PACKAGE_VERSION_ALTER = v:patch() or "0" + end + end} + specvars.PACKAGE_VERSION_BUILD = version_build or "" + end + + -- get git information + local cmds = + { + GIT_TAG = {"describe", "--tags"}, + GIT_TAG_LONG = {"describe", "--tags", "--long"}, + GIT_BRANCH = {"rev-parse", "--abbrev-ref", "HEAD"}, + GIT_COMMIT = {"rev-parse", "--short", "HEAD"}, + GIT_COMMIT_LONG = {"rev-parse", "HEAD"}, + GIT_COMMIT_DATE = {"log", "-1", "--date=format:%Y%m%d%H%M%S", "--format=%ad"} + } + for name, argv in pairs(cmds) do + specvars[name] = function () + local result + local git = find_tool("git") + if git then + result = try {function () + return os.iorunv(git.program, argv) + end} + end + if not result then + result = "none" + end + return result:trim() + end + end + + -- get user variables + local vars = self:get("specvar") + if vars then + table.join2(specvars, vars) + end + self._specvars = specvars + end + return specvars +end + +-- get the specfile path +function xpack:specfile() + local extensions = { + nsis = ".nsi" + } + local extension = extensions[self:format()] or ".spec" + return self:get("specfile") or path.join(self:buildir(), self:basename() .. extension) +end + +-- get the output filename +function xpack:filename() + local extensions = { + nsis = ".exe" + } + local extension = extensions[self:format()] or "" + return self:basename() .. extension +end + +-- get the output file +function xpack:outputfile() + return path.join(self:outputdir(), self:filename()) +end + +-- get the package version +function xpack:version() + local version = self:get("version") + local version_build + if version == nil then + for _, target in ipairs(self:targets()) do + version, version_build = target:version() + if version then + break + end + end + if version == nil then + version, version_build = project.version() + end + else + version_build = self:extraconf("version", version, "build") + if type(version_build) == "string" then + version_build = os.date(version_build, os.time()) + end + end + return version, version_build +end + +-- get the copied files +function xpack:_copiedfiles(filetype, outputdir) + + -- no copied files? + local copiedfiles = self:get(filetype) + if not copiedfiles then return end + + -- get the extra information + local extrainfo = table.wrap(self:get("__extra_" .. filetype)) + + -- get the source paths and destinate paths + local srcfiles = {} + local dstfiles = {} + local fileinfos = {} + for _, copiedfile in ipairs(table.wrap(copiedfiles)) do + + -- get the root directory + local rootdir, count = copiedfile:gsub("|.*$", ""):gsub("%(.*%)$", "") + if count == 0 then + rootdir = nil + end + if rootdir and rootdir:trim() == "" then + rootdir = "." + end + + -- remove '(' and ')' + local srcpaths = copiedfile:gsub("[%(%)]", "") + if srcpaths then + + -- get the source paths + srcpaths = os.match(srcpaths) + if srcpaths and #srcpaths > 0 then + + -- add the source copied files + table.join2(srcfiles, srcpaths) + + -- the copied directory exists? + if outputdir then + + -- get the file info + local fileinfo = extrainfo[copiedfile] or {} + + -- get the prefix directory + local prefixdir = fileinfo.prefixdir + + -- add the destinate copied files + for _, srcpath in ipairs(srcpaths) do + + -- get the destinate directory + local dstdir = outputdir + if prefixdir then + dstdir = path.join(dstdir, prefixdir) + end + + -- the destinate file + local dstfile = nil + if rootdir then + dstfile = path.absolute(path.relative(srcpath, rootdir), dstdir) + else + dstfile = path.join(dstdir, path.filename(srcpath)) + end + assert(dstfile) + + -- modify filename + if fileinfo.filename then + dstfile = path.join(path.directory(dstfile), fileinfo.filename) + end + + -- add it + table.insert(dstfiles, dstfile) + table.insert(fileinfos, fileinfo) + end + end + end + end + end + return srcfiles, dstfiles, fileinfos +end + +-- get the install files +function xpack:installfiles(outputdir) + return self:_copiedfiles("installfiles", outputdir) +end + +-- get the binary directory +function xpack:bindir() + local bindir = self:get("bindir") + if bindir == nil then + bindir = "bin" + end + return bindir +end + +-- get the library directory +function xpack:libdir() + local libdir = self:get("libdir") + if libdir == nil then + libdir = "lib" + end + return libdir +end + +-- get the include directory +function xpack:includedir() + local includedir = self:get("includedir") + if includedir == nil then + includedir = "include" + end + return includedir +end + +-- new a xpack +function _new(name, info) + return xpack {name, info} +end + +-- get xpack packages +function packages() + local packages = _g.packages + if not packages then + packages = {} + local packages_need = option.get("packages") + if packages_need then + packages_need = hashset.from(packages_need) + end + local formats_need = option.get("formats") + if formats_need then + formats_need = formats_need:split(",") + if formats_need[1] == "all" then + formats_need = nil + end + end + local xpack_scope = project.scope("xpack") + for name, scope in pairs(xpack_scope) do + local need = false + if packages_need then + if packages_need:has(name) then + need = true + end + else + need = true + end + if need then + local instance = _new(name, scope) + if not formats_need or instance:format_has(table.unpack(formats_need)) then + packages[name] = instance + end + end + end + _g.packages = packages + end + return packages +end + +-- get the given package +function package(name) + return packages()[name] +end diff --git a/xmake/plugins/project/cmake/cmakelists.lua b/xmake/plugins/project/cmake/cmakelists.lua index fea241066..150720c1e 100644 --- a/xmake/plugins/project/cmake/cmakelists.lua +++ b/xmake/plugins/project/cmake/cmakelists.lua @@ -908,6 +908,8 @@ function _get_command_string(cmd, outputdir) end elseif kind == "rm" then return string.format("${CMAKE_COMMAND} -E rm -rf %s", _get_relative_unix_path_to_cmake(cmd.filepath, outputdir)) + elseif kind == "rmdir" then + return string.format("${CMAKE_COMMAND} -E rm -rf %s", _get_relative_unix_path_to_cmake(cmd.dir, outputdir)) elseif kind == "mv" then return string.format("${CMAKE_COMMAND} -E rename %s %s", _get_relative_unix_path_to_cmake(cmd.srcpath, outputdir), _get_relative_unix_path_to_cmake(cmd.dstpath, outputdir)) diff --git a/xmake/plugins/project/make/makefile.lua b/xmake/plugins/project/make/makefile.lua index 7d2559ea1..7d462b138 100644 --- a/xmake/plugins/project/make/makefile.lua +++ b/xmake/plugins/project/make/makefile.lua @@ -215,6 +215,15 @@ function _get_cmd_rm(filedir) end end +-- get command: rmdir +function _get_cmd_rmdir(filedir) + if is_subhost("windows") then + return string.format("@rmdir /S /Q %s > NUL 2>&1", filedir) + else + return string.format("@rm -rf %s", filedir) + end +end + -- get command: echo function _get_cmd_echo(str) return string.format("@echo %s", colors.ignore(str)) @@ -248,6 +257,8 @@ function _get_command_string(cmd, outputdir) end elseif kind == "rm" then return _get_cmd_rm(_get_relative_unix_path(cmd.filepath, outputdir)) + elseif kind == "rmdir" then + return _get_cmd_rmdir(_get_relative_unix_path(cmd.dir, outputdir)) elseif kind == "mv" then return _get_cmd_mv(_get_relative_unix_path(cmd.srcpath, outputdir), _get_relative_unix_path(cmd.dstpath, outputdir)) elseif kind == "ln" then diff --git a/xmake/plugins/project/vstudio/impl/vs201x.lua b/xmake/plugins/project/vstudio/impl/vs201x.lua index 3b6f145f4..a8b48dada 100644 --- a/xmake/plugins/project/vstudio/impl/vs201x.lua +++ b/xmake/plugins/project/vstudio/impl/vs201x.lua @@ -102,6 +102,8 @@ function _get_command_string(cmd, vcxprojdir) return string.format("copy /Y \"%s\" \"%s\"", _translate_path(cmd.srcpath, vcxprojdir), _translate_path(cmd.dstpath, vcxprojdir)) elseif kind == "rm" then return string.format("del /F /Q \"%s\" || rmdir /S /Q \"%s\"", _translate_path(cmd.filepath, vcxprojdir), _translate_path(cmd.filepath, vcxprojdir)) + elseif kind == "rmdir" then + return string.format("rmdir /S /Q \"%s\"", _translate_path(cmd.filepath, vcxprojdir)) elseif kind == "mv" then return string.format("rename \"%s\" \"%s\"", _translate_path(cmd.srcpath, vcxprojdir), _translate_path(cmd.dstpath, vcxprojdir)) elseif kind == "cd" then diff --git a/xmake/scripts/xpack/nsis/makensis.nsi b/xmake/scripts/xpack/nsis/makensis.nsi new file mode 100644 index 000000000..3b79361bd --- /dev/null +++ b/xmake/scripts/xpack/nsis/makensis.nsi @@ -0,0 +1,301 @@ +; this is a NSI spec file, +; it is autogenerated by the xmake build system. +; do not edit by hand. + +; includes +!include "MUI2.nsh" +!include "WordFunc.nsh" +!include "WinMessages.nsh" +!include "FileFunc.nsh" +!include "UAC.nsh" + +; the version information +!define VERSION "${PACKAGE_VERSION}" +!if "${PACKAGE_VERSION_BUILD}" != "" +!define VERSION_FULL "${PACKAGE_VERSION}+${PACKAGE_VERSION_BUILD}" +!else +!define VERSION_FULL "${PACKAGE_VERSION}" +!endif + +; set the package name +Name "${PACKAGE_NAME} - v${VERSION}" + +; set the output file path +OutFile "${PACKAGE_OUTPUTFILE}" + +; set the working directory +!cd "${PACKAGE_WORKDIR}" + +; use unicode +Unicode true + +; use best compressor +SetCompressor /FINAL /SOLID lzma +SetCompressorDictSize 64 +SetDatablockOptimize ON + +; set the default installation directory +!if "${PACKAGE_ARCH}" == "x64" + !define PROGRAMFILES $PROGRAMFILES64 + !define HKLM HKLM64 + !define HKCU HKCU64 +!else + !define PROGRAMFILES $PROGRAMFILES + !define HKLM HKLM + !define HKCU HKCU +!endif + +; request application privileges for Windows Vista +RequestExecutionLevel user + +; set DPI aware +ManifestDPIAware true + +; set icon +!if "${PACKAGE_ICONFILE}" != "" + !define MUI_ICON "${PACKAGE_ICONFILE}" +!endif + +; UAC +!macro Init thing + uac_tryagain: + !insertmacro UAC_RunElevated + ${Switch} $0 + ${Case} 0 + ${IfThen} $1 = 1 ${|} Quit ${|} ;we are the outer process, the inner process has done its work, we are done + ${IfThen} $3 <> 0 ${|} ${Break} ${|} ;we are admin, let the show go on + ${If} $1 = 3 ;RunAs completed successfully, but with a non-admin user + MessageBox mb_YesNo|mb_IconExclamation|mb_TopMost|mb_SetForeground "This ${thing} requires admin privileges, try again" /SD IDNO IDYES uac_tryagain IDNO 0 + ${EndIf} + ;fall-through and die + ${Case} 1223 + MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "This ${thing} requires admin privileges, aborting!" + Quit + ${Case} 1062 + MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "Logon service not running, aborting!" + Quit + ${Default} + MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "Unable to elevate, error $0" + Quit + ${EndSwitch} + + ; The UAC plugin changes the error level even in the inner process, reset it. + ; note fix install exit code 1223 to 0 with slient /S + SetErrorLevel 0 + SetShellVarContext all +!macroend + +; add the install Pages +!insertmacro MUI_PAGE_WELCOME +!if "${PACKAGE_LICENSEFILE}" != "" + !insertmacro MUI_PAGE_LICENSE "${PACKAGE_LICENSEFILE}" +!endif +!insertmacro MUI_PAGE_COMPONENTS +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH + +; add uninstall Pages +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES +!insertmacro MUI_UNPAGE_FINISH + +; add languages +!insertmacro MUI_LANGUAGE "English" + +; set registry paths +!define RegUninstall "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PACKAGE_NAME}" + +; set product information +VIProductVersion "${VERSION}.0" +VIFileVersion "${VERSION}.0" +VIAddVersionKey /LANG=0 ProductName "${PACKAGE_NAME}" +VIAddVersionKey /LANG=0 Comments "${PACKAGE_DESCRIPTION}" +VIAddVersionKey /LANG=0 CompanyName "${PACKAGE_COMPANY}" +VIAddVersionKey /LANG=0 LegalCopyright "${PACKAGE_COPYRIGHT}" +VIAddVersionKey /LANG=0 FileDescription "${PACKAGE_NAME} Installer - v${VERSION}" +VIAddVersionKey /LANG=0 OriginalFilename "${PACKAGE_FILENAME}" +VIAddVersionKey /LANG=0 FileVersion "${VERSION_FULL}" +VIAddVersionKey /LANG=0 ProductVersion "${VERSION_FULL}" + +; helper functions +Function TrimQuote + Exch $R1 ; Original string + Push $R2 + +Loop: + StrCpy $R2 "$R1" 1 + StrCmp "$R2" "'" TrimLeft + StrCmp "$R2" "$\"" TrimLeft + StrCmp "$R2" "$\r" TrimLeft + StrCmp "$R2" "$\n" TrimLeft + StrCmp "$R2" "$\t" TrimLeft + StrCmp "$R2" " " TrimLeft + GoTo Loop2 +TrimLeft: + StrCpy $R1 "$R1" "" 1 + Goto Loop + +Loop2: + StrCpy $R2 "$R1" 1 -1 + StrCmp "$R2" "'" TrimRight + StrCmp "$R2" "$\"" TrimRight + StrCmp "$R2" "$\r" TrimRight + StrCmp "$R2" "$\n" TrimRight + StrCmp "$R2" "$\t" TrimRight + StrCmp "$R2" " " TrimRight + GoTo Done +TrimRight: + StrCpy $R1 "$R1" -1 + Goto Loop2 + +Done: + Pop $R2 + Exch $R1 +FunctionEnd + +; setup installer +Var BinDir +Var NoAdmin +Function .onInit + ${GetOptions} $CMDLINE "/NOADMIN" $NoAdmin + ${If} ${Errors} + !insertmacro Init "installer" + StrCpy $NoAdmin "false" + ${Else} + StrCpy $NoAdmin "true" + ${EndIf} + + ; get installation root directory + ${If} $InstDir == "" + ${If} $NoAdmin == "false" + ReadRegStr $R0 ${HKLM} ${RegUninstall} "InstallLocation" + ${Else} + ReadRegStr $R0 ${HKCU} ${RegUninstall} "InstallLocation" + ${EndIf} + ${If} $R0 != "" + Push $R0 + Call TrimQuote + Pop $R0 + StrCpy $InstDir $R0 + ${EndIf} + ${EndIf} + ${If} $InstDir == "" + StrCpy $InstDir "${PROGRAMFILES}\${PACKAGE_NAME}" + ${EndIf} + + ; get binary directory + StrCpy $BinDir "$InstDir\${PACKAGE_BINDIR}" + +FunctionEnd + +Section "${PACKAGE_NAME} (required)" InstallExeutable + + SectionIn RO + + ; set output path to the installation directory. + SetOutPath $InstDir + + ; add install commands + ${PACKAGE_INSTALLCMDS} + + ; add uninstaller + WriteUninstaller "uninstall.exe" + ; Write the uninstall keys for Windows + !macro AddReg RootKey + WriteRegStr ${RootKey} ${RegUninstall} "NoAdmin" "$NoAdmin" + WriteRegStr ${RootKey} ${RegUninstall} "DisplayName" "${PACKAGE_NAME} (${PACKAGE_ARCH})" + WriteRegStr ${RootKey} ${RegUninstall} "DisplayIcon" '"$InstDir\${PACKAGE_BINDIR}\${PACKAGE_FILENAME}"' ; TODO + WriteRegStr ${RootKey} ${RegUninstall} "Comments" "${PACKAGE_DESCRIPTION}" + WriteRegStr ${RootKey} ${RegUninstall} "Publisher" "${PACKAGE_COPYRIGHT}" + WriteRegStr ${RootKey} ${RegUninstall} "UninstallString" '"$InstDir\uninstall.exe"' + WriteRegStr ${RootKey} ${RegUninstall} "QuiteUninstallString" '"$InstDir\uninstall.exe" /S' + WriteRegStr ${RootKey} ${RegUninstall} "InstallLocation" $InstDir + WriteRegStr ${RootKey} ${RegUninstall} "HelpLink" "${PACKAGE_HOMEPAGE}" + WriteRegStr ${RootKey} ${RegUninstall} "URLInfoAbout" "${PACKAGE_HOMEPAGE}" + WriteRegStr ${RootKey} ${RegUninstall} "URLUpdateInfo" "${PACKAGE_HOMEPAGE}" + WriteRegDWORD ${RootKey} ${RegUninstall} "VersionMajor" ${PACKAGE_VERSION_MAJOR} + WriteRegDWORD ${RootKey} ${RegUninstall} "VersionMinor" ${PACKAGE_VERSION_MINOR} + WriteRegStr ${RootKey} ${RegUninstall} "DisplayVersion" ${VERSION_FULL} + WriteRegDWORD ${RootKey} ${RegUninstall} "NoModify" 1 + WriteRegDWORD ${RootKey} ${RegUninstall} "NoRepair" 1 + + ; write size to reg + ${GetSize} "$InstDir" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD ${RootKey} ${RegUninstall} "EstimatedSize" "$0" + !macroend + + ${If} $NoAdmin == "false" + !insertmacro AddReg ${HKLM} + ${Else} + !insertmacro AddReg ${HKCU} + ${EndIf} +SectionEnd + +; add the binary directory to %PATH% +Section "Add to PATH" InstallPath + ${If} $NoAdmin == "false" + ReadRegStr $R0 ${HKLM} "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "Path" + ${WordReplace} $R0 ";$BinDir" "" "+" $R1 + WriteRegExpandStr ${HKLM} "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "Path" "$R1;$BinDir" + ${Else} + ReadRegStr $R0 ${HKCU} "Environment" "Path" + ${WordReplace} $R0 ";$BinDir" "" "+" $R1 + WriteRegExpandStr ${HKCU} "Environment" "Path" "$R1;$BinDir" + ${EndIf} + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 +SectionEnd + +${PACKAGE_NSIS_INSTALL_SECTIONS} + +; define language strings +LangString DESC_InstallExeutable ${LANG_ENGLISH} "${PACKAGE_DESCRIPTION}" +LangString DESC_InstallPath ${LANG_ENGLISH} "Add ${PACKAGE_NAME} to PATH" +${PACKAGE_NSIS_INSTALL_DESCS} + +; assign language strings to sections +!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN +!insertmacro MUI_DESCRIPTION_TEXT ${InstallExeutable} $(DESC_InstallExeutable) +!insertmacro MUI_DESCRIPTION_TEXT ${InstallPath} $(DESC_InstallPath) +${PACKAGE_NSIS_INSTALL_DESCRIPTION_TEXTS} +!insertmacro MUI_FUNCTION_DESCRIPTION_END + +; setup uninstaller +Function un.onInit + ; check if we need uac + ReadRegStr $NoAdmin ${HKLM} ${RegUninstall} "NoAdmin" + IfErrors 0 +2 + ReadRegStr $NoAdmin ${HKCU} ${RegUninstall} "NoAdmin" + + ${IfNot} $NoAdmin == "true" + !insertmacro Init "uninstaller" + ${EndIf} + + ; get binary directory + StrCpy $BinDir "$InstDir\${PACKAGE_BINDIR}" + +FunctionEnd + +Section "Uninstall" + + ; add uninstall commands + ${PACKAGE_UNINSTALLCMDS} + + ; remove the binary directory from %Path% + ${If} $NoAdmin == "false" + DeleteRegKey ${HKLM} ${RegUninstall} + ReadRegStr $R0 ${HKLM} "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "Path" + ${WordReplace} $R0 ";$BinDir" "" "+" $R1 + WriteRegExpandStr ${HKLM} "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "Path" "$R1" + ${Else} + DeleteRegKey ${HKCU} ${RegUninstall} + ReadRegStr $R0 ${HKCU} "Environment" "Path" + ${WordReplace} $R0 ";$BinDir" "" "+" $R1 + WriteRegExpandStr ${HKCU} "Environment" "Path" "$R1" + ${EndIf} + + ; remove uninstall.exe + Delete "$InstDir\uninstall.exe" +SectionEnd + |
