Лучший способ управлять сборками разработки, тестирования и производства с различными настройками и именем

У меня есть три API с разными API Keys и несколько разных настроек

  • Для разработки или сборки внутреннего тестирования - Распространение разработки вне iOS App Store

    • Host - devapi.project-name.com
    • API Key - API Key разработки
    • FLEX [ 1 ] - Включить
  • Для клиентского тестирования сборки - корпоративное распространение за пределами iOS App Store

    • Host - stgapi.project-name.com
    • API Key - enterprise_key
    • FLEX - Включить
  • Для производственной сборки - распространение в iOS App Store

    • Host - API.project-name.com
    • API key - app_store_key
    • FLEX - отключить

Я могу управлять двумя настройками с помощью DEBUG

#if DEBUG
    #define API_BASE_URL @"http://devapi.project-name.com/api/v1"
    #define API_KEY @"development_key"
#else
    #define API_BASE_URL @"http://stgapi.project-name.com/api/v1"
    #define API_KEY @"enterprise_key"
#endif

// In AppDelegate.m 
#if DEBUG
    [[FLEXManager sharedManager] showExplorer];
#endif

Но первая проблема - это дистрибутив Enterprise (для тестирования клиентов) и дистрибутив (сборка) сборки iOS App Store, для дистрибутива Enterprise и App Store каждый раз нужно менять код

  • Для корпоративного распространения

    #if DEBUG
        //debug setting
    #else
        //enterprise setting
        #define API_BASE_URL @"http://stgapi.project-name.com/api/v1"
        #define API_KEY @"enterprise_key"
    #endif
    
  • Для распространения в App Store

    #if DEBUG
        //debug setting
    #else
        //app store setting
        #define API_BASE_URL @"http://api.project-name.com/api/v1"
        #define API_KEY @"app_store_key"
    #endif
    

Я ищу способ что-то вроде этого

#ifdef DEVELOPMENT
    #define API_BASE_URL @"http://devapi.project-name.com/api/v1"
    #define API_KEY @"development_key"
#elif ENTERPRISE
    #define API_BASE_URL @"http://stgapi.project-name.com/api/v1"
    #define API_KEY @"enterprise_key"
#elif APP_STORE
    #define API_BASE_URL @"http://api.project-name.com/api/v1"
    #define API_KEY @"app_store_key"
#endif

Или любой другой?


Вторая проблема

Есть ли способ создать три сборки с разными именами без создания другой цели?

  • ProductName - для магазина приложений
  • ProductName-Dev - для внутренней разработки build
  • ProductName-Stg - для сборки клиентского тестирования (Enterprise)

Я только что создал демонстрационный проект и полное визуальное руководство на основе решения от iamnichols

Ответ 1

Разница между отладкой и сборкой выпуска заключается в том, что он архивируется и экспортируется, а другой выполняется локально через Xcode в отладчике. Возможно, вы обнаружите, что иногда хотите запустить производственную или промежуточную сборку в отладчике, но, разделив содержимое на #ifdef DEBUG, вы, вероятно, столкнетесь с проблемами.

Это упрощенная версия того, что я делаю:

Создать индивидуальные конфигурации

В настройках проекта (не целевых) создайте (дублируйте из оригиналов) следующие конфигурации:

  • Debug_Dev
  • Debug_Staging
  • Debug_Prod
  • Release_Dev
  • Release_Staging
  • Release_Prod

Обратите внимание, что если вы используете Cocoapods, вам нужно будет установить конфигурации обратно в none, удалить содержимое папки Pods в вашем проекте (Не проект Pods) и повторно запустить pod install.

Создайте схему для каждой среды

Вместо того, чтобы просто иметь схему MyApp, создайте следующее (дублируйте оригинал):

  • MyApp_Dev
  • MyApp_Staging
  • MyApp_Prod

В каждой схеме используйте соответствующие конфигурации Debug_ * и Release_ *, где это необходимо.

Добавить макрос препроцессора для определения сред

Добавьте дополнительный макрос препроцессора, чтобы определить, с какой средой вы строите.

В настройках сборки проекта щелкните значок + и добавьте пользовательский параметр сборки и назовите его как-то вроде MYAPP_ENVIRONMENT. Затем для каждой группы различных групп добавьте другой макрос препроцессора к каждому из них. я ENV_DEV=1, ENV_STAGING=1 и ENV_PROD=1.

Затем в макросах препроцессора c (опять же на уровне проекта, а не на целевом уровне) добавьте этот новый параметр MYAPP_ENVIRONMENT, используя $(MYAPP_ENVIRONMENT).

Таким образом, вы можете определить, с какой средой вы строите против:

#ifdef ENV_DEV
    NSString * const MyAppAPIBaseURL = @"https://api-dev.myapp.com/";
#elif ENV_SAGING
    NSString * const MyAppAPIBaseURL = @"https://api-staging.myapp.com/";
#elif ENV_PROD
    NSString * const MyAppAPIBaseURL = @"https://api.myapp.com/";
#endif

Скорее всего, это займет много времени, но дайте мне знать, как вы продвигаетесь.


Затем вы можете создавать различные пользовательские настройки сборки, чтобы делать разные вещи, например изменять отображаемое имя вашего приложения.

Вы могли бы сделать это, создав, например, новый параметр под названием MYAPP_DISPLAY_NAME, установите правильное имя для каждой конфигурации, а затем в info.plist установите значение отображаемого имени Bundle на $(MYAPP_DISPLAY_NAME).

Ответ 2

Более простое и менее сложное решение будет использовать разные файлы заголовков для каждой конфигурации, а #importing только один из них. Это не автоматическое, но довольно простое:

// You only need to switch the following lines when passing from qa 2 production and back:
#import "Mode_QA.h"
//#import "Mode_Production.h"

Ответ 3

подробности

  • Версия Xcode 10.2.1 (10E1001), Swift 5

Решение

1) Создание (или дублирование) целей

enter image description here

ИЛИ ЖЕ

enter image description here

Мой образец:

Я продублировал существующие цели и переименовал их. Имена моих целей:

  • Приложение-производственное
  • App-Подмости
  • App-развития

2) Организация и переименование информационных списков

Я поместил все списки в одну папку: info.plists

enter image description here

3) Переименовать схемы сборки

enter image description here


enter image description here

4) Проверьте схемы сборки параметров

Нажмите на кнопку Изменить

enter image description here


Убедитесь, что ваша схема сборки подключена к правильным целям.

Мой образец:

Схема сборки разработки приложений (посмотрите на левый верхний угол изображения ниже), подключенная к цели разработки приложений (цели расположены в центре изображения ниже).

  • в схеме сборки App-Development выбранной целью является App-Development
  • в схеме сборки App-Staging выбранной целью является App-Staging
  • в схеме сборки App-Production выбранной целью является App-Production

enter image description here


Также проверьте исполняемое приложение.

Мой образец:

  • В схеме сборки App-Development исполняемым приложением является App-Development.app
  • В схеме сборки App-Staging исполняемым приложением является App-Staging.app
  • В схеме сборки App-Production исполняемым приложением является App-Production.app

enter image description here

5) Добавить значения в информационные списки

enter image description here

Мой образец:

Info-Production.plist

<key>LSEnvironment</key>
<dict>
    <key>Environment</key>
    <string>Production</string>
    <key>Host</key>
    <string>https://production.host.com</string>
    <key>AppID</key>
    <integer>1</integer>
    <key>AdvertisementEnabled</key>
    <true/>
</dict>

Info-Development.plist

<key>LSEnvironment</key>
<dict>
    <key>Environment</key>
    <string>Development</string>
    <key>Host</key>
    <string>https://development.host.com</string>
    <key>AppID</key>
    <integer>2</integer>
    <key>AdvertisementEnabled</key>
    <false/>
</dict>

Info-Staging.plist

<key>LSEnvironment</key>
<dict>
    <key>Environment</key>
    <string>Staging</string>
    <key>Host</key>
    <string>https://staging.host.com</string>
    <key>AppID</key>
    <integer>3</integer>
    <key>AdvertisementEnabled</key>
    <false/>
</dict>

Environment.swift

import Foundation

// MARK: - Environment main class

class Environment {

    class Value { private init(){} }
    class Enums { private init(){} }
}

extension Environment.Value {
    static var all: [String: Any] {
        return Bundle.main.infoDictionary?["LSEnvironment"] as? [String: Any] ?? [:]
    }
}

extension Environment.Value {

    private enum Keys: String {
        case environment = "Environment"
        case host = "Host"
        case appID = "AppID"
        case advertisementEnabled = "AdvertisementEnabled"
    }

    private static func get<T>(value key: Keys, type: T.Type) -> T? {
        return all[key.rawValue] as? T
    }
}

// MARK: - Environment type value

extension Environment.Enums {
    enum EnvironmentType: String {
        case production = "Production"
        case staging = "Staging"
        case development = "Development"
    }
}

extension Environment.Value {
    static var type: Environment.Enums.EnvironmentType {
        let environment = get(value: .environment, type: String.self)!
        return Environment.Enums.EnvironmentType(rawValue: environment)!
    }
}

// MARK: - Host (sample with string)

extension Environment.Value {
    static var host: String { return get(value: .host, type: String.self)! }
}

// MARK: - App ID (sample with number)

extension Environment.Value {
    static var appID: Int { return get(value: .appID, type: Int.self)! }
}

// MARK: - Advertisement Enabled (sample with bool)

extension Environment.Value {
    static var advertisementEnabled: Bool { return get(value: .advertisementEnabled, type: Bool.self)! }
}

использование

print("All values: \(Environment.Value.all)")

switch Environment.Value.type {
    case .development: print("Environment: dev")
    case .staging: print("Environment: stage")
    case .production: print("Environment: prod")
}
print("Host: \(Environment.Value.host)")
print("App ID: \(Environment.Value.appID)")
print("Advertisement Enabled: \(Environment.Value.advertisementEnabled)")

enter image description here

Когда вы запустите одну из ваших схем сборки, у вас будут разные значения.

Выберите схему сборки

enter image description here

Бежать

enter image description here