diff options
| author | ruki <[email protected]> | 2025-11-26 18:25:01 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-11-26 18:25:01 +0800 |
| commit | fee49894d216c926cd789ccf7883e220971c20ef (patch) | |
| tree | d76c02691ccd4edb2ea650f6606a5459494b1a66 | |
| parent | e7cd3c2c12d3d51ffef3d2aa9885bfef9db8d650 (diff) | |
| parent | b9bb0835067ae93417112cf15eb686a1d9c1f7da (diff) | |
Merge pull request #7064 from xmake-io/appimage
Pack Appimage support
| -rw-r--r-- | CHANGELOG.md | 2 | ||||
| -rw-r--r-- | tests/plugins/pack/console/src/assets/xmake.png | bin | 0 -> 24378 bytes | |||
| -rw-r--r-- | tests/plugins/pack/console/xmake.lua | 10 | ||||
| -rw-r--r-- | xmake/modules/detect/tools/find_appimagetool.lua | 60 | ||||
| -rw-r--r-- | xmake/plugins/pack/appimage/main.lua | 229 | ||||
| -rw-r--r-- | xmake/plugins/pack/xpack.lua | 3 |
6 files changed, 302 insertions, 2 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 43a99dff5..fc4611d7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### New features +* [#7064](https://github.com/xmake-io/xmake/pull/7064): Add appimage xpack format for Linux application packaging * [#7062](https://github.com/xmake-io/xmake/pull/7062): Add dmg xpack format for macOS application packaging ## v3.0.5 @@ -2197,6 +2198,7 @@ ### 新特性 +* [#7064](https://github.com/xmake-io/xmake/pull/7064): 添加 appimage xpack 格式,支持 Linux 应用程序打包 * [#7062](https://github.com/xmake-io/xmake/pull/7062): 添加 dmg xpack 格式,支持 macOS 应用程序打包 ## v3.0.5 diff --git a/tests/plugins/pack/console/src/assets/xmake.png b/tests/plugins/pack/console/src/assets/xmake.png Binary files differnew file mode 100644 index 000000000..be7f6c4f2 --- /dev/null +++ b/tests/plugins/pack/console/src/assets/xmake.png diff --git a/tests/plugins/pack/console/xmake.lua b/tests/plugins/pack/console/xmake.lua index 244bcaee6..6db4316c5 100644 --- a/tests/plugins/pack/console/xmake.lua +++ b/tests/plugins/pack/console/xmake.lua @@ -19,7 +19,7 @@ target("foo") add_packages("zlib") xpack("test") - set_formats("nsis", "srpm", "rpm", "deb", "zip", "targz", "srczip", "srctargz", "runself", "wix", "dmg") + set_formats("nsis", "srpm", "rpm", "deb", "zip", "targz", "srczip", "srctargz", "runself", "wix", "dmg", "appimage") set_title("hello") set_author("ruki <[email protected]>") set_description("A test installer.") @@ -30,7 +30,6 @@ xpack("test") add_installfiles("src/(assets/*.png)", {prefixdir = "images"}) add_sourcefiles("(src/**)") add_sourcefiles("xmake.lua") - set_iconfile("src/assets/xmake.ico") add_components("LongPath") on_load(function (package) @@ -39,6 +38,13 @@ xpack("test") else package:set("basename", "test-$(plat)-$(arch)-v$(version)") end + -- set icon file based on format (use PNG for appimage, ICO for other formats) + local scriptdir = os.scriptdir() + if package:format() == "appimage" then + package:set("iconfile", path.join(scriptdir, "src/assets/xmake.png")) + else + package:set("iconfile", path.join(scriptdir, "src/assets/xmake.ico")) + end end) after_installcmd(function (package, batchcmds) diff --git a/xmake/modules/detect/tools/find_appimagetool.lua b/xmake/modules/detect/tools/find_appimagetool.lua new file mode 100644 index 000000000..f19c9264d --- /dev/null +++ b/xmake/modules/detect/tools/find_appimagetool.lua @@ -0,0 +1,60 @@ +--!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, Xmake Open Source Community. +-- +-- @author ruki +-- @file find_appimagetool.lua +-- + +-- imports +import("lib.detect.find_program") +import("lib.detect.find_programver") + +-- find appimagetool +-- +-- @param opt the argument options, e.g. {version = true} +-- +-- @return program, version +-- +-- @code +-- +-- local appimagetool = find_appimagetool() +-- local appimagetool, version = find_appimagetool({version = true}) +-- +-- @endcode +-- +function main(opt) + + -- only for linux + if not is_host("linux") then + return + end + + -- init options + opt = opt or {} + opt.envs = opt.envs or {} + opt.envs.APPIMAGE_EXTRACT_AND_RUN = "1" + + -- find program + local program = find_program(opt.program or "appimagetool", opt) + + -- find program version + local version = nil + if program and opt and opt.version then + version = find_programver(program, opt) + end + return program, version +end + diff --git a/xmake/plugins/pack/appimage/main.lua b/xmake/plugins/pack/appimage/main.lua new file mode 100644 index 000000000..3ae3e5c99 --- /dev/null +++ b/xmake/plugins/pack/appimage/main.lua @@ -0,0 +1,229 @@ +--!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, Xmake Open Source Community. +-- +-- @author ruki +-- @file main.lua +-- + +-- imports +import("core.base.option") +import("lib.detect.find_tool") +import("private.action.require.impl.packagenv") +import("private.action.require.impl.install_packages") +import(".batchcmds") + +-- handle icon file +function _handle_icon(package, appdir, appname) + local iconname = nil + local iconfile = package:get("iconfile") + if iconfile then + local iconpath = path.absolute(iconfile) + if not os.isfile(iconpath) then + raise("icon file not found: %s", iconfile) + end + local ext = path.extension(iconpath) + -- AppImage only supports .png, .svg, .xpm + if ext ~= ".png" and ext ~= ".svg" and ext ~= ".xpm" then + raise("appimage format only supports .png, .svg, or .xpm icon files, but got: %s", ext) + end + iconname = appname .. ext + -- copy to AppDir root (required for AppImage) + os.cp(iconpath, path.join(appdir, iconname)) + -- also copy to standard location + local iconsdir = path.join(appdir, "usr", "share", "icons", "hicolor", "256x256", "apps") + os.mkdir(iconsdir) + os.cp(iconpath, path.join(iconsdir, iconname)) + end + return iconname +end + +-- create desktop file +function _create_desktop_file(package, appdir, appname, apptitle, appdescription, main_executable, iconname) + local desktopfile = path.join(appdir, appname .. ".desktop") + local icon_line = "" + if iconname then + icon_line = string.format("Icon=%s\n", appname) + end + local version_line = "" + local version = package:version() + if version then + version_line = string.format("X-AppImage-Version=%s\n", version) + end + local desktop_content = string.format([[ +[Desktop Entry] +Type=Application +Name=%s +Comment=%s +Exec=usr/bin/%s +%s%sCategories=Utility; +]], apptitle, appdescription, main_executable, icon_line, version_line) + io.writefile(desktopfile, desktop_content) +end + +-- create AppRun script +function _create_apprun_script(appdir, main_executable) + local apprun = path.join(appdir, "AppRun") + local apprun_content = string.format([[ +#!/bin/bash +HERE="$(dirname "$(readlink -f "${0}")")" +exec "${HERE}/usr/bin/%s" "$@" +]], main_executable) + io.writefile(apprun, apprun_content) + os.vrunv("chmod", {"+x", apprun}) +end + +-- get the appimagetool +function _get_appimagetool() + + -- enter the environments of appimagetool + local oldenvs = packagenv.enter("appimagetool") + + -- find appimagetool + local packages = {} + local appimagetool = find_tool("appimagetool") + if not appimagetool then + table.join2(packages, install_packages("appimagetool")) + 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 appimagetool then + appimagetool = find_tool("appimagetool", {force = true}) + end + assert(appimagetool, "appimagetool not found!") + return appimagetool, oldenvs +end + +-- get main executable from package +function _get_main_executable(package, usrdir) + local main_executable = nil + local main_executable_path = nil + + -- try to find from targets first + for _, target in ipairs(package:targets()) do + if target:is_binary() then + main_executable = target:basename() + -- check if file exists in usr/bin + local exec_path = path.join(usrdir, "bin", main_executable) + if os.isfile(exec_path) then + main_executable_path = exec_path + break + end + end + end + + -- fallback: find in bindir + if not main_executable_path then + local bindir = package:bindir() + if bindir and os.isdir(bindir) then + -- find executable files in bindir using os.files callback + os.files(path.join(bindir, "*"), function (file, isdir) + if not isdir and os.isfile(file) and not os.islink(file) and os.isexec(file) then + main_executable = path.filename(file) + main_executable_path = path.join(usrdir, "bin", main_executable) + if os.isfile(main_executable_path) then + return false + end + end + return true + end) + end + end + + return main_executable, main_executable_path +end + +-- pack appimage package +function _pack_appimage(package, appimagetool) + + -- check platform + assert(package:is_plat("linux"), "appimage format only supports Linux platform!") + + -- archive binary files + batchcmds.get_installcmds(package):runcmds() + for _, component in table.orderpairs(package:components()) do + if component:get("default") ~= false then + batchcmds.get_installcmds(component):runcmds() + end + end + + -- get install root directory + local rootdir = package:install_rootdir() + assert(os.isdir(rootdir), "install root directory not found: %s", rootdir) + + -- get output file + local outputfile = package:outputfile() + os.tryrm(outputfile) + + -- create AppDir directory structure + local builddir = package:builddir() + local appdir = path.join(builddir, "AppDir") + os.tryrm(appdir) + os.mkdir(appdir) + + -- copy files to AppDir/usr + local usrdir = path.join(appdir, "usr") + os.cp(rootdir, usrdir) + + -- get main executable + local main_executable, main_executable_path = _get_main_executable(package, usrdir) + assert(main_executable and main_executable_path and os.isfile(main_executable_path), + "main executable not found! Please ensure at least one binary target is added to xpack.") + + -- get application name and title + local appname = package:name() + local apptitle = package:title() or appname + local appdescription = package:description() or "" + + -- handle icon file + local iconname = _handle_icon(package, appdir, appname) + + -- create desktop file + _create_desktop_file(package, appdir, appname, apptitle, appdescription, main_executable, iconname) + + -- create AppRun script + _create_apprun_script(appdir, main_executable) + + -- create AppImage using appimagetool + os.vrunv(appimagetool, {appdir, outputfile}, {envs = {APPIMAGE_EXTRACT_AND_RUN = "1"}}) + + -- verify AppImage was created + assert(os.isfile(outputfile), "generate %s failed!", outputfile) +end + +function main(package) + + -- only for linux + if not is_host("linux") then + return + end + + cprint("packing %s .. ", package:outputfile()) + + -- get appimagetool + local appimagetool, oldenvs = _get_appimagetool() + + -- pack appimage package + _pack_appimage(package, appimagetool.program) + + -- done + os.setenvs(oldenvs) +end + diff --git a/xmake/plugins/pack/xpack.lua b/xmake/plugins/pack/xpack.lua index 1e39d6a83..bccb93cfa 100644 --- a/xmake/plugins/pack/xpack.lua +++ b/xmake/plugins/pack/xpack.lua @@ -206,6 +206,7 @@ function xpack:inputkind() zip = "binary", targz = "binary", dmg = "binary", + appimage = "binary", srczip = "source", srctargz = "source", runself = "source", @@ -226,6 +227,7 @@ function xpack:outputkind() zip = "binary", targz = "binary", dmg = "binary", + appimage = "binary", srczip = "source", srctargz = "source", runself = "source", @@ -376,6 +378,7 @@ function xpack:extension() zip = ".zip", targz = ".tar.gz", dmg = ".dmg", + appimage = ".AppImage", srczip = ".zip", srctargz = ".tar.gz", runself = ".gz.run", |
