Class: Aibika::AibikaBuilder

Inherits:
Object
  • Object
show all
Defined in:
lib/aibika/aibika_builder.rb

Overview

Utility class that produces the actual executable. Opcodes (createfile, mkdir etc) are added by invoking methods on an instance of AibikaBuilder.

Constant Summary collapse

Signature =
[0x41, 0xb6, 0xba, 0x4e].freeze
OP_END =
0
OP_CREATE_DIRECTORY =
1
OP_CREATE_FILE =
2
OP_CREATE_PROCESS =
3
OP_DECOMPRESS_LZMA =
4
OP_SETENV =
5
OP_POST_CREATE_PROCESS =
6
OP_ENABLE_DEBUG_MODE =
7
OP_CREATE_INST_DIRECTORY =
8

Instance Method Summary collapse

Constructor Details

#initialize(path, windowed) ⇒ AibikaBuilder

Returns a new instance of AibikaBuilder.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/aibika/aibika_builder.rb', line 19

def initialize(path, windowed)
  @paths = {}
  @files = {}
  File.open(path, 'wb') do |aibikafile|
    image = if windowed
              Aibika.stubwimage
            else
              Aibika.stubimage
            end

    Aibika.fatal_error 'Stub image not available' unless image
    aibikafile.write(image)
  end

  system Aibika.ediconpath, path, Aibika.icon_filename if Aibika.icon_filename

  opcode_offset = File.size(path)

  File.open(path, 'ab') do |aibikafile|
    tmpinpath = 'tmpin'

    @of = if Aibika.lzma_mode
            File.open(tmpinpath, 'wb')
          else
            aibikafile
          end

    if Aibika.debug
      Aibika.msg('Enabling debug mode in executable')
      aibikafile.write([OP_ENABLE_DEBUG_MODE].pack('V'))
    end

    createinstdir Aibika.debug_extract, !Aibika.debug_extract, Aibika.chdir_first

    yield(self)

    @of.close if Aibika.lzma_mode

    if Aibika.lzma_mode && !Aibika.inno_script
      tmpoutpath = 'tmpout'
      begin
        data_size = File.size(tmpinpath)
        Aibika.msg "Compressing #{data_size} bytes"
        system(Aibika.lzmapath, 'e', tmpinpath, tmpoutpath) or raise
        compressed_data_size = File.size?(tmpoutpath)
        aibikafile.write([OP_DECOMPRESS_LZMA, compressed_data_size].pack('VV'))
        IO.copy_stream(tmpoutpath, aibikafile)
      ensure
        File.unlink(@of.path) if File.exist?(@of.path)
        File.unlink(tmpoutpath) if File.exist?(tmpoutpath)
      end
    end

    aibikafile.write([OP_END].pack('V'))
    aibikafile.write([opcode_offset].pack('V')) # Pointer to start of opcodes
    aibikafile.write(Signature.pack('C*'))
  end

  return unless Aibika.inno_script

  begin
    iss = "#{File.read(Aibika.inno_script)}\n\n"

    iss << "[Dirs]\n"
    @paths.each_key do |p|
      iss << "Name: \"{app}/#{p}\"\n"
    end
    iss << "\n"

    iss << "[Files]\n"
    path_escaped = path.to_s.gsub('"', '""')
    iss << "Source: \"#{path_escaped}\"; DestDir: \"{app}\"\n"
    @files.each do |tgt, src|
      src_escaped = src.to_s.gsub('"', '""')
      target_dir_escaped = Pathname.new(tgt).dirname.to_s.gsub('"', '""')
      unless src_escaped =~ IGNORE_MODULE_NAMES
        iss << "Source: \"#{src_escaped}\"; DestDir: \"{app}/#{target_dir_escaped}\"\n"
      end
    end
    iss << "\n"

    Aibika.verbose_msg "### INNOSETUP SCRIPT ###\n\n#{iss}\n\n"

    f = File.open('aibikatemp.iss', 'w')
    f.write(iss)
    f.close

    iscc_cmd = ['iscc']
    iscc_cmd << '/Q' unless Aibika.verbose
    iscc_cmd << 'aibikatemp.iss'
    Aibika.msg 'Running InnoSetup compiler ISCC'
    result = system(*iscc_cmd)
    unless result
      case $CHILD_STATUS
      when 0 then raise 'ISCC reported success, but system reported error?'
      when 1 then raise 'ISCC reports invalid command line parameters'
      when 2 then raise 'ISCC reports that compilation failed'
      else raise 'ISCC failed to run. Is the InnoSetup directory in your PATH?'
      end
    end
  rescue Exception => e
    Aibika.fatal_error("InnoSetup installer creation failed: #{e.message}")
  ensure
    File.unlink('aibikatemp.iss') if File.exist?('aibikatemp.iss')
    File.unlink(path) if File.exist?(path)
  end
end

Instance Method Details

#close ⇒ Object



183
184
185
# File 'lib/aibika/aibika_builder.rb', line 183

def close
  @of.close
end

#createfile(src, tgt) ⇒ Object



153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/aibika/aibika_builder.rb', line 153

def createfile(src, tgt)
  return if @files[tgt]

  @files[tgt] = src
  src = Aibika.Pathname(src)
  tgt = Aibika.Pathname(tgt)
  ensuremkdir(tgt.dirname)
  str = File.open(src, 'rb', &:read)
  Aibika.verbose_msg "a #{showtempdir tgt}"
  return if Aibika.inno_script # InnoSetup will install the file with a [Files] statement

  @of << [OP_CREATE_FILE, tgt.to_native, str.size].pack('VZ*V')
  @of << str
end

#createinstdir(next_to_exe = false, delete_after = false, chdir_before = false) ⇒ Object



147
148
149
150
151
# File 'lib/aibika/aibika_builder.rb', line 147

def createinstdir(next_to_exe = false, delete_after = false, chdir_before = false)
  return if Aibika.inno_script # Creation of installation directory will be handled by InnoSetup

  @of << [OP_CREATE_INST_DIRECTORY, next_to_exe ? 1 : 0, delete_after ? 1 : 0, chdir_before ? 1 : 0].pack('VVVV')
end

#createprocess(image, cmdline) ⇒ Object



168
169
170
171
# File 'lib/aibika/aibika_builder.rb', line 168

def createprocess(image, cmdline)
  Aibika.verbose_msg "l #{showtempdir image} #{showtempdir cmdline}"
  @of << [OP_CREATE_PROCESS, image.to_native, cmdline].pack('VZ*Z*')
end

#ensuremkdir(tgt) ⇒ Object



137
138
139
140
141
142
143
144
145
# File 'lib/aibika/aibika_builder.rb', line 137

def ensuremkdir(tgt)
  tgt = Aibika.Pathname(tgt)
  return if tgt.path == '.'

  return if @paths[tgt.to_posix.downcase]

  ensuremkdir(tgt.dirname)
  mkdir(tgt)
end

#mkdir(path) ⇒ Object



127
128
129
130
131
132
133
134
135
# File 'lib/aibika/aibika_builder.rb', line 127

def mkdir(path)
  return if @paths[path.path.downcase]

  @paths[path.path.downcase] = true
  Aibika.verbose_msg "m #{showtempdir path}"
  return if Aibika.inno_script # The directory will be created by InnoSetup with a [Dirs] statement

  @of << [OP_CREATE_DIRECTORY, path.to_native].pack('VZ*')
end

#postcreateprocess(image, cmdline) ⇒ Object



173
174
175
176
# File 'lib/aibika/aibika_builder.rb', line 173

def postcreateprocess(image, cmdline)
  Aibika.verbose_msg "p #{showtempdir image} #{showtempdir cmdline}"
  @of << [OP_POST_CREATE_PROCESS, image.to_native, cmdline].pack('VZ*Z*')
end

#setenv(name, value) ⇒ Object



178
179
180
181
# File 'lib/aibika/aibika_builder.rb', line 178

def setenv(name, value)
  Aibika.verbose_msg "e #{name} #{showtempdir value}"
  @of << [OP_SETENV, name, value].pack('VZ*Z*')
end

#showtempdir(tdir) ⇒ Object



187
188
189
# File 'lib/aibika/aibika_builder.rb', line 187

def showtempdir(tdir)
  tdir.to_s.gsub(TEMPDIR_ROOT, '<tempdir>')
end