Skip to content

Commit

Permalink
Merge pull request #166 from mrackwitz/ext_build_settings
Browse files Browse the repository at this point in the history
Generate targets with same build settings as Xcode (related to #164)
  • Loading branch information
kylef committed Nov 20, 2014
2 parents 8bf91f2 + 7163b7a commit 768a228
Show file tree
Hide file tree
Showing 163 changed files with 9,676 additions and 136 deletions.
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,44 @@
# Xcodeproj Changelog

## Master

##### Breaking

* `Constants` The build settings match now those from Xcode 6.1.
[Marius Rackwitz](https://github.com/mrackwitz)
[Kyle Fuller](https://github.com/kylef)
[Xcodeproj#166](https://github.com/CocoaPods/Xcodeproj/pull/166)


##### Enhancements

* `ProjectHelper` The `::common_build_settings` method supports now a new
parameter `language` to select the language used in the target. Acceptable
options are either `:objc` or `:swift`.
[Marius Rackwitz](https://github.com/mrackwitz)
[Xcodeproj#166](https://github.com/CocoaPods/Xcodeproj/pull/166)

* `ProjectHelper` Supports to create framework targets for iOS & OSX with the
correct build settings.
[Marius Rackwitz](https://github.com/mrackwitz)
[Xcodeproj#166](https://github.com/CocoaPods/Xcodeproj/pull/164)

* `Commands` Xcodeproj CLI has a new command `config-dump`, which allows to
read the build settings from all configurations of all targets of a given
Xcode project and serialize them to .xcconfig files.
[Marius Rackwitz](https://github.com/mrackwitz)
[Xcodeproj#166](https://github.com/CocoaPods/Xcodeproj/pull/166)


##### Development Enhancements

* `Rakefile` Brings a set of new tasks to interactively generate fixture targets
for all target configurations supported by Xcode to update the xcconfig
fixtures used for the new specs, which check the build settings constants.
[Marius Rackwitz](https://github.com/mrackwitz)
[Xcodeproj#166](https://github.com/CocoaPods/Xcodeproj/pull/166)


## 0.20.2

##### Bug Fixes
Expand Down Expand Up @@ -197,6 +236,7 @@
[Alessandro Orrù](https://github.com/alessandroorru)
[Xcodeproj#155](https://github.com/CocoaPods/Xcodeproj/pull/155)


## 0.17.0

###### Enhancements
Expand Down
124 changes: 124 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,107 @@ begin
@rvm_ruby_dir ||= File.expand_path('../..', `which ruby`.strip)
end

# Common Build settings
#-----------------------------------------------------------------------------#

namespace :common_build_settings do
PROJECT_PATH = 'Project/Project.xcodeproj'

task :prepare do
verbose false
cd 'spec/fixtures/CommonBuildSettings'
end

desc "Create a new empty project"
task :new_project => [:prepare] do
verbose false
Bundler.require 'xcodeproj'
title "Setup Boilerplate"

confirm "Delete existing fixture project and all data"
rm_rf 'Project/*'

subtitle "Create a new fixture project"
Xcodeproj::Project.new(PROJECT_PATH).save

subtitle "Open the project …"
sh 'open "Project/Project.xcodeproj"'
end

desc "Interactive walkthrough for creating fixture targets"
task :targets => [:prepare] do
verbose false
Bundler.require 'xcodeproj'

title "Create Targets"
subtitle "You will be guided how to *manually* create the needed targets."
subtitle "Each target name will been copied to your clipboard."
confirm "Make sure you have nothing unsaved there"

targets = {
"Objc_iOS_Native" => { platform: :ios, type: :application, language: :objc, how: "iOS > Master-Detail Application > Language: Objective-C" },
"Swift_iOS_Native" => { platform: :ios, type: :application, language: :swift, how: "iOS > Master-Detail Application > Language: Swift" },
"Objc_iOS_Framework" => { platform: :ios, type: :framework, language: :objc, how: "iOS > Cocoa Touch Framework > Language: Objective-C" },
"Swift_iOS_Framework" => { platform: :ios, type: :framework, language: :swift, how: "iOS > Cocoa Touch Framework > Language: Swift" },
"Objc_iOS_StaticLibrary" => { platform: :ios, type: :static_library, language: :objc, how: "iOS > Cocoa Touch Static Library" },
"Objc_OSX_Native" => { platform: :osx, type: :application, language: :objc, how: "OSX > Cocoa Application > Language: Objective-C" },
"Swift_OSX_Native" => { platform: :osx, type: :application, language: :swift, how: "OSX > Cocoa Application > Language: Swift" },
"Objc_OSX_Framework" => { platform: :osx, type: :framework, language: :objc, how: "OSX > Cocoa Framework > Language: Objective-C" },
"Swift_OSX_Framework" => { platform: :osx, type: :framework, language: :swift, how: "OSX > Cocoa Framework > Language: Swift" },
"Objc_OSX_StaticLibrary" => { platform: :osx, type: :static_library, language: :objc, how: "OSX > Library > Type: Static" },
"Objc_OSX_DynamicLibrary" => { platform: :osx, type: :dynamic_library, language: :objc, how: "OSX > Library > Type: Dynamic" },
"OSX_Bundle" => { platform: :osx, type: :bundle, how: "OSX > Bundle" },
}

targets.each do |name, attributes|
begin
sh "printf '#{name}' | pbcopy"
confirm "Create a target named '#{name}' by: #{attributes[:how]}", false

project = Xcodeproj::Project.open(PROJECT_PATH)
raise "Project couldn't be opened." if project.nil?

target = project.targets.find { |t| t.name == name }
raise "Target wasn't found." if target.nil?

raise "Platform doesn't match." unless target.platform_name == attributes[:platform]
raise "Type doesn't match." unless target.symbol_type == attributes[:type]

debug_config = target.build_configurations.find { |c| c.name == 'Debug' }
raise "Debug configuration is missing" if debug_config.nil?

release_config = target.build_configurations.find { |c| c.name == 'Release' }
raise "Release configuration is missing" if release_config.nil?

is_swift_present = debug_config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] != nil
is_swift_expected = attributes[:language] == :swift
raise "Language doesn't match." unless is_swift_present == is_swift_expected

puts green("Target matches.")
puts
rescue StandardError => e
puts "#{red(e.message)} Try again."
retry
end
end

puts green("All targets were been successfully created.")
end

desc "Dump the build settings of the fixture project to xcconfig files"
task :dump => [:prepare] do
verbose false
sh "../../../bin/xcodeproj config-dump Project/Project.xcodeproj configs"
end

desc "Recreate the xcconfig files for the fixture project targets from scratch"
task :rebuild => [
:new_project,
:targets,
:dump,
]
end

#-----------------------------------------------------------------------------#

namespace :spec do
Expand Down Expand Up @@ -118,8 +219,31 @@ def yellow(string)
"\033[0;33m#{string}\e[0m"
end

# Colorizes a string to red.
#
def red(string)
"\033[0;31m#{string}\e[0m"
end

# Colorizes a string to green.
#
def green(string)
"\033[0;32m#{string}\e[0m"
end

# Colorizes a string to cyan.
#
def cyan(string)
"\n\033[0;36m#{string}\033[0m"
end

def confirm(message, decline_by_default=true)
options = ['y', 'n']
options[decline_by_default ? 1 : 0].upcase!
print yellow("#{message}: [#{options.join('/')}] ")
input = STDIN.gets.chomp
if input == options[1].downcase || (input == '' && decline_by_default)
puts red("Aborted by user.")
exit 1
end
end
4 changes: 3 additions & 1 deletion lib/xcodeproj/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module Xcodeproj
require 'colored'

class Command
autoload :ConfigDump, 'xcodeproj/command/config_dump'
autoload :TargetDiff, 'xcodeproj/command/target_diff'
autoload :ProjectDiff, 'xcodeproj/command/project_diff'
autoload :Show, 'xcodeproj/command/show'
Expand Down Expand Up @@ -56,7 +57,7 @@ def shift_argument
end

def self.banner
commands = ['target-diff', 'project-diff', 'show', 'sort']
commands = ['config-dump', 'target-diff', 'project-diff', 'show', 'sort']
banner = "To see help for the available commands run:\n\n"
banner + commands.map { |cmd| " * $ xcodeproj #{cmd.green} --help" }.join("\n")
end
Expand Down Expand Up @@ -101,6 +102,7 @@ def self.parse(*argv)
String.send(:define_method, :colorize) { |string, _| string } if argv.option('--no-color')

command_class = case command_argument = argv.shift_argument
when 'config-dump' then ConfigDump
when 'target-diff' then TargetDiff
when 'project-diff' then ProjectDiff
when 'show' then Show
Expand Down
78 changes: 78 additions & 0 deletions lib/xcodeproj/command/config_dump.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
module Xcodeproj
class Command
class ConfigDump < Command
def self.banner
<<-eos.strip_heredoc
Dumps the build settings of all project targets for all configurations in
directories named by the target in given output directory.
$ config-dump PROJECT OUTPUT
It extracts common build settings in a per target base.xcconfig file.
If no `PROJECT` is specified then the current work directory is searched
for one.
If no `OUTPUT` is specified then the project directory will be used.%)
eos
end

def initialize(argv)
xcodeproj_path = argv.shift_argument
@xcodeproj_path = File.expand_path(xcodeproj_path) if xcodeproj_path

@output_path = Pathname(argv.shift_argument || '.')
unless @output_path.directory?
raise Informative, 'The output path must be a directory.'
end

super unless argv.empty?
end

def run
dump_all_configs(xcodeproj, 'Project')

xcodeproj.targets.each do |target|
dump_all_configs(target, target.name)
end
end

def dump_all_configs(configurable, name)
path = Pathname(name)

# Dump base configuration to file
base_settings = extract_common_settings!(configurable.build_configurations)
base_file_path = path + "#{name}_base.xcconfig"
dump_config_to_file(base_settings, base_file_path)

# Dump each configuration to file
configurable.build_configurations.each do |config|
settings = config.build_settings
dump_config_to_file(settings, path + "#{name}_#{config.name.downcase}.xcconfig", [base_file_path])
end
end

def extract_common_settings!(build_configurations)
# Grasp all common build settings
all_build_settings = build_configurations.map(&:build_settings)
common_build_settings = all_build_settings.reduce do |settings, config_build_settings|
settings.select { |key, value| value == config_build_settings[key] }
end

# Remove all common build settings from each configuration specific build settings
build_configurations.each do |config|
config.build_settings.reject! { |key| !common_build_settings[key].nil? }
end

common_build_settings
end

def dump_config_to_file(settings, file_path, includes = [])
dir = @output_path + file_path + '..'
dir.mkdir unless dir.exist?

config = Config.new(settings)
config.includes = includes
config.save_as(@output_path + file_path)
end
end
end
end
24 changes: 22 additions & 2 deletions lib/xcodeproj/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,25 @@ module Xcodeproj
class Config
require 'set'

KEY_VALUE_PATTERN = /
(
[^=]+ # Any char, but not an assignment operator (non-greedy)
(?: # One or multiple conditional subscripts
\[
[^\]]* # The subscript key
(?:
= # The subscript comparison operator
[^\]]* # The subscript value
)?
\]
)*
)
\s+ # Whitespaces after the key (needed because subscripts
# always end with ']')
= # The assignment operator
(.*) # The value
/x

# @return [Hash{String => String}] The attributes of the settings file
# excluding frameworks, weak_framework and libraries.
#
Expand Down Expand Up @@ -294,8 +313,9 @@ def extract_include(line)
# entry is the value.
#
def extract_key_value(line)
key, value = line.split('=', 2)
if key && value
match = line.match(KEY_VALUE_PATTERN)
if match
key, value = match[1], match[2]
[key.strip, value.strip]
else
[]
Expand Down
Loading

0 comments on commit 768a228

Please sign in to comment.