Skip to content

Instantly share code, notes, and snippets.

@konstantinbe
Last active December 16, 2015 09:29
Show Gist options
  • Save konstantinbe/5413428 to your computer and use it in GitHub Desktop.
Save konstantinbe/5413428 to your computer and use it in GitHub Desktop.
Building Mac apps with Rake (no Xcode)
#
# 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