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

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

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

    • Host - devapi.project-name.com
    • API Key - development_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 - Для App Store
  • ProductName-Dev - Для внутреннего развития build
  • ProductName-Stg - Для сборки Client Testing (Enterprise)

Я только что создал демо-проект и полное визуальное руководство на основе решения от liamnichols

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

https://github.com/vineetchoudhary/BuildManagement

2 ответа

Решение

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

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

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

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

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

Обратите внимание, что если вы используете Cocoapods, вам нужно будет вернуть конфигурации в ноль, удалить содержимое папки 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 установите значение отображаемого имени пакета в $(MYAPP_DISPLAY_NAME),

подробности

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

Решение

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

ИЛИ ЖЕ

Мой образец:

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

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

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

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

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


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

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


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

Мой образец:

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

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


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

Мой образец:

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

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

Мой образец:

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)")

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

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

Бегать

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

// You only need to switch the following lines when passing from qa 2 production and back:
#import "Mode_QA.h"
//#import "Mode_Production.h"
Другие вопросы по тегам