Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for scheme environment variables #326

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 4 additions & 16 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,35 +1,23 @@
# This configuration was generated by `rubocop --auto-gen-config`
# on 2015-03-06 13:49:50 +0100 using RuboCop version 0.28.0.
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2015-10-25 15:59:02 -0700 using RuboCop version 0.34.2.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.

# Offense count: 499
# Offense count: 804
# Configuration parameters: AllowURI, URISchemes.
Metrics/LineLength:
Max: 186

# Offense count: 66
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 46

# Offense count: 2
# Configuration parameters: CountKeywordArgs.
Metrics/ParameterLists:
Max: 7

# Offense count: 9
Metrics/PerceivedComplexity:
Max: 16

# Offense count: 94
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
Style/DotPosition:
Enabled: false

# Offense count: 1
Style/DoubleNegation:
Enabled: false
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

##### Enhancements

* Add accessors for working with Environment Variables in xcscheme files
[Justin Martin](https://github.com/justinseanmartin)
[Xcodeproj#326](https://github.com/CocoaPods/Xcodeproj/pull/326)

* Add method to create new variant groups (groups for localized versions of the same file)
[Tim Bodeit](https://github.com/timbodeit)
[Xcodeproj#315](https://github.com/CocoaPods/Xcodeproj/pull/315)
Expand Down
2 changes: 1 addition & 1 deletion lib/xcodeproj/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module Constants

# @return [String] The last known OS X SDK (stable).
#
LAST_KNOWN_OSX_SDK = '10.11'
LAST_KNOWN_OSX_SDK = '10.11'

# @return [String] The last known tvOS SDK (stable).
LAST_KNOWN_TVOS_SDK = '9.0'
Expand Down
170 changes: 170 additions & 0 deletions lib/xcodeproj/scheme/environment_variables.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
require 'xcodeproj/scheme/xml_element_wrapper'

module Xcodeproj
class XCScheme
VARIABLES_NODE = 'EnvironmentVariables'
VARIABLE_NODE = 'EnvironmentVariable'

# This class wraps the EnvironmentVariables node of a .xcscheme XML file. This
# is just a container of EnvironmentVariable objects. It can either appear on a
# LaunchAction or TestAction scheme group.
#
class EnvironmentVariables < XMLElementWrapper
# @param [nil,REXML::Element,Array<EnvironmentVariable>,Array<Hash{Symbol => String,Bool}>] node_or_variables
# The 'EnvironmentVariables' XML node, or list of environment variables, that this object represents.
# - If nil, an empty 'EnvironmentVariables' XML node will be created
# - If an REXML::Element, it must be named 'EnvironmentVariables'
# - If an Array of objects or Hashes, they'll each be passed to {#assign_variable}
#
def initialize(node_or_variables = nil)
create_xml_element_with_fallback(node_or_variables, VARIABLES_NODE) do
@all_variables = []
node_or_variables.each { |var| assign_variable(var) } unless node_or_variables.nil?
end
end

# @return [Array<EnvironmentVariable>]
# The key value pairs currently set in @xml_element
#
def all_variables
@all_variables ||= @xml_element.get_elements(VARIABLE_NODE).map { |variable| EnvironmentVariable.new(variable) }
end

# Adds a given variable to the set of environment variables, or replaces it if that key already exists
#
# @param [EnvironmentVariable,Hash{Symbol => String,Bool}] variable
# The variable to add or update, backed by an EnvironmentVariable node.
# - If an EnvironmentVariable, the previous reference will still be valid
# - If a Hash, must conform to {EnvironmentVariable#initialize} requirements
# @return [Array<EnvironmentVariable>]
# The new set of environment variables after addition
#
def assign_variable(variable)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely prefer that assign_variable.

BTW, talking about [] and []= it actually seem like a good addition (but still while keeping this assign_variable of course).

While assign_variable would take an EnvironmentVariable, Hash{Symbol=>String,Bool}, []= would take the variable name as key and expect the variable content as value (and would assume enabled=YES, no way to specify enabled with that syntax). And [] would take the variable name as key and return the EnvironmentVariable.
That could provide a nice syntactic sugar alternative. What do you think?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me. Should [] operate on enabled variables only as well?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, since it would return an EnvironmentVariable anyway, I'd say let it access all variables (enabled or disabled). The user could still check the variable state once retrieved.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

env_var = variable.is_a?(EnvironmentVariable) ? variable : EnvironmentVariable.new(variable)
all_variables.each { |existing_var| remove_variable(existing_var) if existing_var.key == env_var.key }
@xml_element.add_element(env_var.xml_element)
@all_variables << env_var
end

# Removes a specified variable (by string or object) from the set of environment variables
#
# @param [EnvironmentVariable,String] variable
# The variable to remove
# @return [Array<EnvironmentVariable>]
# The new set of environment variables after removal
#
def remove_variable(variable)
env_var = variable.is_a?(EnvironmentVariable) ? variable : all_variables.find { |var| var.key == variable }
raise "Unexpected parameter type: #{env_var.class}" unless env_var.is_a?(EnvironmentVariable)
@xml_element.delete_element(env_var.xml_element)
@all_variables -= [env_var]
end

# @param [String] key
# The key to lookup
# @return [EnvironmentVariable] variable
# Returns the matching environment variable for a specified key
#
def [](key)
all_variables.find { |var| var.key == key }
end

# Assigns a value for a specified key
#
# @param [String] key
# The key to update in the environment variables
# @param [String] value
# The value to lookup
# @return [EnvironmentVariable] variable
# The newly updated environment variable
#
def []=(key, value)
assign_variable(:key => key, :value => value)
self[key]
end

# @return [Array<Hash{Symbol => String,Bool}>]
# The current environment variables represented as an array
#
def to_a
all_variables.map(&:to_h)
end
end

# This class wraps the EnvironmentVariable node of a .xcscheme XML file.
# Environment variables are accessible via the NSDictionary returned from
# [[NSProcessInfo processInfo] environment] in your app code.
#
class EnvironmentVariable < XMLElementWrapper
# @param [nil,REXML::Element,Hash{Symbol => String,Bool}] node_or_variable
# - If nil, it will create a default XML node to use
# - If a REXML::Element, should be a <EnvironmentVariable> XML node to wrap
# - If a Hash, must contain keys :key and :value (Strings) and optionally :enabled (Boolean)
#
def initialize(node_or_variable)
create_xml_element_with_fallback(node_or_variable, VARIABLE_NODE) do
raise "Must pass a Hash with 'key' and 'value'!" unless node_or_variable.is_a?(Hash) &&
node_or_variable.key?(:key) && node_or_variable.key?(:value)

@xml_element.attributes['key'] = node_or_variable[:key]
@xml_element.attributes['value'] = node_or_variable[:value]

if node_or_variable.key?(:enabled)
@xml_element.attributes['isEnabled'] = bool_to_string(node_or_variable[:enabled])
else
@xml_element.attributes['isEnabled'] = bool_to_string(true)
end
end
end

# Returns the EnvironmentValue's key
# @return [String]
#
def key
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add YARD doc

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (and for all below)

@xml_element.attributes['key']
end

# Sets the EnvironmentValue's key
# @param [String] key
#
def key=(key)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add YARD doc

@xml_element.attributes['key'] = key
end

# Returns the EnvironmentValue's value
# @return [String]
#
def value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add YARD doc

@xml_element.attributes['value']
end

# Sets the EnvironmentValue's value
# @param [String] value
#
def value=(value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add YARD doc

@xml_element.attributes['value'] = value
end

# Returns the EnvironmentValue's enabled state
# @return [Bool]
#
def enabled
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add YARD doc

string_to_bool(@xml_element.attributes['isEnabled'])
end

# Sets the EnvironmentValue's enabled state
# @param [Bool] enabled
#
def enabled=(enabled)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add YARD doc

@xml_element.attributes['isEnabled'] = bool_to_string(enabled)
end

# @return [Hash{:key => String, :value => String, :enabled => Bool}]
# The environment variable XML node with attributes converted to a representative Hash
#
def to_h
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ruby standard name for this method is to_hash, not to_h

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to be differing information. From what I've read, appears that to_hash is specifically if the object acts like a hash and can be converted with no loss of fidelity. I don't think that is the case here. I don't feel strongly though, so LMK if I'm just misunderstood.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, actually you're right, didn't know about that. Let's keep to_h then.

{ :key => key, :value => value, :enabled => enabled }
end
end
end
end
16 changes: 16 additions & 0 deletions lib/xcodeproj/scheme/launch_action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@ def buildable_product_runnable=(runnable)
@xml_element.add_element(runnable.xml_element) if runnable
end

# @return [EnvironmentVariables]
# Returns the EnvironmentVariables that will be defined at app launch
#
def environment_variables
EnvironmentVariables.new(@xml_element.elements[XCScheme::VARIABLES_NODE])
end

# @param [EnvironmentVariables,nil] env_vars
# Sets the EnvironmentVariables that will be defined at app launch
#
def environment_variables=(env_vars)
@xml_element.delete_element(XCScheme::VARIABLES_NODE)
@xml_element.add_element(env_vars.xml_element) if env_vars
env_vars
end

# @todo handle 'AdditionalOptions' tag
end
end
Expand Down
18 changes: 18 additions & 0 deletions lib/xcodeproj/scheme/test_action.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'xcodeproj/scheme/abstract_scheme_action'
require 'xcodeproj/scheme/environment_variables'

module Xcodeproj
class XCScheme
Expand Down Expand Up @@ -84,6 +85,23 @@ def add_macro_expansion(macro_expansion)
@xml_element.add_element(macro_expansion.xml_element)
end

# @return [EnvironmentVariables]
# Returns the EnvironmentVariables that will be defined at test launch
#
def environment_variables
EnvironmentVariables.new(@xml_element.elements[XCScheme::VARIABLES_NODE])
end

# @param [EnvironmentVariables,nil] env_vars
# Sets the EnvironmentVariables that will be defined at test launch
# @return [EnvironmentVariables]
#
def environment_variables=(env_vars)
@xml_element.delete_element(XCScheme::VARIABLES_NODE)
@xml_element.add_element(env_vars.xml_element) if env_vars
env_vars
end

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

class TestableReference < XMLElementWrapper
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@
ReferencedContainer = "container:SharedSchemes.xcodeproj">
</BuildableReference>
</MacroExpansion>
<EnvironmentVariables>
<EnvironmentVariable
key = "testkey"
value = "testval"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "testkeydisabled"
value = "testvaldisabled"
isEnabled = "NO">
</EnvironmentVariable>
</EnvironmentVariables>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
Expand All @@ -48,7 +60,8 @@
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
allowLocationSimulation = "YES">
<BuildableProductRunnable>
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "632143E8175736EE0038D40D"
Expand All @@ -57,6 +70,18 @@
ReferencedContainer = "container:SharedSchemes.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "launchkey"
value = "launchval"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "launchkeydisabled"
value = "launchvaldisabled"
isEnabled = "NO">
</EnvironmentVariable>
</EnvironmentVariables>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
Expand All @@ -66,7 +91,8 @@
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable>
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "632143E8175736EE0038D40D"
Expand Down
2 changes: 1 addition & 1 deletion spec/plist_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def o.to_hash

it 'allows hashes, strings, booleans, numbers, and arrays of hashes and strings as values' do
hash = {
'hash' => { 'a hash' => 'in a hash' },
'hash' => { 'a hash' => 'in a hash' },
'string' => 'string',
'true_bool' => '1',
'false_bool' => '0',
Expand Down
Loading