yonaskolb / XcodeGen

A Swift command line tool for generating your Xcode project
MIT License
7.03k stars 818 forks source link

Create interactive specs #273

Open AliSoftware opened 6 years ago

AliSoftware commented 6 years ago

One use case I would have for XcodeGen would be to generate brand new projects when starting a new contract.

(For now we use liftoff for that but as it's not maintained anymore, we want to migrate to something else like XcodeGen)

The idea would be to create a project.yml with a magic syntax for values which, when encountered, would (1) interactively ask for the value to give to that setting (2) or allow for it to be specified via command line parameters (3) and once the project has been generated once, replace the values in the project.yml with the new ones

So, for example we could have a template.yml file like this:

name: "?name|App Name"
options: 
  createIntermediateGroups: true
  bundleIdPrefix: "?id|Bundle ID prefix"
  developmentLanguage: "en"
  usesTabs: false
  indentWidth: 2
  tabWidth: 2
  deploymentTarget:
    watchOS: "2.0"
    tvOS: "10.0"
    iOS: "?ios|Deployment Target|9.0"
settings:
    GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED: YES
    GCC_WARN_MISSING_PARENTHESES: YES
    GCC_WARN_ABOUT_RETURN_TYPE: YES
    GCC_WARN_CHECK_SWITCH_STATEMENTS: YES
    GCC_WARN_UNUSED_FUNCTION: YES
    GCC_WARN_UNUSED_LABEL: YES

targets:
  "?name":
    type: application

  UnitTests:
    type: bundle.unit-test

And then we could run the command, where we can provide values for all "?…" magic values, either using arguments via the command lines (using the parameter name provided next to the "?") or/and be interactively queried for others (using the second parameter provided after the "?…|" for the prompt, and the optional third part for the default value):

$ xcodegen --template="template.yml" --name=Foo
Bundle ID Prefix: com.mycompany
Deployment Target [9.0]: 9.3
> Generating project.yml from template.yml and the provided values…
> Generating Xcodeproj…
AliSoftware commented 6 years ago

Now that I think about it, I'm not sure if that idea would be self-sufficient.

When we create a new project, both the Xcodeproj (generated by XcodeGen) but also the project files (swift code, copyright header comments, XIBs, Storyboards, …), must be "templatized" and adapted too. And I'm guessing that part isn't XcodeGen's job…

I'm curious to know what people use for such needs, as a solution to replace liftoff, if XcodeGen isn't the tool for that, btw.

In the meantime I'm working on a tiny ruby script to do that job of create-new-projet-structure-from-a-template, but if there's already some existing tool which handles that for us, I'm interested to know to avoid re-inventing the wheel…

Ruby script to create a new project structure from a template (WIP) ```ruby #!/usr/bin/env ruby require 'readline' require 'fileutils' template_dir = File.expand_path('template', File.dirname(__FILE__)) target_dir = ARGV.shift unless target_dir puts <<-HELP.gsub(/^.*\|/,'') | |Usage: #{File.basename(__FILE__)} TARGET_DIR | | Creates a new Xcode project and project files/structure | in folder TARGET_DIR, based on a template — after prompting | the user for some values to inject in that template. HELP exit 1 end def ask(prompt, default = '') prompt += " [#{default}]" unless default.empty? prompt += ': ' v = Readline.readline(prompt) || '' v.empty? ? default : v end ############### values = { :name => ask('Project Name'), :ios => ask('iOS Deployment Target', '9.0'), :lang => ask('Development Language', 'fr') } ### Main ###### def transform(content, values) values.each { |k,v| content.gsub!("<##{k.to_s}#>", v) } content end FileUtils.mkdir(target_dir) src_files = Dir.chdir(template_dir) { Dir.glob("**/*", File::FNM_DOTMATCH) } # Then we do a replacement on each text file of target_dir src_files.each do |file| next if ['.', '..'].include?(File.basename(file)) src = "#{template_dir}/#{file}" dst = transform("#{target_dir}/#{file}", values) if File.directory?(src) FileUtils.mkdir(dst) else content = File.read(src) new_content = transform(content, values) File.write(dst, new_content) end end ```
yonaskolb commented 6 years ago

Yeah scaffolding/templating is probably outside the scope of XcodeGen. Generating a project spec from an existing project, or interactively from scratch is on the cards though.

Some other tools I know of in this space though are: https://github.com/audreyr/cookiecutter http://yeoman.io https://github.com/workshop/cook https://github.com/JohnSundell/SwiftPlate

Cookiecutter is probably your best bet

I think there's an opportunity in the community to build a good tool like this in Swift. I know that @jcampbell05 wanted to work on something like that, and @rjchatfield showed me a cool tool the other day that he's been building internally for Atlasssian that solves similar problems.

Personally I'd like to see something similar to the template files in SwagGen Basically a template manifest file that defines options and a list of Stencil files that get passed those options. These options could also be interactively queried for in the command line tool. There could be some default templates for Xcode Projects, and people could then edit and share them.

jcampbell05 commented 6 years ago

I started a basic tool for this. It essentially just consumed the XCTemplate file and hooked into a tool such as Xcake, Struct or XcodeGen to spit out the files.

This was a very early alpha of it https://github.com/workshop/cook its in ruby sadly. I've tried to re-start it in swift since but not got quite so far.

yonaskolb commented 6 years ago

I’ve got some ideas about a tool like this... Watch this space

Lutzifer commented 6 years ago

can we add support for env files to the project.yml?

then we could call it like

name=FOO code_signing_identity=BAR xcodegen

I'm curious what @yonaskolb has in mind, I would have gone the .erb route for customising the project.yml I guess.

yonaskolb commented 6 years ago

Ok guys, I've written a new tool called Genesis for templating. It utilises a template manifest and templates written in Stencil.

Taking the exact @AliSoftware example originally posted, it would look like this in the most simplest form in Genesis.

template.yml

options:
 - name: name
   question: What's your project name?
 - name: id
   question: What's the bundle id?
 - name: iOSVersion
   question: What version of iOS is this using?
   choices:
    - 9.0
    - 10.0
    - 11.0
files:
  - template: project.yml

project.yml:

name: {{ name }}
options: 
  createIntermediateGroups: true
  bundleIdPrefix: {{ id }}
  developmentLanguage: "en"
  usesTabs: false
  indentWidth: 2
  tabWidth: 2
  deploymentTarget:
    watchOS: "2.0"
    tvOS: "10.0"
    iOS: {{ iOSVersion }}
settings:
    GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED: YES
    GCC_WARN_MISSING_PARENTHESES: YES
    GCC_WARN_ABOUT_RETURN_TYPE: YES
    GCC_WARN_CHECK_SWITCH_STATEMENTS: YES
    GCC_WARN_UNUSED_FUNCTION: YES
    GCC_WARN_UNUSED_LABEL: YES

targets:
  {{ name }}
    type: application

  UnitTests:
    type: bundle.unit-test

You could then generate this using

genesis generate template.yml

It looks for options via various methods (env, file, arguments) and asks you interactively if none are found. An easy way to pass the options is like this:

genesis generate template.yml --options "name:MyProject, id: com.company, iOSVersion: 10.0"

There are more complex configuration options than this, but this is a simple example. If you could take a look at the repo and kick the tires I'd appreciate it.

AliSoftware commented 6 years ago

Cool!

iiuc we can list as many files as we want in the files entry and those files should actually be stencil templates that gets rendered using the barracks previously defined as context?

yonaskolb commented 6 years ago

Yep! Those files can also have dynamic paths, and even be rendered out multiple times with different child contexts if it reference an array via context.

yonaskolb commented 6 years ago

You could even make an XcodeGen project spec be the data for a template with --option-path and then dynamically generate the folder structure from that by reading from it as a context, and then providing templated Info.plist files and things

atreat commented 6 years ago

I was looking for a way to use Environment variables in my project.yml file for project configuration. Looking into Genesis and it looks promising!

Lutzifer commented 5 years ago

Coming back on this:

https://github.com/SwiftGen/SwiftGen/pull/564 implements this for SwiftGen https://github.com/krzysztofzablocki/Sourcery/pull/724 implements this for Sourcery

Can we please do something like this directly in XcodeGen? My team would crucify me, if I bring in yet another tool like Genesis 😅.

yonaskolb commented 5 years ago

A PR has just been merged that supports referencing environment variables https://github.com/yonaskolb/XcodeGen/pull/594

While this is not interactive, a wrapper script could still ask the user for these values

Lutzifer commented 5 years ago

@yonaskolb Awesome, looking forward to try it!