Last active
December 16, 2015 09:29
-
-
Save konstantinbe/5413428 to your computer and use it in GitHub Desktop.
Building Mac apps with Rake (no Xcode)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# Copyright (c) 2013 Konstantin Bender. | |
# | |
require 'rubygems' | |
# ------------------------------------------------------------- Constants ------ | |
NAME = ENV['name'] || "ExampleAppMac" | |
TARGET = ENV['target'] || "test" | |
VERSION = ENV['version'] || 'x.x.x' | |
DIRECTORY = ENV['directory'] || "Build" | |
APP = "ExampleApp" | |
NIB = "GMMainMenu" | |
OSX = ENV['osx'] || '10.9' | |
SDK_OSX = "macosx#{OSX}" | |
FLAGS = "-g -std=gnu99 -fobjc-arc -I#{DIRECTORY} -Wall -Wno-unused-function -Wno-unused-variable -Wno-missing-braces" | |
FLAGS_OSX = "-arch x86_64 -mmacosx-version-min=#{OSX}" | |
FLAGS_TEST = "-DTEST=1 -O0" | |
FLAGS_DEBUG = "-DDEBUG=1 -O0" | |
FLAGS_RELEASE ="-DRELEASE=1 -Os" | |
FLAGS_PROFILE = "#{FLAGS_DEBUG} -fprofile-arcs -ftest-coverage" | |
FLAGS_ANALYZE = "#{FLAGS_DEBUG} --analyze" | |
FLAGS_TARGET = FLAGS_TEST if TARGET == "test" | |
FLAGS_TARGET = FLAGS_DEBUG if TARGET == "debug" | |
FLAGS_TARGET = FLAGS_RELEASE if TARGET == "release" | |
FLAGS_TARGET = FLAGS_PROFILE if TARGET == "profile" | |
FLAGS_TARGET = FLAGS_ANALYZE if TARGET == "analyze" | |
FLAGS_TARGET = "" unless defined? FLAGS_TARGET | |
# ---------------------------------------------------------------- Colors ------ | |
TERMINAL = STDOUT.tty? | |
BOLD = TERMINAL ? "\x1B[0;1m" : "" | |
BLACK = TERMINAL ? "\x1B[0;30m" : "" | |
RED = TERMINAL ? "\x1B[0;31m" : "" | |
GREEN = TERMINAL ? "\x1B[0;32m" : "" | |
YELLOW = TERMINAL ? "\x1B[0;33m" : "" | |
BLUE = TERMINAL ? "\x1B[0;34m" : "" | |
MAGENTA = TERMINAL ? "\x1B[0;35m" : "" | |
CYAN = TERMINAL ? "\x1B[0;36m" : "" | |
WHITE = TERMINAL ? "\x1B[0;37m" : "" | |
BLACK_BRIGHT = TERMINAL ? "\x1B[1;30m" : "" | |
RED_BRIGHT = TERMINAL ? "\x1B[1;31m" : "" | |
GREEN_BRIGHT = TERMINAL ? "\x1B[1;32m" : "" | |
YELLOW_BRIGHT = TERMINAL ? "\x1B[1;33m" : "" | |
BLUE_BRIGHT = TERMINAL ? "\x1B[1;34m" : "" | |
MAGENTA_BRIGHT = TERMINAL ? "\x1B[1;35m" : "" | |
CYAN_BRIGHT = TERMINAL ? "\x1B[1;36m" : "" | |
WHITE_BRIGHT = TERMINAL ? "\x1B[1;37m" : "" | |
RESET = TERMINAL ? "\x1B[0m" : "" | |
OK = GREEN + "OK" + RESET | |
FAILED = RED + "FAILED" + RESET | |
# ----------------------------------------------------------------- Tasks ------ | |
desc "build source & app" | |
task :build => ['build:source', 'build:app'] do | |
# Nothing to do yet. | |
end | |
desc "build source" | |
task 'build:source' do | |
header_files = FileList["Source/*.h"] | |
source_files = FileList["Source/*.m"] | |
run "mkdir -p #{DIRECTORY}/Source" | |
run "rm -rf #{DIRECTORY}/Source/*.a" | |
put "Compiling source ... " | |
source_files.each do |source_file| | |
source = source_file | |
sources = [source_file] + header_files | |
destination = "#{DIRECTORY}/" + source_file.gsub('.m', '') | |
run "xcrun --sdk #{SDK_OSX} clang #{FLAGS} #{FLAGS_OSX} #{FLAGS_TARGET} -c -o #{destination}@OSX.o #{source}" unless uptodate? "#{destination}@OSX.o", sources | |
end | |
puts OK | |
end | |
desc "build app" | |
task 'build:app' => ['build:source'] do | |
put "Preparing app ... " | |
run "rm -rf #{DIRECTORY}/#{NAME}.app" | |
run "mkdir -p #{DIRECTORY}/#{APP}.app/Contents/MacOS" | |
run "mkdir -p #{DIRECTORY}/#{APP}.app/Contents/Resources" | |
run "xcrun --sdk #{SDK_OSX} clang #{FLAGS} #{FLAGS_OSX} #{FLAGS_TARGET} -framework Cocoa -framework QuartzCore -framework WebKit -o #{DIRECTORY}/#{APP}.app/Contents/MacOS/#{APP} #{DIRECTORY}/Source/*@OSX.o" | |
run "chmod +x #{DIRECTORY}/#{APP}.app/Contents/MacOS/#{APP}" | |
puts OK | |
put "Copying resources ... " | |
push "Resources", "#{DIRECTORY}/#{APP}.app/Contents/" | |
puts OK | |
put "Compiling UI ... " | |
source = "Assets/GMMainMenu.xib" | |
destination = "#{DIRECTORY}/#{APP}.app/Contents/Resources/#{NIB}.nib" | |
run "xcrun --sdk #{SDK_OSX} ibtool --compile #{destination} #{source}" unless uptodate? destination, [source] | |
puts OK | |
put "Copying Info.plist ... " | |
push "Assets/Info.plist", "#{DIRECTORY}/#{APP}.app/Contents/" | |
puts OK | |
put "Updating Info.plist ... " | |
# TODO: implement. | |
puts OK | |
end | |
desc "build & run app" | |
task :play do | |
run "rake build target=release directory=#{DIRECTORY}/Play" | |
put "Opening app ... " | |
run "open #{DIRECTORY}/Play/#{APP}.app" | |
puts OK | |
end | |
desc "build & analyze" | |
task "analyze" do | |
put "Preparing analysis ... " | |
run "rm -rf #{DIRECTORY}/Analyze" | |
puts OK | |
run "rake build:source target=analyze directory=#{DIRECTORY}/Analyze" | |
end | |
desc "release a version" | |
task :release do | |
if dirty? | |
result = ask "#{YELLOW}Warning#{RESET}: #{BOLD}repo is dirty, proceed anyway?#{RESET}" | |
if result == 'n' | |
puts "Release aborted, commit all changes and try again." | |
exit 1 | |
end | |
end | |
put "Preparing release ... " | |
run "rm -rf #{DIRECTORY}/Release" | |
run "mkdir -p #{DIRECTORY}/Release" | |
puts OK | |
run "rake build target=release directory=#{DIRECTORY}/Release version=#{VERSION}" | |
run "rake website directory=#{DIRECTORY}/Release version=#{VERSION}" | |
put "Preparing package ... " | |
run "rm -rf #{DIRECTORY}/Release/Package" | |
run "mkdir -p #{DIRECTORY}/Release/Package" | |
run "cp -r #{DIRECTORY}/Release/#{NAME} #{DIRECTORY}/Release/Package/" | |
run "cp -r #{DIRECTORY}/Release/Website #{DIRECTORY}/Release/Package/" | |
run "cp -r ReadMe.md #{DIRECTORY}/Release/Package/" | |
run "cd #{DIRECTORY}/Release/Package; git init --quiet; git add --all; git commit --quiet --message 'Package #{NAME} #{VERSION}'; git tag #{NAME}-#{VERSION}-Package" | |
puts OK | |
if VERSION.match /^(0\.0\.0)|(x\.x\.x)/ | |
puts "\nVersion is #{VERSION}, stopping here (dry run)" | |
exit 0 | |
end | |
put "Pushing package ... " | |
origin = `pwd`.strip | |
run "cd #{DIRECTORY}/Release/Package; git push --quiet --force #{origin} --tags" | |
puts OK | |
put "Tagging release ... " | |
run "git tag #{NAME}-#{VERSION}" | |
puts OK | |
end | |
desc "build website" | |
task :website => :prepare do | |
require 'redcarpet' if available? 'redcarpet' | |
markdown = Redcarpet::Markdown.new Redcarpet::Render::HTML, :autolink => true, :space_after_headers => true | |
put "Building website ... " | |
run "rm -rf #{DIRECTORY}/Website" | |
run "mkdir -p #{DIRECTORY}/Website" | |
run "cp Website/* #{DIRECTORY}/Website/" | |
index_html = load "Website/index.html" | |
content_md = load "Website/content.md" | |
index_html = index_html.gsub "<!-- content.md -->", markdown.render(content_md) | |
save "#{DIRECTORY}/Website/index.html", index_html | |
puts OK | |
end | |
desc "update all externals" | |
task :update do | |
put "Nothing to update ... " | |
puts OK | |
end | |
desc "create a build directory" | |
task :prepare do | |
run "mkdir -p #{DIRECTORY}" | |
end | |
desc "delete the build directory" | |
task :clean do | |
put "Cleaning ... " | |
run "rm -rf #{DIRECTORY}" | |
puts OK | |
end | |
# ------------------------------------------------------------- Functions ------ | |
def assert(condition, message = "") | |
unless condition | |
puts message + "\n\n" | |
exit 1 | |
end | |
end | |
def check(condition) | |
unless condition | |
puts FAILED | |
exit 1 | |
end | |
end | |
def run(command, options = {}) | |
success = system command | |
check success unless options[:silent] | |
end | |
def put(string) | |
print string | |
STDOUT.flush | |
end | |
def ask(message) | |
put message + " [y/n] " | |
answer = STDIN.getc | |
puts "\n" | |
if answer == 'y' || answer == 'n' then answer else ask message end | |
end | |
def invoke(task) | |
Rake::Task[task].invoke | |
end | |
def load(file_path) | |
File.open(file_path, "r") { |file| file.read } | |
end | |
def save(file_path, content) | |
File.open(file_path, "w") { |file| file.write content } | |
end | |
def push(source, destination) | |
run "rsync --archive --delete --quiet #{source} #{destination}" | |
end | |
def replace(file_path, pattern, replacement) | |
string = load file_path | |
string.gsub! pattern, replacement | |
save file_path, string | |
end | |
def available?(name) | |
`gem list #{name}`.strip.length > 0 | |
end | |
def dirty? | |
`git status --porcelain`.length > 0 | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment