Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/lib/shoes/pack.rb
diff options
context:
space:
mode:
authorIshan Bansal <ishan@seeta.in>2011-02-04 12:53:17 (GMT)
committer Ishan Bansal <ishan@seeta.in>2011-02-04 12:53:17 (GMT)
commitee852209b70c803e6ab1bb69b71c7dafcb1448e7 (patch)
treeac20673816893b398aed4c4da463b157d9e4899e /lib/shoes/pack.rb
Imported Upstream version 1HEADmaster
Diffstat (limited to 'lib/shoes/pack.rb')
-rw-r--r--lib/shoes/pack.rb543
1 files changed, 543 insertions, 0 deletions
diff --git a/lib/shoes/pack.rb b/lib/shoes/pack.rb
new file mode 100644
index 0000000..bb1b4d0
--- /dev/null
+++ b/lib/shoes/pack.rb
@@ -0,0 +1,543 @@
+#
+# lib/shoes/pack.rb
+# Packing apps into Windows, OS X and Linux binaries
+#
+require 'shoes/shy'
+require 'binject'
+require 'open-uri'
+
+class Shoes
+ module Pack
+ def self.rewrite a, before, hsh
+ File.open(before) do |b|
+ b.each do |line|
+ a << line.gsub(/\#\{(\w+)\}/) { hsh[$1] }
+ end
+ end
+ end
+
+ def self.pkg(platform, opt)
+ $stderr.puts "#{platform} #{opt}"
+ extension = case platform
+ when "win32" then
+ "exe"
+ when "linux" then
+ "run"
+ when "osx" then
+ "dmg"
+ else
+ raise "Unknown platform"
+ end
+
+ case opt
+ when Shoes::I_YES then
+ url = "http://shoes.heroku.com/pkg/#{Shoes::RELEASE_NAME.downcase}/#{platform}/shoes"
+ local_file_path = File.join(LIB_DIR, Shoes::RELEASE_NAME.downcase, platform, "latest_shoes.#{extension}")
+ when Shoes::I_NOV then
+ url = "http://shoes.heroku.com/pkg/#{Shoes::RELEASE_NAME.downcase}/#{platform}/shoes-novideo"
+ local_file_path = File.join(LIB_DIR, Shoes::RELEASE_NAME.downcase, platform, "latest_shoes-novideo.#{extension}")
+ when I_NET then
+ url = false
+ else
+ raise "missing download option #{opt}"
+ end
+
+ FileUtils.makedirs File.join(LIB_DIR, Shoes::RELEASE_NAME.downcase, platform)
+
+ if url then
+ begin
+ url = open(url).read.strip
+ debug url
+ internet_ok = true
+ rescue Exception => e
+ error e
+ internet_ok = false
+ end
+
+ if File.exists? local_file_path
+ return open(local_file_path)
+ elsif internet_ok then
+ begin
+ debug "Downloading #{url}..."
+ downloaded = open(url)
+ debug "Download of #{url} finished"
+ rescue Exception => e
+ error "Could not download from the internet at #{url}\n" + e
+ internet_ok = false
+ end
+ if internet_ok then
+ begin
+ File.open(local_file_path, "wb") do |f|
+ f.write(downloaded.read)
+ end
+ return open(local_file_path)
+ rescue Exception => e
+ raise "The download failed from\n#{url}\nor could not write to local files" + e
+ end
+ end
+ else
+ noHopeMsg = "Failed to find an existing Shoes at:\n#{local_file_path}\nor download from\n#{url} to include with your script."
+ raise noHopeMsg
+ end
+ end
+ end
+
+ def self.exe(script, opt, &blk)
+ size = File.size(script)
+ f = File.open(script, 'rb')
+ exe = Binject::EXE.new(File.join(DIR, "static", "stubs", "blank.exe"))
+ size += script.length
+ exe.inject("SHOES_FILENAME", File.basename(script))
+ size += File.size(script)
+ exe.inject("SHOES_PAYLOAD", f)
+ f2 = pkg("win32", opt)
+ if f2
+ size += File.size(f2.path)
+ f3 = File.open(f2.path, 'rb')
+ exe.inject("SHOES_SETUP", f3)
+
+ count, last = 0, 0.0
+ exe.save(script.gsub(/\.\w+$/, '') + ".exe") do |len|
+ count += len
+ prg = count.to_f / size.to_f
+ blk[last = prg] if blk and prg - last > 0.02 and prg < 1.0
+ end
+
+ f.close
+ f2.close
+ f3.close
+ else
+ Dir.chdir DIR + '/static/stubs' do
+ `.\\shoes-stub-inject.exe #{script.gsub('/', "\\")}`
+ end
+ end
+ blk[1.0] if blk
+ end
+
+ def self.dmg(script, opt, &blk)
+ name = File.basename(script).gsub(/\.\w+$/, '')
+ app_name = name.capitalize.gsub(/[-_](\w)/) { $1.capitalize }
+ vol_name = name.capitalize.gsub(/[-_](\w)/) { " " + $1.capitalize }
+ app_app = "#{app_name}.app"
+ vers = [1, 0]
+
+ tmp_dir = File.join(LIB_DIR, "+dmg")
+ FileUtils.rm_rf(tmp_dir)
+ FileUtils.mkdir_p(tmp_dir)
+ FileUtils.cp(File.join(DIR, "static", "stubs", "blank.hfz"),
+ File.join(tmp_dir, "blank.hfz"))
+ app_dir = File.join(tmp_dir, app_app)
+ res_dir = File.join(tmp_dir, app_app, "Contents", "Resources")
+ mac_dir = File.join(tmp_dir, app_app, "Contents", "MacOS")
+ [res_dir, mac_dir].map { |x| FileUtils.mkdir_p(x) }
+ FileUtils.cp(File.join(DIR, "static", "Shoes.icns"), app_dir)
+ FileUtils.cp(File.join(DIR, "static", "Shoes.icns"), res_dir)
+ File.open(File.join(app_dir, "Contents", "PkgInfo"), 'w') do |f|
+ f << "APPL????"
+ end
+ File.open(File.join(app_dir, "Contents", "Info.plist"), 'w') do |f|
+ f << <<END
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+<key>CFBundleGetInfoString</key>
+<string>#{app_name} #{vers.join(".")}</string>
+<key>CFBundleExecutable</key>
+<string>#{name}-launch</string>
+<key>CFBundleIdentifier</key>
+<string>org.hackety.#{name}</string>
+<key>CFBundleName</key>
+<string>#{app_name}</string>
+<key>CFBundleIconFile</key>
+<string>Shoes.icns</string>
+<key>CFBundleShortVersionString</key>
+<string>#{vers.join(".")}</string>
+<key>CFBundleInfoDictionaryVersion</key>
+<string>6.0</string>
+<key>CFBundlePackageType</key>
+<string>APPL</string>
+<key>IFMajorVersion</key>
+<integer>#{vers[0]}</integer>
+<key>IFMinorVersion</key>
+<integer>#{vers[1]}</integer>
+</dict>
+</plist>
+END
+ end
+ File.open(File.join(app_dir, "Contents", "version.plist"), 'w') do |f|
+ f << <<END
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+<key>BuildVersion</key>
+<string>1</string>
+<key>CFBundleVersion</key>
+<string>#{vers.join(".")}</string>
+<key>ProjectName</key>
+<string>#{app_name}</string>
+<key>SourceVersion</key>
+<string>#{Time.now.strftime("%Y%m%d")}</string>
+</dict>
+</plist>
+END
+ end
+ File.open(File.join(mac_dir, "#{name}-launch"), 'w') do |f|
+ f << <<END
+#!/bin/bash
+SHOESPATH=/Applications/Shoes.app/Contents/MacOS
+APPPATH="${0%/*}"
+unset DYLD_LIBRARY_PATH
+cd "$APPPATH"
+echo "[Pango]" > /tmp/pangorc
+echo "ModuleFiles=$SHOESPATH/pango.modules" >> /tmp/pangorc
+if [ ! -d /Applications/Shoes.app ]
+ then ./cocoa-install
+ fi
+ open -a /Applications/Shoes.app "#{File.basename(script)}"
+ # DYLD_LIBRARY_PATH=$SHOESPATH PANGO_RC_FILE="$APPPATH/pangorc" $SHOESPATH/shoes-bin "#{File.basename(script)}"
+END
+ end
+ FileUtils.cp(script, File.join(mac_dir, File.basename(script)))
+ FileUtils.cp(File.join(DIR, "static", "stubs", "cocoa-install"),
+ File.join(mac_dir, "cocoa-install"))
+
+ dmg = Binject::DMG.new(File.join(tmp_dir, "blank.hfz"), vol_name)
+ f2 = pkg("osx", opt)
+ if f2
+ dmg.grow(10)
+ dmg.inject_file("setup.dmg", f2.path)
+ end
+ dmg.inject_dir(app_app, app_dir)
+ dmg.chmod_file(0755, "#{app_app}/Contents/MacOS/#{name}-launch")
+ dmg.chmod_file(0755, "#{app_app}/Contents/MacOS/cocoa-install")
+ dmg.save(script.gsub(/\.\w+$/, '') + ".dmg") do |perc|
+ blk[perc * 0.01] if blk
+ end
+ FileUtils.rm_rf(tmp_dir)
+ blk[1.0] if blk
+ end
+
+ def self.linux(script, opt, &blk)
+ name = File.basename(script).gsub(/\.\w+$/, '')
+ app_name = name.capitalize.gsub(/[-_](\w)/) { $1.capitalize }
+ run_path = script.gsub(/\.\w+$/, '') + ".run"
+ tgz_path = script.gsub(/\.\w+$/, '') + ".tgz"
+ tmp_dir = File.join(LIB_DIR, "+run")
+ FileUtils.mkdir_p(tmp_dir)
+ pkgf = pkg("linux", opt)
+ prog = 1.0
+ if pkgf
+ size = Shy.hrun(pkgf)
+ pblk = Shy.progress(size) do |name, perc, left|
+ blk[perc * 0.5]
+ end if blk
+ Shy.xzf(pkgf, tmp_dir, &pblk)
+ prog -= 0.5
+ end
+
+ FileUtils.cp(script, File.join(tmp_dir, File.basename(script)))
+ File.open(File.join(tmp_dir, "sh-install"), 'wb') do |a|
+ rewrite a, File.join(DIR, "static", "stubs", "sh-install"),
+ 'SCRIPT' => "./#{File.basename(script)}"
+ end
+ FileUtils.chmod 0755, File.join(tmp_dir, "sh-install")
+
+ raw = Shy.du(tmp_dir)
+ File.open(tgz_path, 'wb') do |f|
+ pblk = Shy.progress(raw) do |name, perc, left|
+ blk[prog + (perc * prog)]
+ end if blk
+ Shy.czf(f, tmp_dir, &pblk)
+ end
+
+ md5, fsize = Shy.md5sum(tgz_path), File.size(tgz_path)
+ File.open(run_path, 'wb') do |f|
+ rewrite f, File.join(DIR, "static", "stubs", "blank.run"),
+ 'CRC' => '0000000000', 'MD5' => md5, 'LABEL' => app_name, 'NAME' => name,
+ 'SIZE' => fsize, 'RAWSIZE' => (raw / 1024) + 1, 'TIME' => Time.now, 'FULLSIZE' => raw
+ File.open(tgz_path, 'rb') do |f2|
+ f.write f2.read(8192) until f2.eof
+ end
+ end
+ FileUtils.chmod 0755, run_path
+ FileUtils.rm_rf(tgz_path)
+ FileUtils.rm_rf(tmp_dir)
+ blk[1.0] if blk
+ end
+ end
+
+ Shoes::I_NET = "No, download Shoes if it's absent."
+ Shoes::I_YES = "Yes, I want Shoes included."
+ Shoes::I_NOV = "Yes, include Shoes, but without video support."
+ PackMake = proc do
+ background "#DDD"
+
+ @page1 = stack do
+ stack do
+ background white
+ background "#FFF".."#EEE", :height => 50, :bottom => 50
+ border "#CCC", :height => 2, :bottom => 0
+ stack :margin => 20 do
+ selt = proc { @sel1.toggle; @sel2.toggle }
+ @path = ""
+ @shy_path = nil
+ @sel1 =
+ flow do
+ para "File to package:"
+ inscription " (or a ", link("directory", &selt), ")"
+ edit1 = edit_line :width => -120
+ @bb = button "Browse...", :width => 100 do
+ @path = edit1.text = ask_open_file
+ #est_recount
+ end
+ end
+ @sel2 =
+ flow :hidden => true do
+ para "Directory:"
+ inscription " (or a ", link("single file", &selt), ")"
+ edit2 = edit_line :width => -120
+ @bf = button "Folder...", :width => 100 do
+ @path = edit2.text = ask_open_folder
+ #est_recount
+ end
+ end
+
+ para "Packaging options"
+ para "Should Shoes be included with your script or should the script ",
+ "download Shoes when the user runs it? Not all options are available on all ",
+ "systems. The defaults work."
+ flow :margin_left => 20 do
+ @shy = check
+ para "Shoes (.shy) for users who have Shoes already", :margin_right => 20
+ end
+ items = [Shoes::I_NET, Shoes::I_YES, Shoes::I_NOV]
+ items.shift unless ::RUBY_PLATFORM =~ /mswin|mingw/
+ flow :margin_left => 20 do
+ flow :width => 0.25 do
+ @exe = check
+ para "Windows"
+ end
+ @incWin = list_box :items => items, :width => 0.6, :height => 30, do
+ @downOpt = @incWin.text
+ est_recount
+ end
+ @incWin.choose items[0]
+ end
+ flow :margin_left => 20 do
+ flow :width => 0.25 do
+ @dmg = check
+ para "OS X", :margin_right => 47
+ end
+ osxop = [Shoes::I_NET, Shoes::I_NOV]
+ @incOSX = list_box :items => osxop, :width => 0.6, :height => 30 do
+ @downOpt = @incOSX.text
+ est_recount
+ end
+ @incOSX.choose(Shoes::I_NOV)
+ end
+ flow :margin_left => 20 do
+ flow :width => 0.25 do
+ @run = check
+ para "Linux", :margin_right => 49
+ end
+ @incLinux = list_box :items => [Shoes::I_NET], :width => 0.6,
+ :height => 30 do
+ est_recount
+ end
+ @incLinux.choose(Shoes::I_NET)
+ end
+ end
+ end
+
+ stack :margin => 20 do
+ @est = para "Estimated size of your choice: ", strong("0k"), :margin => 0, :margin_bottom => 4
+ def est_recount
+ base =
+ case @downOpt
+ when Shoes::I_NET; 98
+ when Shoes::I_YES; 11600
+ when Shoes::I_NOV; 7000
+ end
+ base += ((File.directory?(@path) ? Shy.du(@path) : File.size(@path)) rescue 0) / 1024
+ @est.replace "Estimated size of each app: ", strong(base > 1024 ?
+ "%0.1fM" % [base / 1024.0] : "#{base}K")
+ end
+ def build_thread
+ @shy_path = nil
+ if File.directory? @path
+ @shy_path = @path.gsub(%r![\\/]+$!, '') + ".shy"
+ elsif @shy.style[:checked]
+ @shy_path = @path.gsub(/\.\w+$/, '') + ".shy"
+ end
+ if @shy_path and not @shy_meta
+ @page_shy.show
+ @shy_para.text = File.basename(@shy_path)
+ @shy_launch.items = Shy.launchable(@path)
+ return
+ end
+ @page2.show
+ @path2.replace File.basename(@path)
+ #inc_text = @inc.text
+ inc_win_text, inc_osx_text, inc_linux_text = @incWin.text, @incOSX.text, @incLinux.text
+ Thread.start do
+ begin
+ sofar, stage = 0.0, 1.0 / [@shy.style[:checked], @exe.style[:checked], @dmg.style[:checked], @run.style[:checked]].
+ select { |x| x }.size
+ blk = proc do |frac|
+ @prog.style(:width => sofar + (frac * stage))
+ end
+
+ if @shy_path
+ @status.replace "Compressing the script's folder."
+ pblk = Shy.progress(Shy.du(@path)) do |name, perc, left|
+ blk[perc]
+ end
+ Shy.c(@shy_path, @shy_meta, @path, &pblk)
+ @path = @shy_path
+ @prog.style(:width => sofar += stage)
+ end
+ if @exe.style[:checked]
+ @status.replace "Working on an .exe for Windows."
+ Shoes::Pack.exe(@path, inc_win_text, &blk)
+ @prog.style(:width => sofar += stage)
+ end
+ if @dmg.style[:checked]
+ @status.replace "Working on a .dmg for Mac OS X."
+ Shoes::Pack.dmg(@path, inc_osx_text, &blk)
+ @prog.style(:width => sofar += stage)
+ end
+ if @run.style[:checked]
+ @status.replace "Working on a .run for Linux."
+ Shoes::Pack.linux(@path, inc_linux_text, &blk)
+ @prog.style(:width => sofar += stage)
+ end
+ if @shy_path and not @shy.style[:checked]
+ FileUtils.rm_rf(@shy_path)
+ end
+
+ every do
+ if @prog.style[:width] == 1.0
+ @page2.hide
+ @page3.show
+ @path3.replace File.basename(@path)
+ end
+ end
+ rescue => e
+ @packErrMsg = e
+ # weirdness begins
+ @page2.hide
+ @path3.style :font => 'italic', :size => 12
+ @page3.show
+ @path3.replace @packErrMsg
+ end
+ end
+ end
+
+ inscription "Using the latest Shoes build (0.r#{Shoes::REVISION})", :margin => 0
+ flow :margin_top => 10, :margin_left => 310 do
+ button "OK", :margin_right => 4 do
+ @page1.hide; @bb.hide; @bf.hide
+ @packErrMsg = nil
+ build_thread
+ end
+ button "Cancel" do
+ close
+ end
+ end
+ end
+ end
+
+ @page_shy = stack :hidden => true do
+ stack do
+ background white
+ border "#DDD", :height => 2, :bottom => 0
+ stack :margin => 20 do
+ para "Details for:", :margin => 4
+ @shy_para = para "", :size => 20, :margin => 4
+ flow do
+ stack :margin => 10, :width => 0.4 do
+ para "Name of app:"
+ @shy_name = edit_line :width => 1.0
+ end
+ stack :margin => 10, :width => 0.4 do
+ para "Version:"
+ @shy_version = edit_line :width => 120
+ end
+ stack :margin => 10, :width => 0.4 do
+ para "Creator"
+ @shy_creator = edit_line :width => 1.0
+ end
+ stack :margin => 10, :width => 0.5 do
+ para "Launch"
+ @shy_launch = list_box :height => 30
+ end
+ end
+ end
+ end
+
+ stack :margin => 20 do
+ flow :margin_top => 10, :margin_left => 310 do
+ button "OK", :margin_right => 4 do
+ @shy_meta = Shy.new
+ @shy_meta.name = @shy_name.text
+ @shy_meta.creator = @shy_creator.text
+ @shy_meta.version = @shy_version.text
+ @shy_meta.launch = @shy_launch.text
+ @page_shy.hide
+ build_thread
+ end
+ button "Cancel" do
+ close
+ end
+ end
+ end
+ end
+
+ @page2 = stack :hidden => true do
+ stack do
+ background white
+ border "#DDD", :height => 2, :bottom => 0
+ stack :margin => 20 do
+ para "Packaging:", :margin => 4
+ @path2 = para "", :size => 20, :margin => 4
+ @status = para "", :margin => 4
+ end
+ end
+
+ stack :margin => 20 do
+ stack :width => -20, :height => 24 do
+ @prog = background "#{DIR}/static/stripe.png", :curve => 7
+ background "rgb(0, 0, 0, 100)".."rgb(120, 120, 120, 0)", :curve => 6, :height => 16
+ background "rgb(120, 120, 120, 0)".."rgb(0, 0, 0, 100)", :curve => 6,
+ :height => 16, :top => 8
+ border "rgb(60, 60, 60, 80)", :curve => 7, :strokewidth => 2
+ end
+ end
+ end
+
+ @page3 = stack :hidden => true do
+ stack do
+ background white
+ border "#DDD", :height => 2, :bottom => 0
+ stack :margin => 20 do
+ para "Completed:", :margin => 4
+ @path3 = para "", :size => 20, :margin => 4
+ para "Your files are done, you may close this window.", :margin => 4
+ button "Quit" do
+ exit
+ end
+ end
+ end
+ end
+
+ start do
+ @exe.checked = false
+ @dmg.checked = false
+ @run.checked = false
+ @shy.checked = true
+ #@inc.choose( ::RUBY_PLATFORM =~ /mswin|mingw/ ? Shoes::I_NET : Shoes::I_NOV )
+ end
+ end
+end