|
| 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 | + |
0 commit comments