Skip to content

Commit fee4989

Browse files
authored
Merge pull request #7064 from xmake-io/appimage
Pack Appimage support
2 parents e7cd3c2 + b9bb083 commit fee4989

File tree

6 files changed

+302
-2
lines changed

6 files changed

+302
-2
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### New features
66

7+
* [#7064](https://github.com/xmake-io/xmake/pull/7064): Add appimage xpack format for Linux application packaging
78
* [#7062](https://github.com/xmake-io/xmake/pull/7062): Add dmg xpack format for macOS application packaging
89

910
## v3.0.5
@@ -2197,6 +2198,7 @@
21972198

21982199
### 新特性
21992200

2201+
* [#7064](https://github.com/xmake-io/xmake/pull/7064): 添加 appimage xpack 格式,支持 Linux 应用程序打包
22002202
* [#7062](https://github.com/xmake-io/xmake/pull/7062): 添加 dmg xpack 格式,支持 macOS 应用程序打包
22012203

22022204
## v3.0.5
23.8 KB
Loading

tests/plugins/pack/console/xmake.lua

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ target("foo")
1919
add_packages("zlib")
2020

2121
xpack("test")
22-
set_formats("nsis", "srpm", "rpm", "deb", "zip", "targz", "srczip", "srctargz", "runself", "wix", "dmg")
22+
set_formats("nsis", "srpm", "rpm", "deb", "zip", "targz", "srczip", "srctargz", "runself", "wix", "dmg", "appimage")
2323
set_title("hello")
2424
set_author("ruki <[email protected]>")
2525
set_description("A test installer.")
@@ -30,7 +30,6 @@ xpack("test")
3030
add_installfiles("src/(assets/*.png)", {prefixdir = "images"})
3131
add_sourcefiles("(src/**)")
3232
add_sourcefiles("xmake.lua")
33-
set_iconfile("src/assets/xmake.ico")
3433
add_components("LongPath")
3534

3635
on_load(function (package)
@@ -39,6 +38,13 @@ xpack("test")
3938
else
4039
package:set("basename", "test-$(plat)-$(arch)-v$(version)")
4140
end
41+
-- set icon file based on format (use PNG for appimage, ICO for other formats)
42+
local scriptdir = os.scriptdir()
43+
if package:format() == "appimage" then
44+
package:set("iconfile", path.join(scriptdir, "src/assets/xmake.png"))
45+
else
46+
package:set("iconfile", path.join(scriptdir, "src/assets/xmake.ico"))
47+
end
4248
end)
4349

4450
after_installcmd(function (package, batchcmds)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
--!A cross-platform build utility based on Lua
2+
--
3+
-- Licensed under the Apache License, Version 2.0 (the "License");
4+
-- you may not use this file except in compliance with the License.
5+
-- You may obtain a copy of the License at
6+
--
7+
-- http://www.apache.org/licenses/LICENSE-2.0
8+
--
9+
-- Unless required by applicable law or agreed to in writing, software
10+
-- distributed under the License is distributed on an "AS IS" BASIS,
11+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
-- See the License for the specific language governing permissions and
13+
-- limitations under the License.
14+
--
15+
-- Copyright (C) 2015-present, Xmake Open Source Community.
16+
--
17+
-- @author ruki
18+
-- @file find_appimagetool.lua
19+
--
20+
21+
-- imports
22+
import("lib.detect.find_program")
23+
import("lib.detect.find_programver")
24+
25+
-- find appimagetool
26+
--
27+
-- @param opt the argument options, e.g. {version = true}
28+
--
29+
-- @return program, version
30+
--
31+
-- @code
32+
--
33+
-- local appimagetool = find_appimagetool()
34+
-- local appimagetool, version = find_appimagetool({version = true})
35+
--
36+
-- @endcode
37+
--
38+
function main(opt)
39+
40+
-- only for linux
41+
if not is_host("linux") then
42+
return
43+
end
44+
45+
-- init options
46+
opt = opt or {}
47+
opt.envs = opt.envs or {}
48+
opt.envs.APPIMAGE_EXTRACT_AND_RUN = "1"
49+
50+
-- find program
51+
local program = find_program(opt.program or "appimagetool", opt)
52+
53+
-- find program version
54+
local version = nil
55+
if program and opt and opt.version then
56+
version = find_programver(program, opt)
57+
end
58+
return program, version
59+
end
60+
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
--!A cross-platform build utility based on Lua
2+
--
3+
-- Licensed under the Apache License, Version 2.0 (the "License");
4+
-- you may not use this file except in compliance with the License.
5+
-- You may obtain a copy of the License at
6+
--
7+
-- http://www.apache.org/licenses/LICENSE-2.0
8+
--
9+
-- Unless required by applicable law or agreed to in writing, software
10+
-- distributed under the License is distributed on an "AS IS" BASIS,
11+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
-- See the License for the specific language governing permissions and
13+
-- limitations under the License.
14+
--
15+
-- Copyright (C) 2015-present, Xmake Open Source Community.
16+
--
17+
-- @author ruki
18+
-- @file main.lua
19+
--
20+
21+
-- imports
22+
import("core.base.option")
23+
import("lib.detect.find_tool")
24+
import("private.action.require.impl.packagenv")
25+
import("private.action.require.impl.install_packages")
26+
import(".batchcmds")
27+
28+
-- handle icon file
29+
function _handle_icon(package, appdir, appname)
30+
local iconname = nil
31+
local iconfile = package:get("iconfile")
32+
if iconfile then
33+
local iconpath = path.absolute(iconfile)
34+
if not os.isfile(iconpath) then
35+
raise("icon file not found: %s", iconfile)
36+
end
37+
local ext = path.extension(iconpath)
38+
-- AppImage only supports .png, .svg, .xpm
39+
if ext ~= ".png" and ext ~= ".svg" and ext ~= ".xpm" then
40+
raise("appimage format only supports .png, .svg, or .xpm icon files, but got: %s", ext)
41+
end
42+
iconname = appname .. ext
43+
-- copy to AppDir root (required for AppImage)
44+
os.cp(iconpath, path.join(appdir, iconname))
45+
-- also copy to standard location
46+
local iconsdir = path.join(appdir, "usr", "share", "icons", "hicolor", "256x256", "apps")
47+
os.mkdir(iconsdir)
48+
os.cp(iconpath, path.join(iconsdir, iconname))
49+
end
50+
return iconname
51+
end
52+
53+
-- create desktop file
54+
function _create_desktop_file(package, appdir, appname, apptitle, appdescription, main_executable, iconname)
55+
local desktopfile = path.join(appdir, appname .. ".desktop")
56+
local icon_line = ""
57+
if iconname then
58+
icon_line = string.format("Icon=%s\n", appname)
59+
end
60+
local version_line = ""
61+
local version = package:version()
62+
if version then
63+
version_line = string.format("X-AppImage-Version=%s\n", version)
64+
end
65+
local desktop_content = string.format([[
66+
[Desktop Entry]
67+
Type=Application
68+
Name=%s
69+
Comment=%s
70+
Exec=usr/bin/%s
71+
%s%sCategories=Utility;
72+
]], apptitle, appdescription, main_executable, icon_line, version_line)
73+
io.writefile(desktopfile, desktop_content)
74+
end
75+
76+
-- create AppRun script
77+
function _create_apprun_script(appdir, main_executable)
78+
local apprun = path.join(appdir, "AppRun")
79+
local apprun_content = string.format([[
80+
#!/bin/bash
81+
HERE="$(dirname "$(readlink -f "${0}")")"
82+
exec "${HERE}/usr/bin/%s" "$@"
83+
]], main_executable)
84+
io.writefile(apprun, apprun_content)
85+
os.vrunv("chmod", {"+x", apprun})
86+
end
87+
88+
-- get the appimagetool
89+
function _get_appimagetool()
90+
91+
-- enter the environments of appimagetool
92+
local oldenvs = packagenv.enter("appimagetool")
93+
94+
-- find appimagetool
95+
local packages = {}
96+
local appimagetool = find_tool("appimagetool")
97+
if not appimagetool then
98+
table.join2(packages, install_packages("appimagetool"))
99+
end
100+
101+
-- enter the environments of installed packages
102+
for _, instance in ipairs(packages) do
103+
instance:envs_enter()
104+
end
105+
106+
-- we need to force detect and flush detect cache after loading all environments
107+
if not appimagetool then
108+
appimagetool = find_tool("appimagetool", {force = true})
109+
end
110+
assert(appimagetool, "appimagetool not found!")
111+
return appimagetool, oldenvs
112+
end
113+
114+
-- get main executable from package
115+
function _get_main_executable(package, usrdir)
116+
local main_executable = nil
117+
local main_executable_path = nil
118+
119+
-- try to find from targets first
120+
for _, target in ipairs(package:targets()) do
121+
if target:is_binary() then
122+
main_executable = target:basename()
123+
-- check if file exists in usr/bin
124+
local exec_path = path.join(usrdir, "bin", main_executable)
125+
if os.isfile(exec_path) then
126+
main_executable_path = exec_path
127+
break
128+
end
129+
end
130+
end
131+
132+
-- fallback: find in bindir
133+
if not main_executable_path then
134+
local bindir = package:bindir()
135+
if bindir and os.isdir(bindir) then
136+
-- find executable files in bindir using os.files callback
137+
os.files(path.join(bindir, "*"), function (file, isdir)
138+
if not isdir and os.isfile(file) and not os.islink(file) and os.isexec(file) then
139+
main_executable = path.filename(file)
140+
main_executable_path = path.join(usrdir, "bin", main_executable)
141+
if os.isfile(main_executable_path) then
142+
return false
143+
end
144+
end
145+
return true
146+
end)
147+
end
148+
end
149+
150+
return main_executable, main_executable_path
151+
end
152+
153+
-- pack appimage package
154+
function _pack_appimage(package, appimagetool)
155+
156+
-- check platform
157+
assert(package:is_plat("linux"), "appimage format only supports Linux platform!")
158+
159+
-- archive binary files
160+
batchcmds.get_installcmds(package):runcmds()
161+
for _, component in table.orderpairs(package:components()) do
162+
if component:get("default") ~= false then
163+
batchcmds.get_installcmds(component):runcmds()
164+
end
165+
end
166+
167+
-- get install root directory
168+
local rootdir = package:install_rootdir()
169+
assert(os.isdir(rootdir), "install root directory not found: %s", rootdir)
170+
171+
-- get output file
172+
local outputfile = package:outputfile()
173+
os.tryrm(outputfile)
174+
175+
-- create AppDir directory structure
176+
local builddir = package:builddir()
177+
local appdir = path.join(builddir, "AppDir")
178+
os.tryrm(appdir)
179+
os.mkdir(appdir)
180+
181+
-- copy files to AppDir/usr
182+
local usrdir = path.join(appdir, "usr")
183+
os.cp(rootdir, usrdir)
184+
185+
-- get main executable
186+
local main_executable, main_executable_path = _get_main_executable(package, usrdir)
187+
assert(main_executable and main_executable_path and os.isfile(main_executable_path),
188+
"main executable not found! Please ensure at least one binary target is added to xpack.")
189+
190+
-- get application name and title
191+
local appname = package:name()
192+
local apptitle = package:title() or appname
193+
local appdescription = package:description() or ""
194+
195+
-- handle icon file
196+
local iconname = _handle_icon(package, appdir, appname)
197+
198+
-- create desktop file
199+
_create_desktop_file(package, appdir, appname, apptitle, appdescription, main_executable, iconname)
200+
201+
-- create AppRun script
202+
_create_apprun_script(appdir, main_executable)
203+
204+
-- create AppImage using appimagetool
205+
os.vrunv(appimagetool, {appdir, outputfile}, {envs = {APPIMAGE_EXTRACT_AND_RUN = "1"}})
206+
207+
-- verify AppImage was created
208+
assert(os.isfile(outputfile), "generate %s failed!", outputfile)
209+
end
210+
211+
function main(package)
212+
213+
-- only for linux
214+
if not is_host("linux") then
215+
return
216+
end
217+
218+
cprint("packing %s .. ", package:outputfile())
219+
220+
-- get appimagetool
221+
local appimagetool, oldenvs = _get_appimagetool()
222+
223+
-- pack appimage package
224+
_pack_appimage(package, appimagetool.program)
225+
226+
-- done
227+
os.setenvs(oldenvs)
228+
end
229+

xmake/plugins/pack/xpack.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ function xpack:inputkind()
206206
zip = "binary",
207207
targz = "binary",
208208
dmg = "binary",
209+
appimage = "binary",
209210
srczip = "source",
210211
srctargz = "source",
211212
runself = "source",
@@ -226,6 +227,7 @@ function xpack:outputkind()
226227
zip = "binary",
227228
targz = "binary",
228229
dmg = "binary",
230+
appimage = "binary",
229231
srczip = "source",
230232
srctargz = "source",
231233
runself = "source",
@@ -376,6 +378,7 @@ function xpack:extension()
376378
zip = ".zip",
377379
targz = ".tar.gz",
378380
dmg = ".dmg",
381+
appimage = ".AppImage",
379382
srczip = ".zip",
380383
srctargz = ".tar.gz",
381384
runself = ".gz.run",

0 commit comments

Comments
 (0)