Rewrite in Lua with LuaRocks

This commit is contained in:
Antonin Décimo
2016-01-21 00:48:04 +01:00
parent 48c3bd657c
commit 873934e526
14 changed files with 1210 additions and 0 deletions

44
.gitignore vendored Normal file
View File

@@ -0,0 +1,44 @@
### Lua ###
# LDoc
/doc
# Compiled Lua sources
luac.out
# luarocks build files
*.src.rock
*.zip
*.tar.gz
# Object files
*.o
*.os
*.ko
*.obj
*.elf
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
*.def
*.exp
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex

85
README.md Normal file
View File

@@ -0,0 +1,85 @@
# love-release
[![License](http://img.shields.io/badge/License-MIT-brightgreen.svg)](LICENSE)
[Lua][lua] 5.1 script that makes [LÖVE][love] game release easier (previously Bash script).
Automates LÖVE [Game Distribution][game_dist].
LÖVE [forum topic][forum_topic].
Available as a [LuaRocks][luarocks] [package][package].
## Features
love-release makes your LÖVE game release easier. It can create from your sources Windows executables, MacOS X applications, Debian packages and simple LÖVE files.
love-release creates only one LÖVE file in a release directory and keeps it synced with your sources.
love-release can extract its informations from the environment: it guesses your game's title from the directory where it's stored, selects by default the latest LÖVE version from the web or uses its latest bundled LÖVE version, then parses the `conf.lua` file to extract even more informations such as the real LÖVE version your project uses.
### Configuration
love-release prints to the command-line a Lua table containing the informations it uses to generate your project. These informations can be stored in your `conf.lua` file to be used later.
```lua
function love.conf(t)
t.releases = {
title = nil, -- The project title (string)
package = nil, -- The project command and package name (string)
loveVersion = nil, -- The project LÖVE version
version = nil, -- The project version
author = nil, -- Your name (string)
email = nil, -- Your email (string)
description = nil, -- The project description (string)
homepage = nil, -- The project homepage (string)
identifier = nil, -- The project Uniform Type Identifier (string)
releaseDirectory = nil, -- Where to store the project releases (string)
}
end
```
## Installation
### Dependencies
love-release is only installable through LuaRocks and highly depends on LuaRocks internal API. love-release is currently build on LuaRocks 2.3.0. LuaRocks API is not meant to be stable, and a future update could break love-release. As love-release is made for LÖVE, it is written for Lua 5.1.
#### Required
- [libzip][libzip] headers for lua-zip.
- [lua-zip][lua-zip] has no official stable version, thus while available on LuaRocks it must be installed manually.
- Other libraries are automatically installed, but let's give them some credit: [luafilesystem][lfs], [loadconf][loadconf], [middleclass][middleclass], [semver][semver].
#### Optional
- `love` can be used to determine your system LÖVE version.
- `fakeroot` and `dpkg-deb` are required to create Debian packages.
### Install
```sh
# sudo
luarocks install --server=http://luarocks.org/dev lua-zip
luarocks install --server=http://luarocks.org/dev love-release
```
### Remove Bash version
You may have previously installed the Bash version of love-release. You can remove it with the following piece of code. Take the time to assure yourself that the paths are correct and match your installation of love-release.
```sh
rm -rf '/usr/bin/love-release'
rm -rf '/usr/share/love-release'
rm -rf '/usr/share/man/man1/love-release.1.gz'
rm -rf '/usr/share/bash-completion/completions/love-release' '/etc/bash_completion.d/love-release'
```
## Contribute
The documentation of love-release internals is written with [LDoc][ldoc]. Generate it by running `ldoc .`.
I do not plan to keep developing the Bash script, not even fixing it. If there appears to be any need for it, let me know and I might consider doing so.
Every bug report or feature request is gladly welcome !
[forum_topic]: https://love2d.org/forums/viewtopic.php?t=75387
[game_dist]: https://www.love2d.org/wiki/Game_Distribution
[ldoc]: https://github.com/stevedonovan/LDoc
[lfs]: https://github.com/keplerproject/luafilesystem
[libzip]: http://www.nih.at/libzip/
[love]: https://www.love2d.org/
[lua]: http://www.lua.org/
[luarocks]: https://luarocks.org/
[lua-zip]: https://github.com/brimworks/lua-zip
[loadconf]: https://github.com/Alloyed/loadconf
[middleclass]: https://github.com/kikito/middleclass
[package]: https://luarocks.org/modules/rucikir/love-release
[semver]: https://github.com/kikito/semver.lua

8
config.ld Normal file
View File

@@ -0,0 +1,8 @@
file = "src"
project = "love-release"
description = [[
love-release - a Lua script to make LÖVE games releases easier
]]
readme = "README.md"
format = "discount"
title = "love-release documentation"

View File

@@ -0,0 +1,42 @@
package = "love-release"
version = "scm-1"
source = {
url = "git://github.com/MisterDA/love-release.git",
branch = "lua",
}
description = {
summary = "Make LÖVE games releases easier",
detailed = [[
love-release make LÖVE games releases easier.
It automates LÖVE Game Distribution.
]],
license = "MIT",
homepage = "https://github.com/MisterDA/love-release",
}
dependencies = {
"loadconf",
"lua ~> 5.1",
"luafilesystem",
"lua-zip",
"middleclass",
"semver",
}
build = {
type = "builtin",
modules = {
["love-release.scripts.debian"] = "src/scripts/debian.lua",
["love-release.scripts.love"] = "src/scripts/love.lua",
["love-release.scripts.macosx"] = "src/scripts/macosx.lua",
["love-release.scripts.windows"] = "src/scripts/windows.lua",
["love-release.pipes.conf"] = "src/pipes/conf.lua",
["love-release.pipes.env"] = "src/pipes/env.lua",
["love-release.project"] = "src/project.lua",
["love-release.script"] = "src/script.lua",
["love-release.utils"] = "src/utils.lua",
},
install = {
bin = {
["love-release"] = "src/main.lua"
},
},
}

20
src/main.lua Normal file
View File

@@ -0,0 +1,20 @@
--- love-release main.
-- @script love-release
local conf = require 'love-release.pipes.conf'
local env = require 'love-release.pipes.env'
local Project = require 'love-release.project'
local p = Project:new()
conf(env(p))
print(p)
local script
script = require 'love-release.scripts.love'
script(p)
script = require 'love-release.scripts.macosx'
script(p)
script = require 'love-release.scripts.windows'
script(p)
script = require 'love-release.scripts.debian'
script(p)

78
src/pipes/conf.lua Normal file
View File

@@ -0,0 +1,78 @@
--- Gather informations from the LÖVE conf.lua file.
-- @module conf
-- @usage conf(project)
local fs = require 'luarocks.fs'
local loadconf = require 'loadconf'
local semver = require 'semver'
local utils = require 'love-release.utils'
local pipe = {}
function pipe.pipe(project)
local err = utils.io.err
-- checks for a conf.lua file
fs.change_dir(project.projectDirectory)
if not fs.exists("conf.lua") then
err("CONF: No conf.lua provided.\n")
return project
end
local conf = assert(loadconf.parse_file("conf.lua"))
fs.pop_dir()
local function setString(key, value)
if type(value) == "string" then
project["set"..key](project, value)
end
end
local function setLoveVersion(v)
if type(v) == "string" and v ~= "" then
local version = semver(v)
if not utils.love.isSupported(version) then
local scriptLoveVersion = project.loveVersion
err("CONF: Your LÖVE conf version ("..v
.. ") is not supported by love-release ("..tostring(scriptLoveVersion)
.. ").\n")
if version > scriptLoveVersion then
err(" You should update love-release.\n")
elseif version < scriptLoveVersion then
err(" You should update your project.\n")
end
end
project:setLoveVersion(version)
end
end
-- extract LÖVE standard fields
setString("Title", conf.title)
setString("Package", conf.package)
setLoveVersion(conf.version)
-- extract love-release fields
local releases = conf.releases
if type(releases) == "table" then
setString("Title", releases.title)
setString("Package", releases.package)
setLoveVersion(releases.loveVersion)
setString("Version", releases.version)
setString("Author", releases.author)
setString("Email", releases.email)
setString("Description", releases.description)
setString("Homepage", releases.homepage)
setString("Identifier", releases.identifier)
setString("ReleaseDirectory", releases.releaseDirectory)
end
return project
end
setmetatable(pipe, {
__call = function(_, project) return pipe.pipe(project) end,
})
return pipe

115
src/pipes/env.lua Normal file
View File

@@ -0,0 +1,115 @@
--- Gather informations from the environment.
-- @module env
-- @usage env(project)
local fs = require 'luarocks.fs'
local semver = require 'semver'
local utils = require 'love-release.utils'
local pipe = {}
--- Gets the version of the installed LÖVE.
-- @treturn semver LÖVE version.
-- @local
local function getSystemLoveVersion()
local handle = io.popen('love --version')
local result = handle:read("*a")
handle:close()
local version = result:match('%d+%.%d+%.%d+')
if version then
return semver(version)
end
end
--- Gets the latest LÖVE version from the web.
-- @treturn semver LÖVE version.
-- @local
local function getWebLoveVersion()
local releasesPath = utils.cache.."/releases.xml"
local ok, err = fs.download("https://love2d.org/releases.xml",
releasesPath,
true)
if ok then
local releasesXml = io.open(releasesPath, "rb")
local version = releasesXml:read("*a"):match("<title>(%d+%.%d+%.%d+)")
releasesXml:close()
return semver(version)
else
return nil, err
end
end
--- Gets the latest LÖVE version from the script, the system and the web.
-- @tparam semver script script version.
-- @tparam semver system system version.
-- @tparam semver web web version.
-- @treturn semver the latest version.
-- @local
local function getLatestLoveVersion(script, system, web)
local version = script
if system and system >= script then
version = system
end
if web and web > version then
version = web
end
return version
end
function pipe.pipe(project)
local err = utils.io.err
-- checks for a main.lua file
fs.change_dir(project.projectDirectory)
if not fs.exists("main.lua") then
err("ENV: No main.lua provided.\n")
os.exit(1)
end
fs.pop_dir()
-- title
project:setTitle(project.projectDirectory:match("[^/]+$"))
-- package
project:setPackage(project.title:gsub("%W", "-"):lower())
-- LÖVE version
local systemLoveVersion = getSystemLoveVersion()
local webLoveVersion = getWebLoveVersion()
local scriptLoveVersion = utils.love.lastVersion()
local isSupported = utils.love.isSupported
if systemLoveVersion and not isSupported(systemLoveVersion) then
err("ENV: Your LÖVE installed version (" .. tostring(systemLoveVersion) ..
") is not supported by love-release (" .. tostring(scriptLoveVersion) ..
").\n")
if systemLoveVersion > scriptLoveVersion then
err(" You should update love-release.\n")
elseif systemLoveVersion < scriptLoveVersion then
err(" You should update LÖVE.\n")
end
end
if webLoveVersion and not isSupported(webLoveVersion) then
err("ENV: The upstream LÖVE version (" .. tostring(webLoveVersion) ..
") is not supported by love-release (" .. tostring(scriptLoveVersion) ..
").\n")
err(" You should update love-release.\n")
end
project:setLoveVersion(getLatestLoveVersion(scriptLoveVersion,
systemLoveVersion,
webLoveVersion))
return project
end
setmetatable(pipe, {
__call = function(_, project) return pipe.pipe(project) end,
})
return pipe

272
src/project.lua Normal file
View File

@@ -0,0 +1,272 @@
--- Provides tools to manipulate a LÖVE project.
-- @classmod project
local fs = require 'luarocks.fs'
local lr_dir = require 'luarocks.dir'
local class = require 'middleclass'
local utils = require 'love-release.utils'
local Project = class('Project')
--- Title of this project.
Project.title = nil
--- Package name. It's the title converted to lowercase, with alpha-numerical
-- characters and hyphens only.
Project.package = nil
--- LÖVE version the project uses.
Project.loveVersion = nil
--- Version.
Project.version = nil
--- Author full name.
Project.author = nil
--- Email.
Project.email = nil
--- Description.
Project.description = nil
--- Homepage URL.
Project.homepage = nil
--- Uniform Type Identifier in reverse-DNS format.
Project.identifier = nil
--- Project directory, where to find the game sources.
Project.projectDirectory = nil
--- Project release directory, where to store the releases.
Project.releaseDirectory = nil
Project._fileTree = nil
Project._fileList = nil
function Project:initialize()
local defaultDirectory = fs.current_dir()
self:setProjectDirectory(defaultDirectory)
self:setReleaseDirectory(defaultDirectory.."/releases")
end
--- Recursive function used to build the tree.
-- @local
local _buildFileTree
_buildFileTree = function(dir)
local subDir
for file in assert(fs.dir()) do
if not file:find("^%.git") then
if fs.is_dir(file) then
subDir = {}
dir[file] = subDir
assert(fs.change_dir(file))
_buildFileTree(subDir, file)
assert(fs.pop_dir())
elseif fs.is_file(file) then
dir[#dir+1] = file
end
end
end
end
--- Constructs the file tree.
-- @return File tree. The table represents the root directory.
-- Sub-directories are represented as sub-tables, indexed by the directory name.
-- Files are strings stored in each sub-tables.
function Project:fileTree()
if not self._fileTree then
assert(fs.change_dir(self.projectDirectory))
self._fileTree = {}
_buildFileTree(self._fileTree)
assert(fs.pop_dir())
end
return self._fileTree
end
--- Recursive function used to build the file list.
-- @local
local _buildFileList
_buildFileList = function(list, tree, dir)
for k, v in pairs(tree) do
if type(v) == "table" then
list[#list+1] = dir..k.."/"
_buildFileList(list, tree[k], dir..k.."/")
elseif type(v) == "string" then
list[#list+1] = dir..v
end
end
end
--- Constructs the file list.
-- @bool build Rebuild the file tree.
-- @treturn table List of this project's files.
function Project:fileList(build)
if not self._fileList or build then
self._fileList = {}
_buildFileList(self._fileList, self:fileTree(), "")
self:excludeFiles()
end
return self._fileList
end
--- Excludes files from the LÖVE file.
-- @todo This function should be able to parse and use CVS files such as
-- gitignore. It should also work on the file tree rather than on the file list.
-- For now it only exludes the release directory if it is within the project
-- directory and works on the file list.
function Project:excludeFiles()
local dir = self.releaseDirectory:gsub(
"^"..utils.lua.escape_string_regex(self.projectDirectory).."/",
"")
if dir then
local n = #self._fileList
for i = 1, n do
if self._fileList[i]:find("^"..dir) then
self._fileList[i] = nil
end
end
end
end
--[[
-- File tree traversal
local function deep(tree)
for k, v in pairs(tree) do
if type(v) == "string" then
print(v)
elseif type(v) == "table" then
print(k)
deep(v)
end
end
end
deep(t)
--]]
local function escape(var)
if type(var) == "string" then
return "'"..var:gsub("'", "\'").."'"
else
return tostring(var)
end
end
--- Prints debug informations.
-- @local
function Project:__tostring()
return
'{\n'..
' title = '..escape(self.title)..',\n'..
' package = '..escape(self.package)..',\n'..
' loveVersion = \''..escape(self.loveVersion)..'\',\n'..
' version = '..escape(self.version)..',\n'..
' author = '..escape(self.author)..',\n'..
' email = '..escape(self.email)..',\n'..
' description = '..escape(self.description)..',\n'..
' homepage = '..escape(self.homepage)..',\n'..
' identifier = '..escape(self.identifier)..',\n'..
' projectDirectory = '..escape(self.projectDirectory)..',\n'..
' releaseDirectory = '..escape(self.releaseDirectory)..',\n'..
'}'
end
--- Sets the title.
-- @string title the title.
-- @treturn project self.
function Project:setTitle(title)
self.title = title
return self
end
--- Sets the package name.
-- @string package the package name.
-- @treturn project self.
function Project:setPackage(package)
self.package = package
return self
end
--- Sets the LÖVE version used.
-- @tparam semver version the LÖVE version.
-- @treturn project self.
function Project:setLoveVersion(version)
self.loveVersion = version
return self
end
--- Sets the project's version.
-- @string version the version.
-- @treturn project self.
function Project:setVersion(version)
self.version = version
return self
end
--- Sets the author.
-- @string author the author.
-- @treturn project self.
function Project:setAuthor(author)
self.author = author
return self
end
--- Sets the author's email.
-- @string email the email.
-- @treturn project self.
function Project:setEmail(email)
self.email = email
return self
end
--- Sets the description.
-- @string description the description.
-- @treturn project self.
function Project:setDescription(description)
self.description = description
return self
end
--- Sets the homepage.
-- @string homepage the homepage.
-- @treturn project self.
function Project:setHomepage(homepage)
self.homepage = homepage
return self
end
--- Sets the identifier.
-- @string identifier the identifier.
-- @treturn project self.
function Project:setIdentifier(identifier)
self.identifier = identifier
return self
end
--- Sets the source directory. The path is normalized and absoluted.
-- @string directory the directory.
-- @treturn project self.
function Project:setProjectDirectory(directory)
directory = fs.absolute_name(lr_dir.normalize(directory))
assert(fs.change_dir(directory))
fs.pop_dir()
self.projectDirectory = directory
return self
end
--- Sets the release directory. The path is normalized and absoluted.
-- @string directory the directory.
-- @treturn project self.
function Project:setReleaseDirectory(directory)
directory = fs.absolute_name(lr_dir.normalize(directory))
assert(fs.make_dir(directory))
assert(fs.change_dir(directory))
fs.pop_dir()
self.releaseDirectory = directory
return self
end
return Project

87
src/script.lua Normal file
View File

@@ -0,0 +1,87 @@
--- A love-release script.
-- @classmod script
local fs = require 'luarocks.fs'
local class = require 'middleclass'
local lfs = require "lfs"
local zip = require 'brimworks.zip'
local utils = require 'love-release.utils'
local Script = class('Script')
--- Current project.
Script.project = nil
--- Name of the LÖVE file.
Script.loveFile = nil
local function validate(project)
local valid, err = true, utils.io.err
if type(project.title) ~= "string" or project.title == "" then
err("SCRIPT: No title specified.\n")
valid = false
end
if type(project.package) ~= "string" or project.package == "" then
err("SCRIPT: No package specified.\n")
valid = false
end
if not type(project.loveVersion) then
err("SCRIPT: No LÖVE version specified.\n")
valid = false
end
if not valid then os.exit(1) end
return project
end
function Script:initialize(project)
self.project = validate(project)
self.loveFile = project.title..'.love'
end
--- Creates a LÖVE file in the release directory of the current project.
function Script:createLoveFile()
local ar = assert(zip.open(self.project.releaseDirectory.."/"..self.loveFile,
zip.OR(zip.CREATE, zip.CHECKCONS)))
assert(fs.change_dir(self.project.projectDirectory))
local attributes, stat
for _, file in ipairs(self.project:fileList()) do
attributes = assert(lfs.attributes(file))
stat = ar:stat(file)
-- file is not present in the filesystem nor the archive
if not attributes and not stat then
utils.io.err("BUILD: "..file.." is not present in the file system.\n")
-- file is not present in the archive
elseif attributes and not stat then
utils.io.out("Add "..file.."\n")
if attributes.mode == "directory" then
ar:add_dir(file)
else
ar:add(file, "file", file)
end
-- file in the filesystem is more recent than in the archive
elseif attributes and stat and attributes.modification > stat.mtime + 5 then
if attributes.mode == "file" then
utils.io.out("Update "..file.."\n")
ar:replace(assert(ar:name_locate(file)), "file", file)
end
end
end
for i = 1, #ar do
local file = ar:stat(i).name
-- file is present in the archive, but not in the filesystem
if not lfs.attributes(file) then
utils.io.out("Delete "..file.."\n")
ar:delete(i)
end
end
ar:close()
assert(fs.pop_dir())
end
return Script

138
src/scripts/debian.lua Normal file
View File

@@ -0,0 +1,138 @@
--- Debian package release.
-- @module scripts.debian
-- @usage debian(project)
local fs = require 'luarocks.fs'
local dir = require 'luarocks.dir'
local lfs = require 'lfs'
local Script = require 'love-release.script'
local utils = require 'love-release.utils'
local s = {}
local function validate(project)
local valid, err = true, utils.io.err
if type(project.author) ~= "string" or project.author == "" then
err("DEBIAN: No author specified.\n")
valid = false
end
if type(project.description) ~= "string" or project.description == "" then
err("DEBIAN: No description specified.\n")
valid = false
end
if type(project.email) ~= "string" or project.email == "" then
err("DEBIAN: No email specified.\n")
valid = false
end
if type(project.homepage) ~= "string" or project.homepage == "" then
err("DEBIAN: No homepage specified.\n")
valid = false
end
if type(project.version) ~= "string" or project.version == "" then
err("DEBIAN: No version specified.\n")
valid = false
end
if not valid then os.exit(1) end
return project
end
-- Is it such a good design to load the Debian package into memory with
-- temporary files ?
function s.script(project)
local ok1, err1 = fs.is_tool_available("fakeroot", "fakeroot", "-v")
local ok2, err2 = fs.is_tool_available("dpkg-deb", "dpkg-deb")
if not ok1 or not ok2 then
if not ok1 then utils.io.err(err1) end
if not ok2 then utils.io.err(err2) end
os.exit(1)
end
local script = Script:new(validate(project))
script:createLoveFile()
local tempDir = assert(fs.make_temp_dir("debian"))
local loveFileDeb = "/usr/share/games/"..project.package.."/"..script.loveFile
local loveFileRel = project.releaseDirectory.."/"..script.loveFile
local md5sums = {}
local function writeFile(path, content, md5)
local fullPath = tempDir..path
assert(fs.make_dir(dir.dir_name(fullPath)))
local file = assert(io.open(fullPath, "wb"))
file:write(content)
file:close()
if md5 then
md5sums[#md5sums+1] = { path = path, md5 = assert(fs.get_md5(fullPath)) }
end
end
local function copyFile(orig, dest, md5)
local fullPath = tempDir..dest
assert(fs.make_dir(dir.dir_name(fullPath)))
assert(fs.copy(orig, fullPath))
if md5 then
md5sums[#md5sums+1] = { path = dest, md5 = assert(fs.get_md5(fullPath)) }
end
end
-- /DEBIAN/control
writeFile("/DEBIAN/control",
"Package: "..project.package.."\n"..
"Version: "..project.version.."\n"..
"Architecture: all\n"..
"Maintainer: "..project.author.." <"..project.email..">\n"..
"Installed-Size: "..
math.floor(assert(lfs.attributes(loveFileRel, "size")) / 1024).."\n"..
"Depends: love (>= "..tostring(project.loveVersion)..")\n"..
"Priority: extra\n"..
"Homepage: "..project.homepage.."\n"..
"Description: "..project.description.."\n"
)
-- /usr/share/applications/${PACKAGE}.desktop
writeFile("/usr/share/applications/"..project.package..".desktop",
"[Desktop Entry]\n"..
"Name="..project.title.."\n"..
"Comment="..project.description.."\n"..
"Exec="..project.package.."\n"..
"Type=Application\n"..
"Categories=Game;\n",
true
)
-- /usr/bin/${PACKAGE}
writeFile("/usr/bin/"..project.package,
"#!/bin/sh\n"..
"love "..loveFileDeb.."\n",
true
)
-- /usr/share/games/${PACKAGE}/${LOVE_FILE}
copyFile(project.releaseDirectory.."/"..script.loveFile, loveFileDeb, true)
-- /DEBIAN/md5sums
local sum = ""
for _, v in ipairs(md5sums) do
sum = sum..v.md5.." "..v.path.."\n"
end
writeFile("/DEBIAN/md5sums", sum)
-- create the package
local deb = project.releaseDirectory.."/"..project.package.."-"..
project.version.."_all.deb"
fs.delete(deb)
assert(fs.execute("fakeroot dpkg-deb -b ", tempDir, deb),
"DEBIAN: error while building the package.")
fs.delete(tempDir)
end
setmetatable(s, {
__call = function(_, project) return s.script(project) end,
})
return s

20
src/scripts/love.lua Normal file
View File

@@ -0,0 +1,20 @@
--- LÖVE file release.
-- @module scripts.love
-- @usage love(project)
local Script = require "love-release.script"
local s = {}
function s.script(project)
local script = Script:new(project)
script:createLoveFile()
end
setmetatable(s, {
__call = function(_, project) return s.script(project) end,
})
return s

91
src/scripts/macosx.lua Normal file
View File

@@ -0,0 +1,91 @@
--- MacOS X app release.
-- @module scripts.macosx
-- @usage macosx(project)
local fs = require "luarocks.fs"
local semver = require "semver"
local zip = require "brimworks.zip"
local Script = require "love-release.script"
local utils = require "love-release.utils"
local s = {}
local function validate(project)
local valid, err = true, utils.io.err
if type(project.identifier) ~= "string" or project.identifier == "" then
err("DEBIAN: No author specified.\n")
valid = false
end
if not valid then os.exit(1) end
return project
end
function s.script(project)
local script = Script:new(validate(project))
script:createLoveFile()
fs.change_dir(project.releaseDirectory)
local prefix = "love-"..tostring(project.loveVersion).."-macosx-"
local bin
if project.loveVersion >= semver'0.9.0' then
bin = prefix.."x64.zip"
else
bin = prefix.."ub.zip"
end
local url = "https://bitbucket.org/rude/love/downloads/"..bin
local cache = utils.cache.."/"..bin
-- Can't cache the archive because luarocks functions use a HEAD request to
-- Amazon AWS which will answer a 403.
-- assert(fs.download(url, cache, true))
if not fs.exists(cache) then
assert(fs.download(url, cache))
end
fs.delete(bin)
assert(fs.copy(cache, bin))
-- local ar = assert(zip.open(bin, zip.OR(zip.CHECKCONS)))
local ar = zip.open(bin)
local infoPlistIndex = assert(ar:name_locate("love.app/Contents/Info.plist"))
local infoPlistSize = assert(ar:stat(infoPlistIndex).size)
local infoPlistHandle = assert(ar:open(infoPlistIndex))
local infoPlist = assert(infoPlistHandle:read(infoPlistSize))
infoPlistHandle:close()
infoPlist = infoPlist
:gsub("\n\t<key>UTExportedTypeDeclarations</key>.*</array>",
"")
:gsub("(CFBundleIdentifier.-<string>)(.-)(</string>)",
"%1"..project.identifier.."%3")
:gsub("(CFBundleName.-<string>)(.-)(</string>)",
"%1"..project.title..".love%3")
ar:add("love.app/Contents/Resources/"..script.loveFile,
"file", script.loveFile)
local app = project.title..".app"
for i = 1, #ar do
ar:rename(i, ar:stat(i).name:gsub("^love%.app", app))
end
ar:close()
-- for unknown reason, replacing the Info.plist content earlier would cause
-- random crashes
ar = zip.open(bin)
assert(ar:replace(infoPlistIndex, "string", infoPlist))
ar:close()
os.rename(bin, project.title.."-macosx.zip")
fs.pop_dir()
end
setmetatable(s, {
__call = function(_, project) return s.script(project) end,
})
return s

87
src/scripts/windows.lua Normal file
View File

@@ -0,0 +1,87 @@
--- Windows exe release.
-- @module scripts.windows
-- @usage windows(project)
local fs = require "luarocks.fs"
local semver = require "semver"
local zip = require "brimworks.zip"
local Script = require "love-release.script"
local utils = require "love-release.utils"
local s = {}
local function release(script, project, arch)
local prefix = "love-"..tostring(project.loveVersion).."-win"
local dir, bin
if project.loveVersion >= semver'0.9.0' then
bin = prefix..arch..".zip"
dir = prefix..arch.."/"
else
if arch == 32 then
bin = prefix.."-x86.zip"
dir = prefix.."-x86/"
elseif arch == 64 then
bin = prefix.."-x64.zip"
dir = prefix.."-x64/"
end
end
local url = "https://bitbucket.org/rude/love/downloads/"..bin
local cache = utils.cache.."/"..bin
-- Can't cache the archive because luarocks functions use a HEAD request to
-- Amazon AWS which will answer a 403.
-- assert(fs.download(url, cache, true))
if not fs.exists(cache) then
assert(fs.download(url, cache))
end
fs.delete(bin)
assert(fs.copy(cache, bin))
local gameHandle = assert(io.open(script.loveFile, "rb"))
local game = gameHandle:read("*a")
gameHandle:close()
-- local ar = assert(zip.open(bin, zip.OR(zip.CHECKCONS)))
local ar = zip.open(bin)
local exeHandle = assert(ar:open(dir.."love.exe"))
local exe = assert(exeHandle:read(assert(ar:stat(dir.."love.exe")).size))
exeHandle:close()
ar:add(dir..project.package..".exe", "string", exe..game)
ar:delete(dir.."love.exe")
local stat
for i = 1, #ar do
stat = ar:stat(i)
if stat then
ar:rename(i, stat.name:gsub(
"^"..utils.lua.escape_string_regex(dir),
utils.lua.escape_string_regex(project.title).."-win"..arch.."/"))
end
end
ar:close()
os.rename(bin, project.title.."-win"..arch..".zip")
end
function s.script(project)
local script = Script:new(project)
script:createLoveFile()
fs.change_dir(project.releaseDirectory)
release(script, project, 32)
if project.loveVersion >= semver'0.8.0' then
release(script, project, 64)
end
fs.pop_dir()
end
setmetatable(s, {
__call = function(_, project) return s.script(project) end,
})
return s

123
src/utils.lua Normal file
View File

@@ -0,0 +1,123 @@
--- Provides utility functions and constants.
-- @module utils
local cfg = require 'luarocks.cfg'
local fs = require 'luarocks.fs'
local semver = require 'semver'
local utils = {}
--[[ CACHE ]]--
--- Cache directory.
utils.cache = nil
do
local cache
if cfg.platforms.windows then
cache = os.getenv("APPDATA")
else
cache = os.getenv("HOME").."/.cache"
end
cache = fs.absolute_name(cache.."/love-release")
assert(fs.make_dir(cache))
utils.cache = cache
end
--[[ LÖVE VERSION ]]--
utils.love = {}
--- All supported LÖVE versions.
-- @local
utils.love.versionTable = {
semver'0.10.0',
semver'0.9.2', semver'0.9.1', semver'0.9.0',
semver'0.8.0',
semver'0.7.2', semver'0.7.1', semver'0.7.0',
semver'0.6.2', semver'0.6.1', semver'0.6.0',
--[[
semver'0.5.0',
semver'0.4.0',
semver'0.3.2', semver'0.3.1', semver'0.3.0',
semver'0.2.1', semver'0.2.0',
semver'0.1.1',
--]]
}
--- Last script LÖVE version.
function utils.love.lastVersion()
return utils.love.versionTable[1]
end
--- First supported LÖVE version.
function utils.love.minVersion()
return utils.love.versionTable[#utils.love.versionTable]
end
--- Checks if a LÖVE version exists and is supported.
-- @tparam semver version LÖVE version.
-- @treturn bool true is the version is supported.
function utils.love.isSupported(version)
if version >= utils.love.minVersion()
and version <= utils.love.lastVersion() then
for _, v in ipairs(utils.love.versionTable) do
if version == v then
return true
end
end
end
return false
end
--[[ LUA ]]--
utils.lua = {}
--- Compiles a file to LuaJIT bytecode.
-- @string file file path.
-- @treturn string bytecode.
function utils.lua.bytecode(file)
if package.loaded.jit then
return string.dump(assert(loadfile(file)), true)
else
local handle = io.popen('luajit -b '..file..' -')
local result = handle:read("*a")
handle:close()
return result
end
end
--- Escapes a string to use as a regex.
-- @string string to escape.
function utils.lua.escape_string_regex(string)
-- ^$()%.[]*+-?
return string:gsub('%%', '%%%%'):gsub('^%^', '%%^'):gsub('%$$', '%%$')
:gsub('%(', '%%('):gsub('%)', '%%)'):gsub('%.', '%%.')
:gsub('%[', '%%['):gsub('%]', '%%]'):gsub('%*', '%%*')
:gsub('%+', '%%+'):gsub('%-', '%%-'):gsub('%?', '%%?')
end
--[[ IO ]]--
local stdout = io.output(io.stdout)
local stderr = io.output(io.stderr)
utils.io = {}
--- Prints a message to stdout.
-- @string string the message.
function utils.io.out(string)
stdout:write(string)
end
--- Prints a message to stderr.
-- @string string the message.
function utils.io.err(string)
stderr:write(string)
end
return utils