Skip to content

Instantly share code, notes, and snippets.

@stbuehler
Created April 30, 2011 12:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stbuehler/949636 to your computer and use it in GitHub Desktop.
Save stbuehler/949636 to your computer and use it in GitHub Desktop.
palm-package recode in ruby
#!/usr/bin/env ruby
require 'optparse'
require 'tmpdir'
require 'json'
require 'fileutils'
require 'zlib'
require 'rubygems' rescue nil
require 'libarchive'
def readfile(filename)
data = File.read(filename)
# data.force_encoding('UTF-8')
data
end
def allfiles(dir)
Dir.glob(File.join(dir, '**', '*'), File::FNM_DOTMATCH).select { |i| not ['.', '..'].include?(File.basename(i)) }
end
def targz(outfilename, dir)
prefixlen = dir.length
prefixlen = prefixlen + 1 if dir[-1,1] != '/'
# Archive.write_open_filename(outfilename, Archive::COMPRESSION_GZIP, Archive::FORMAT_TAR_USTAR) do |ar|
mem = ''
Archive.write_open_memory(mem, Archive::COMPRESSION_GZIP, Archive::FORMAT_TAR_USTAR) do |ar|
allfiles(dir).each do |fn|
next unless File.file?(fn) || File.directory?(fn) || File.symlink?(fn)
ar.new_entry do |entry|
entry.copy_stat(fn)
entry.pathname = "./" + fn[prefixlen..-1]
entry.uid = 0
entry.gid = 0
entry.uname = "root"
entry.gname = "wheel"
ar.write_header(entry)
if File.file?(fn) then
ar.write_data(File.read(fn))
elsif File.directory?(fn)
elsif File.symlink?(fn)
ar.copy_symlink(fn)
end
end
end
end
mem
end
def ar_ipk(outfilename, entries)
File.open(outfilename, 'w') do |f|
f.write("!<arch>\n");
entries.each do |name, content|
if content then
f.write("%-16s%-12s%-6s%-6s%-8s%-10s%c%c" % [name, Time.now.to_i, 0, 0, '100644', content.length, 0x60, 0x0a])
f.write(content)
f.write("\n") if (1 == content.length % 2)
else
filename = name
name = File.basename(name)
s = File.stat(filename)
f.write("%-16s%-12s%-6s%-6s%-8s%-10s%c%c" % [name, s.mtime.to_i, 0, 0, '100644', s.size, 0x60, 0x0a])
f.write(File.read(filename))
f.write("\n") if (1 == s.size % 2)
end
end
end
end
class PackageSource
def initialize(options)
@options = options
@tmpdir = options[:tmpdir]
@controldir = File.join(@tmpdir, 'control')
@datadir = File.join(@tmpdir, 'data')
@source = options[:source]
@source = @source[0..-2] if @source[-1,1] == '/'
end
def makecontrol
control = "Package: #{@packagename}\nVersion: #{@packageversion}\nSecion: #{@packagesection}\nPriority: optional\nArchitecture: #{@arch}\nInstalled-Size: #{installedsize}\nMaintainer: #{@maintainer}\nDescription: #{@description}\n"
control += "webOS-Package-Format-Version: 2\nwebOS-Packager-Version: 2.0.0b64\n"
File.open(File.join(@controldir, 'control'), 'w') {|f| f.write(control) }
end
def pack
control = File.join(@tmpdir, 'control.tar.gz')
data = File.join(@tmpdir, 'data.tar.gz')
targz(control, @controldir)
targz(data, @datadir)
ar_ipk(@pkgfile, [ [ 'control.tar.gz', targz(control, @controldir) ], [ 'data.tar.gz', targz(data, @datadir) ], [ 'debian-binary', "2.0\n" ] ])
end
def build
Dir.mkdir(@controldir)
Dir.mkdir(@datadir)
pinfo = packageinfo
raise "No package name found" unless pinfo["id"]
raise "No package version found" unless pinfo["version"]
@packagename = pinfo["id"]
@packageversion = pinfo["version"]
@packagesection = pinfo["section"] || "misc"
@maintainer = pinfo["maintainer"] || "N/A <nobody@example.com>"
@description = pinfo["description"] || "This is a webOS application."
@arch = detectarch
@pkgfile = File.join(@options[:output], @packagename + "_" + @packageversion + "_" + @arch + ".ipk")
install
makecontrol
pack
end
def detectarch
"all"
end
def installedsize
(Dir[File.join(@datadir, '**')].select { |i| File.file?(i) }.map { |f| File.stat(f).size }.reduce { |s,x| s+x } || 0) / 1024
end
end
class PackageSimpleSource < PackageSource
def initialize(options)
super(options)
end
def packageinfo
@appinfo = JSON.parse(readfile(File.join(@source, 'appinfo.json')))
{
"id" => @appinfo["id"],
"version" => @appinfo["version"],
"section" => @appinfo["section"],
"description" => @appinfo["description"] || @appinfo["title"],
"maintainer" => @appinfo["maintainer"] || @appinfo["vendor"],
}
end
def install
files = allfiles(@source)
appdir = File.join(@datadir, "usr", "palm", "applications", @appinfo["id"])
FileUtils.mkdir_p(appdir)
files.each do |f|
dest = File.join(appdir, f[@source.length..-1])
if File.directory?(f) then
Dir.mkdir(dest)
elsif File.file?(f) then
FileUtils.cp f, dest
end
end
end
def self.detect(options)
return true if File.file?(File.join(options[:source], 'appinfo.json'))
subdirs = Dir[File.join(options[:source], '*', 'appinfo.json')].select { |i| File.file?(i) }
return false if subdirs.length != 1
options[:source] = File.dirname(subdirs[0])
return true
end
end
def main(options)
sources = [PackageSimpleSource]
source = sources.select { |s| s.detect(options) }.first
raise "Couldn't find valid source package" unless source
source = source.new(options)
source.build
end
options = { :source => '.', :output => '.' }
opt = OptionParser.new do |opts|
opts.banner = "Usage: ipkbuild [options] [source directory] [output directory]"
end
opt.parse!
options[:source] = ARGV[0] if ARGV.length > 0
options[:output] = ARGV[1] if ARGV.length > 1
Dir.mktmpdir { |dir|
options[:tmpdir] = dir
main(options)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment