Merge branch 'master' of ssh://webmasters.boum.org/~/wiki
[tails-test.git] / Rakefile
blobab51412d10aa9de250dff8d8f15cf0e730175f33
1 # -*- coding: utf-8 -*-
2 # -*- mode: ruby -*-
3 # vi: set ft=ruby :
5 # Tails: The Amnesic Incognito Live System
6 # Copyright © 2012 Tails developers <tails@boum.org>
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 require 'rbconfig'
22 require 'rubygems'
23 require 'vagrant'
24 require 'uri'
26 $:.unshift File.expand_path('../vagrant/lib', __FILE__)
27 require 'tails_build_settings'
28 require 'vagrant_version'
30 # Path to the directory which holds our Vagrantfile
31 VAGRANT_PATH = File.expand_path('../vagrant', __FILE__)
33 # Branches that are considered 'stable' (used to select SquashFS compression)
34 STABLE_BRANCH_NAMES = ['stable', 'testing']
36 # Environment variables that will be exported to the build script
37 EXPORTED_VARIABLES = ['http_proxy', 'MKSQUASHFS_OPTIONS', 'TAILS_RAM_BUILD', 'TAILS_CLEAN_BUILD', 'TAILS_BOOTSTRAP_CACHE']
39 # Let's save the http_proxy set before playing with it
40 EXTERNAL_HTTP_PROXY = ENV['http_proxy']
42 # In-VM proxy URL
43 INTERNEL_HTTP_PROXY = "http://#{VIRTUAL_MACHINE_HOSTNAME}:3142"
45 def primary_vm
46   env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
47   if vagrant_old
48     return env.primary_vm
49   else
50     name = env.primary_machine_name
51     return env.machine(name, env.default_provider)
52   end
53 end
55 def primary_vm_state
56   if vagrant_old
57     return primary_vm.state
58   else
59     return primary_vm.state.id
60   end
61 end
63 def current_vm_memory
64   vm = primary_vm
65   uuid = vm.uuid
66   info = vm.driver.execute 'showvminfo', uuid, '--machinereadable'
67   $1.to_i if info =~ /^memory=(\d+)/
68 end
70 def current_vm_cpus
71   vm = primary_vm
72   uuid = vm.uuid
73   info = vm.driver.execute 'showvminfo', uuid, '--machinereadable'
74   $1.to_i if info =~ /^cpus=(\d+)/
75 end
77 def vm_running?
78   primary_vm_state == :running
79 end
81 def enough_free_memory?
82   return false unless RbConfig::CONFIG['host_os'] =~ /linux/i
84   begin
85     usable_free_mem = `free`.split[16].to_i
86     usable_free_mem > VM_MEMORY_FOR_RAM_BUILDS * 1024
87   rescue
88     false
89   end
90 end
92 def is_release?
93   branch_name = `git name-rev --name-only HEAD`
94   tag_name = `git describe --exact-match HEAD 2> /dev/null`
95   STABLE_BRANCH_NAMES.include? branch_name.chomp or tag_name.chomp.length > 0
96 end
98 def system_cpus
99   return nil unless RbConfig::CONFIG['host_os'] =~ /linux/i
101   begin
102     File.read('/proc/cpuinfo').scan(/^processor\s+:/).count
103   rescue
104     nil
105   end
108 task :parse_build_options do
109   options = ''
111   # Default to in-memory builds if there is enough RAM available
112   options += 'ram ' if enough_free_memory?
114   # Use in-VM proxy unless an external proxy is set
115   options += 'vmproxy ' unless EXTERNAL_HTTP_PROXY
117   # Default to fast compression on development branches
118   options += 'gzipcomp ' unless is_release?
120   # Make sure release builds are clean
121   options += 'cleanall ' if is_release?
123   # Default to the number of system CPUs when we can figure it out
124   cpus = system_cpus
125   options += "cpus=#{cpus} " if cpus
127   options += ENV['TAILS_BUILD_OPTIONS'] if ENV['TAILS_BUILD_OPTIONS']
128   options.split(' ').each do |opt|
129     case opt
130     # Memory build settings
131     when 'ram'
132       unless vm_running? || enough_free_memory?
133         abort "Not enough free memory to do an in-memory build. Aborting."
134       end
135       ENV['TAILS_RAM_BUILD'] = '1'
136     when 'noram'
137       ENV['TAILS_RAM_BUILD'] = nil
138     # Bootstrap cache settings
139     when 'cache'
140       ENV['TAILS_BOOTSTRAP_CACHE'] = '1'
141     when 'nocache'
142       ENV['TAILS_BOOTSTRAP_CACHE'] = nil
143     # HTTP proxy settings
144     when 'extproxy'
145       abort "No HTTP proxy set, but one is required by TAILS_BUILD_OPTIONS. Aborting." unless EXTERNAL_HTTP_PROXY
146       ENV['http_proxy'] = EXTERNAL_HTTP_PROXY
147     when 'vmproxy'
148       ENV['http_proxy'] = INTERNEL_HTTP_PROXY
149     when 'noproxy'
150       ENV['http_proxy'] = nil
151     # SquashFS compression settings
152     when 'gzipcomp'
153       ENV['MKSQUASHFS_OPTIONS'] = '-comp gzip'
154     when 'defaultcomp'
155       ENV['MKSQUASHFS_OPTIONS'] = nil
156     # Clean-up settings
157     when 'cleanall'
158       ENV['TAILS_CLEAN_BUILD'] = '1'
159     # Virtual CPUs settings
160     when /cpus=(\d+)/
161       ENV['TAILS_BUILD_CPUS'] = $1
162     # Git settings
163     when 'ignorechanges'
164       ENV['TAILS_BUILD_IGNORE_CHANGES'] = '1'
165     end
166   end
169 task :ensure_clean_repository do
170   unless `git status --porcelain`.empty?
171     if ENV['TAILS_BUILD_IGNORE_CHANGES']
172       $stderr.puts <<-END_OF_MESSAGE.gsub(/^        /, '')
174         You have uncommited changes in the Git repository. They will
175         be ignored for the upcoming build.
177       END_OF_MESSAGE
178     else
179       $stderr.puts <<-END_OF_MESSAGE.gsub(/^        /, '')
181         You have uncommited changes in the Git repository. Due to limitations
182         of the build system, you need to commit them before building Tails.
184         If you don't care about those changes and want to build Tails nonetheless,
185         please add `ignorechanges` to the TAILS_BUILD_OPTIONS environment
186         variable.
188       END_OF_MESSAGE
189       abort 'Uncommited changes. Aborting.'
190     end
191   end
194 task :validate_http_proxy do
195   if ENV['http_proxy']
196     proxy_host = URI.parse(ENV['http_proxy']).host
198     if proxy_host.nil?
199       ENV['http_proxy'] = nil
200       $stderr.puts "Ignoring invalid HTTP proxy."
201       return
202     end
204     if ['localhost', '[::1]'].include?(proxy_host) || proxy_host.start_with?('127.0.0.')
205       abort 'Using an HTTP proxy listening on the loopback is doomed to fail. Aborting.'
206     end
208     $stderr.puts "Using HTTP proxy: #{ENV['http_proxy']}"
209   else
210     $stderr.puts "No HTTP proxy set."
211   end
214 desc 'Build Tails'
215 task :build => ['parse_build_options', 'ensure_clean_repository', 'validate_http_proxy', 'vm:up'] do
216   exported_env = EXPORTED_VARIABLES.select { |k| ENV[k] }.
217                   collect { |k| "#{k}='#{ENV[k]}'" }.join(' ')
218   if vagrant_old
219     chan = primary_vm.channel
220   else
221     chan = primary_vm.communicate
222   end
223   status = chan.execute("#{exported_env} build-tails",
224                                           :error_check => false) do |fd, data|
225     (fd == :stdout ? $stdout : $stderr).write data
226   end
228   # Move build products to the current directory
229   FileUtils.mv Dir.glob("#{VAGRANT_PATH}/tails-*"),
230                File.expand_path('..', __FILE__), :force => true
232   exit status
235 namespace :vm do
236   desc 'Start the build virtual machine'
237   task :up => ['parse_build_options', 'validate_http_proxy'] do
238     case primary_vm_state
239     when :not_created
240       # Do not use non-existant in-VM proxy to download the basebox
241       if ENV['http_proxy'] == INTERNEL_HTTP_PROXY
242         ENV['http_proxy'] = nil
243         restore_internal_proxy = true
244       end
246       $stderr.puts <<-END_OF_MESSAGE.gsub(/^      /, '')
248         This is the first time that the Tails builder virtual machine is
249         started. The virtual machine template is about 300 MB to download,
250         so the process might take some time.
252         Please remember to shut the virtual machine down once your work on
253         Tails is done:
255             $ rake vm:halt
257       END_OF_MESSAGE
258     when :poweroff
259       $stderr.puts <<-END_OF_MESSAGE.gsub(/^      /, '')
261         Starting Tails builder virtual machine. This might take a short while.
262         Please remember to shut it down once your work on Tails is done:
264             $ rake vm:halt
266       END_OF_MESSAGE
267     when :running
268       if ENV['TAILS_RAM_BUILD'] && current_vm_memory < VM_MEMORY_FOR_RAM_BUILDS
269         $stderr.puts <<-END_OF_MESSAGE.gsub(/^          /, '')
271           The virtual machine is not currently set with enough memory to
272           perform an in-memory build. Either remove the `ram` option from
273           the TAILS_BUILD_OPTIONS environment variable, or shut the
274           virtual machine down using `rake vm:halt` before trying again.
276         END_OF_MESSAGE
277         abort 'Not enough memory for the virtual machine to run an in-memory build. Aborting.'
278       end
279       if ENV['TAILS_BUILD_CPUS'] && current_vm_cpus != ENV['TAILS_BUILD_CPUS'].to_i
280         $stderr.puts <<-END_OF_MESSAGE.gsub(/^          /, '')
282           The virtual machine is currently running with #{current_vm_cpus}
283           virtual CPU(s). In order to change that number, you need to
284           stop the VM first, using `rake vm:halt`. Otherwise, please
285           adjust the `cpus` options accordingly.
287         END_OF_MESSAGE
288         abort 'The virtual machine needs to be reloaded to change the number of CPUs. Aborting.'
289       end
290     end
291     env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
292     result = env.cli('up')
293     abort "'vagrant up' failed" unless result
295     ENV['http_proxy'] = INTERNEL_HTTP_PROXY if restore_internal_proxy
296   end
298   desc 'Stop the build virtual machine'
299   task :halt do
300     env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
301     result = env.cli('halt')
302     abort "'vagrant halt' failed" unless result
303   end
305   desc 'Re-run virtual machine setup'
306   task :provision => ['parse_build_options', 'validate_http_proxy'] do
307     env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
308     result = env.cli('provision')
309     abort "'vagrant provision' failed" unless result
310   end
312   desc 'Destroy build virtual machine (clean up all files)'
313   task :destroy do
314     env = Vagrant::Environment.new(:cwd => VAGRANT_PATH, :ui_class => Vagrant::UI::Basic)
315     result = env.cli('destroy', '--force')
316     abort "'vagrant destroy' failed" unless result
317   end
320 namespace :basebox do
321   task :create_preseed_cfg => 'validate_http_proxy' do
322     require 'erb'
324     preseed_cfg_path = File.expand_path('../vagrant/definitions/squeeze/preseed.cfg', __FILE__)
325     template = ERB.new(File.read("#{preseed_cfg_path}.erb"))
326     File.open(preseed_cfg_path, 'w') do |f|
327       f.write template.result
328     end
329   end
331   desc 'Create virtual machine template (a.k.a. basebox)'
332   task :create_basebox => [:create_preseed_cfg] do
333     # veewee is pretty stupid regarding path handling
334     Dir.chdir(VAGRANT_PATH) do
335       require 'veewee'
337       # Veewee assumes a separate process for each task. So we mimic that.
339       env = Vagrant::Environment.new(:ui_class => Vagrant::UI::Basic)
341       Process.fork do
342         env.cli('basebox', 'build', 'squeeze')
343       end
344       Process.wait
345       abort "Building the basebox failed (exit code: #{$?.exitstatus})." if $?.exitstatus != 0
347       Process.fork do
348         env.cli('basebox', 'validate', 'squeeze')
349       end
350       Process.wait
351       abort "Validating the basebox failed (exit code: #{$?.exitstatus})." if $?.exitstatus != 0
353       Process.fork do
354         env.cli('basebox', 'export', 'squeeze')
355       end
356       Process.wait
357       abort "Exporting the basebox failed (exit code: #{$?.exitstatus})." if $?.exitstatus != 0
358     end
359   end