Giter Site home page Giter Site logo

configgenerator's Introduction

Configen - Kin + Carta Create

configen

A command line tool to auto-generate configuration file code, for use in Xcode projects. To read about the motivation behind this project and the scenarios in which it might be used see our blog post.

The configen tool is used to auto-generate configuration code from a property list. It is intended to create the kind of configuration needed for external URLs or API keys used by your app. Currently supports both Swift and Objective-C code generation.

Installation

To add the configen tool to your project you must first aquire the configen excecutable binary. The simplest way to do this is to download the executable binary from the latest release.

Alternatively you can download or clone this repository. Once you have done so, open and build the configen.xcodeproj project in Xcode, right click on the configen product and select ‘Show in Finder’.

Once you have the executable file, make a copy and add it to the root directory of your project. Now you are ready to go! Next you need to create the relevant files and set-up your project accordingly. This is outlined below.

Usage

Step 1: The mapping file

Before running the configen tool, you need to create a mapping file (.map), in which you define the configuration variables you support. For example:

entryPointURL : URL
enableFileSharing : Bool
retryCount : Int
adUnitPrefix : String
analyticsKey : String
arrayOfHashes: [String]
environment : Environment

The configen mapping file uses a custom set of types which map to Swift types. Therefore for Objective-C code generation, you must still use Swift equivalent types in the mapping file.

Step 2: A plist for each environment

Then you need to create a property list (plist) file, in which you provide values for each of the keys defined in your mapping file, above. You need to create a property list file for each required environment. For example, you may have a test and a production environment.

Using the above example, the plist source code for a production environment may look as follows:

<plist version="1.0">
<dict>
<key>entryPointURL</key>
<string>http://example.com/production</string>
<key>enableFileSharing</key>
<true/>
<key>retryCount</key>
<integer>4</integer>
<key>adUnitPrefix</key>
<string>production_ad_unit</string>
<key>analyticsKey</key>
<string>haf6d9fha8v56abs</string>
<key>environment</key>
<string>.Production</string>
</dict>
</plist>

Before proceeding to the next step, ensure that both the mapping file and plist files are placed inside your project directory. To keep things simple it might be best to place all these files in the same place, for example, in a Config sub-folder. You will need to reference the path to these files in step 3.

Step 3: An external build step for each environment

Finally, you need to create a build target for each of your environments. This can be done be selecting File -> New -> Target and selecting 'External Build System' from the 'Cross-Platform' tab.

In the settings of each build target point the 'Build Tool' to the location of the configen script that you copied to your directory earlier and invoke the arguments as follows. Note that the output directory must be created separately.

configen --plist-path <plist> --hints-path <mapping-file> --class-name <output-class-name> --output-directory <output-directory>

  -p, --plist-path:
      Path to the input plist file
  -h, --hints-path:
      Path to the input hints file
  -n, --class-name:
      The output config class name
  -o, --output-directory:
      The output config class directory
  -c, --objective-c:
      Whether to generate Objective-C files instead of Swift

# e.g.

configen --plist-path EnvironmentConfig/EnvironmentConfig_Prod.plist --hints-path EnvironmentConfig.map --class-name EnvironmentConfig --output-directory EnvironmentConfig

configen generates Swift code by default. You can generate Objective-C code by providing the -c or --objective-c switches

The best way to support multiple environments is to define a separate scheme for each one. Then add the relevant target as an external build step for each scheme ensuring that 'Parallelize Build' is disabled.

Please refer to the example project included in the repository for further guidance.

Standard types supported

  • Int: Expects integer type in plist
  • String: Expects string type in plist
  • Bool: Expects Boolean type in plist
  • Double: Expects floating point type in plist
  • URL: Expects a string in the plist, which can be converted to a URL (validated at compile time)
  • Array : Expects an array of values in the plist

Custom types

Any other type is supported, by providing a string in the plist which compiles successfully when converted to code. For example:

enum Environment {
  case Development
  case UAT
  case Production
}

Providing the mapping type environment : Environment in the mapping file, and the string .Production in the plist, the property in your configuration class will be as follows:

  static let environment: Environment = .Production

This is powerful, because it allows you to work with optionals, which are not supported by the standard types. For example:

Mapping file:

retryCount : Int?

You have to make the type in your plist a string, and input either a number -- e.g. 1 -- or the word nil, so the output property becomes, for example:

  let retryCount: Int? = nil

Arrays

Configen also supports reading arrays from the plist. For example, you can create an array of certificate hashes for pinning purposes and configen will automatically map them to an array in the generated configuration file. The arrays can be of any depth (for example: [String], [[URL]], [[[Any]]], etc).

The downside of using an array in the plist is that the order of the array and whether there are any array elements at all is not guaranteed, so keep that in mind when using this functionality.

Example:

Plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>arrayOfHashes</key>
	<array>
		<string>9BV3692736V893B47V893BY4V94B8V6123984BV6983V6B093</string>
		<string>BVQ09PY89V86BY98VY9876BV9786B98687B6976BOP967BP96</string>
		<string>PB869869P6B76P9B7869P8B69P697P69769769P7B697PB89B</string>
	</array>
</dict>
</plist>

Mapping (.map)

arrayOfHashes: [String]

Config file (generated)

static let arrayOfHashes: [String] = [
  "9BV3692736V893B47V893BY4V94B8V6123984BV6983V6B093",
  "BVQ09PY89V86BY98VY9876BV9786B98687B6976BOP967BP96",
  "PB869869P6B76P9B7869P8B69P697P69769769P7B697PB89B"
]

configgenerator's People

Contributors

joncox avatar jsanderson44 avatar kanecheshire avatar marcoguerrieritab avatar neil3079 avatar neilkimmett avatar nickhegarty avatar roger-tan avatar samdods avatar theblixguy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

configgenerator's Issues

1.1.2 regression build failure

👋 I am trying to build configen for sonoma arm, but ran into some build failure as below:

...
      /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -Xlinker -reproducible -target x86_64-apple-macos10.10 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.4.sdk -Os -L/tmp/configen-20240804-4564-py2xbh/ConfigGenerator-1.1.2/build/EagerLinkingTBDs/Release -L/tmp/configen-20240804-4564-py2xbh/ConfigGenerator-1.1.2/build/Release -F/tmp/configen-20240804-4564-py2xbh/ConfigGenerator-1.1.2/build/EagerLinkingTBDs/Release -F/tmp/configen-20240804-4564-py2xbh/ConfigGenerator-1.1.2/build/Release -filelist /tmp/configen-20240804-4564-py2xbh/ConfigGenerator-1.1.2/build/configen.build/Release/configen.build/Objects-normal/x86_64/configen.LinkFileList -Xlinker -rpath -Xlinker /usr/lib/swift -Xlinker -rpath -Xlinker @executable_path/../Frameworks -Xlinker -rpath -Xlinker @loader_path/../Frameworks -Xlinker -object_path_lto -Xlinker /tmp/configen-20240804-4564-py2xbh/ConfigGenerator-1.1.2/build/configen.build/Release/configen.build/Objects-normal/x86_64/configen_lto.o -fobjc-link-runtime -L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx -L/usr/lib/swift -Xlinker -add_ast_path -Xlinker /tmp/configen-20240804-4564-py2xbh/ConfigGenerator-1.1.2/build/configen.build/Release/configen.build/Objects-normal/x86_64/configen.swiftmodule -Xlinker -no_adhoc_codesign -Xlinker -dependency_info -Xlinker /tmp/configen-20240804-4564-py2xbh/ConfigGenerator-1.1.2/build/configen.build/Release/configen.build/Objects-normal/x86_64/configen_dependency_info.dat -o /tmp/configen-20240804-4564-py2xbh/ConfigGenerator-1.1.2/build/Release/configen
  clang: error: SDK does not contain 'libarclite' at the path '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_macosx.a'; try increasing the minimum deployment target
  
  Copy /tmp/configen-20240804-4564-py2xbh/ConfigGenerator-1.1.2/build/Release/configen.swiftmodule/Project/x86_64-apple-macos.swiftsourceinfo /tmp/configen-20240804-4564-py2xbh/ConfigGenerator-1.1.2/build/configen.build/Release/configen.build/Objects-normal/x86_64/configen.swiftsourceinfo (in target 'configen' from project 'configen')
      cd /tmp/configen-20240804-4564-py2xbh/ConfigGenerator-1.1.2
      builtin-copy -exclude .DS_Store -exclude CVS -exclude .svn -exclude .git -exclude .hg -resolve-src-symlinks -rename /tmp/configen-20240804-4564-py2xbh/ConfigGenerator-1.1.2/build/configen.build/Release/configen.build/Objects-normal/x86_64/configen.swiftsourceinfo /tmp/configen-20240804-4564-py2xbh/ConfigGenerator-1.1.2/build/Release/configen.swiftmodule/Project/x86_64-apple-macos.swiftsourceinfo
  
  /tmp/configen-20240804-4564-py2xbh/ConfigGenerator-1.1.2/configen.xcodeproj: warning: The macOS deployment target 'MACOSX_DEPLOYMENT_TARGET' is set to 10.10, but the range of supported deployment target versions is 10.13 to 14.4.99. (in target 'configen' from project 'configen')
  ** BUILD FAILED **
  
  
  The following build commands failed:
  	Ld /tmp/configen-20240804-4564-py2xbh/ConfigGenerator-1.1.2/build/Release/configen normal (in target 'configen' from project 'configen')
  (1 failure)

full log in here, https://github.com/Homebrew/homebrew-core/actions/runs/10231498764/job/28319900515

xcode 15.3 (issue reproducible for xcode 15.2 as well)

readme details

Thanks for this script it sounds great. Are you able to provide more details on how to install the script in your project and configure the scheme external build script invocation? I'm not able to follow how to get this configured.

Publishing configen

I don't like idea of including 5-10 MB binary into my repository. Have you thought about releasing configen to Homebrew?

Overriding values or inheriting from another plist

We've come across a situation where we have a config that needs to be identical to another one (and stay identical if changes are made) except for one value

Currently I'm using plutil to override the value just before calling configen but this is quite hacky. So I see there are two options:

Allow overriding a key value on the command line, eg:

configen -p "$configLocation" -h "$configDir/Config.map" -n $configName -o "$configDir" --override KEY VALUE

Inheriting from another plist, eg:

baseconfig.plist

<dict>
  <key>KEY1</key>
  <string>VALUE1</string>
  <key>KEY2</key>
  <string>VALUE2</string>
</dict>

config.plist:

<dict>
  <key>:super</key>
  <string>baseconfig.plist</string>
  <key>KEY2</key>
  <string>NEW_VALUE2</string>
</dict>

The key of the base plist being something like :super, :inherit_from or :base etc. But starting with a character like : that makes it an invalid swift variable name.

That way the generated config.swift from config.plist is:

class Config {
  static let KEY1 = "VALUE1"
  static let KEY2 = "NEW_VALUE2"
}

I think the latter plist inheritance option would be preferable as it keeps all the values in the plists and there's no chance of one hiding in a script somewhere. Would this be desirable to implement and create a pull request?

Support for optional values

Does configen support optional values? for example can we declare String? in the mapping file. The reasoning for this is that some environments don't need all the properties.

create plist helper

Hey,

Thanks for this nice tool, and the writeup in your blog is also detailed and wants me to use your configuration :) I was wondering if it would be possible to create the plist files as the mapping file already gives us the keys. Having to enter the keys once for each build config feels cumbersome, maybe a helper command could create the plist files with keys but empty values for valid configurations in the project, and the user only populates the plists with the appropriate values, without touching the keys? and then the main configen script would be run the prepare the .swift files.

Project configuration

Finally, you need to create a build target for each of your enviroments. This can be done be selecting File -> New -> Target and selecting 'External Build System'

The best way to support multiple environments is to define a separate scheme for each one. Then add the relevant target as an external build step for each scheme ensuring that 'Parallelize Build' is disabled.

Why is this so complex? I found adding build pre-action in scheme settings working just fine:
screen shot 2017-07-14 at 15 11 17

Am I missing something?

fatal error: OptionsParser.swift, line 33

I just installed configen 1.0.1 through homebrew and ran the command without arguments to see the usage info. The info printed successfully but was followed by an error.

$ configen
Missing required options: ["-p, --plist-path", "-h, --hints-path", "-n, --class-name", "-o, --output-directory"]

Usage: configen [options]
  -p, --plist-path:
      Path to the input plist file
  -h, --hints-path:
      Path to the input hints file
  -n, --class-name:
      The output config class name
  -o, --output-directory:
      The output config class directory
  -c, --objective-c:
      Whether to generate Objective-C files instead of Swift
fatal error: file /tmp/configen-20171009-56117-1w7opbc/ConfigGenerator-1.0.1/configen/OptionsParser.swift, line 33
fish: 'configen' terminated by signal SIGILL (Illegal instruction)

Add example project

To be able to see how configen works very quickly it would be nice if there was an example project to try it out.

Making `Config` a singleton

Very handy tool. One observation though. Currently the Config class that is auto generated is initializable. So we can do something like let configInstance = Config(). Honestly though I am not sure whats wrong with that, but probably Config would be better off as a singleton or at least a private initializer. Cause we really dont want to instantiate Config

readme

You build a tool for ease to use for other developers, but it is not clear how to use in our project. describe it more clearly. How we create mapping file etc.

Documentation improvement suggestions

Ordering of instructions:
Am I right in thinking creation of the mapping & plist files should happen before generating the configen file? If so it's a bit confusing that the instructions seem to start with the last step.

Where does the mapping file go?
It doesn't say anywhere exactly where this mapping file should be put, and what the name of it is. All I know from the instructions is a file should be placed somewhere?

Some with the plist files. A quick sentence pointing out these files should be placed in the Configen example project before generating the configen file would remove any chance of confusion, perhaps pointing out the example project is available as a reference.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.