Organisation projet et environnements

Hello,


 


J'essaie d'organiser au mieux mon projet. Comme la majorité des projets, je dois compiler le projet sur plusieurs environnements ( Prod, recette,...).


 


Ce que je fais pour l'instant  : je crée plusieurs Targets ( prod, recette,..) et pour chaque tarrget un ficher de config associé et un .plist pour les différentes urls, bon ça marche bien mais je trouve que créer une target pour cet usage est un peu "Lourd" ( je me suis retrouvé avec 12 Targets sur un projet :)).


 


Comment vous faites pour vous facilitez la vie ?


 


 


 


 


 


   


Réponses

  • CéroceCéroce Membre, Modérateur

    Je suis pas un spécialiste, mais t'as regardé un peu les Schemes ?


    De base, on peut déjà  distinguer Debug et Release, il me semble qu'on peut en ajouter.


  • AliGatorAliGator Membre, Modérateur
    Une macro.
  • samirg2303samirg2303 Membre
    août 2015 modifié #4

    @Céroce Oui on peut rajouter nos propres schemes, c'est ce que je fais justement : je crée un schème Beta_distribution pour les béta rélease, mais ce que je veux est la configuration des différentes URL (Prod , recette,...).


     


    Ce que je pense faire est une simple interface graphique  qui te permet de sélectionner l'environnement avant le lancement de l'application. 


  • FKDEVFKDEV Membre
    août 2015 modifié #5
    Un script pre-build qui copie les bons fichiers en fonction du scheme.

    Sinon un script en ligne de commande hors Xcode qui copie les fichiers en fonction d'un parametre et lance la compilation (pas besion de schemes dans ce cas)
  • AliGatorAliGator Membre, Modérateur
    // .h
    #define PLATFORM_PROD 0
    #define PLATFORM_PREPROD 1
    #define PLATFORM_DEV 2
    #define PLATFORM_BOUCHONS 3

    #ifndef PLATFORM
    #define PLATFORM PLATFORM_PROD
    #endif

    extern NSString* const FooWebServiceBaseURL;


    // .m
    #if PLATFORM == PLATFORM_BOUCHONS
    FooWebServiceBaseURL = @stub://api.stubs.org/;
    #elsif PLATFORM == PLATFORM_DEV
    FooWebServiceBaseURL = @http://dev.api.myapp.com/;
    #elsif PLATFORM == PLATFORM_PREPROD
    FooWebServiceBaseURL = @http://preprod.api.myapp.com/;
    #else
    FooWebServiceBaseURL = @http://api.myapp.com/;
    #endif
    Et ensuite je bascule d'une plateforme à  l'autre :
    1) Soit en changeant le #define PLATFORM dans le code
    2) Soit en changeant le #define via un Build Setting (genre GCC_PREPROCESSOR_DEFINITIONS)
    3) Mais en général c'est mon intégration continue qui s'en charge : de mon côté si je veux tester sur une PF ou une autre rapidement, je change le #define PLATFORM dans le code pour basculer, mais quand je demande à  ma CI génère une OTA pour l'envoyer au client, j'ai 4 tâches différentes dans ma CI " une pour chaque plateforme " qui invoquent toutes la même commande rake mais avec un paramètre qui injecte "-DPLATFORM=1" ou une autre valeur selon la PF que je veux livrer.


    Cette solution me permet d'éviter de multiplier les targets pour en avoir 4 identiques à  juste un fichier près " d'autant que j'utilise déjà  des targets différents quand je fais une application en Marque Blanche, où là  je compile des Assets et des fichiers de constantes différents en fonction de la marque pour laquelle je génère le produit. Donc je vais éviter d'utiliser les targets aussi pour les différentes variantes de conf WS Prod/Preprod/Dev sinon ça multiplie trop et y'a des chances qu'en terme de maintenance j'oublie de reporter une modification que je pourrais faire sur un target sur les autres targets.


    Je pourrais utiliser les Configurations (ne plus avoir que Debug et Release mais Debug-Prod, Debug-Preprod, Release-Prod, ...), après c'est aussi un peu ch*ant à  gérer (et marche mal quand on a un Workspace avec plusieurs projets, même si on peut se débrouiller et que par exemple CocoaPods a une option pour gérer ce cas là )



    Utiliser les scheme pour ça, je ne vois pas trop comment, quel levier on a dans les schemes qui permettrait de builder des fichiers différents, ou de façon conditionnelle, ou injecter des macros préprocesseur, etc... via des Schemes ?
    Les seuls points d'action que je vois côté Scheme qui pourrait servir c'est d'injecter des variables d'environnement ou bien des paramètres de ligne de commande lors du lancement de l'application via Xcode. Ce qui marche très bien (on peut imaginer déclarer des paramètres de lancement comme "-platform 1" qui va injecter dans le domaine command-line des NSUserDefaults la valeur 1 pour la clé "platform", donc facile à  récupérer par code au runtime), mais (1) ça veut dire que c'est un contrôle au Runtime, et pas à  la compilation (donc le code de la dev/preprod & prod restera dans le binaire final, dommage) et (2) de toute façon ça n'a d'effet que quand l'app est lancée via Xcode, car c'est des paramètres de lancement, pas de compilation, donc quand vous ferez votre binaire final ou votre livraison OTA intermédiaire à  votre client, vous pourrez pas lui faire une livraison intermédiaire pointant vers la Preprod automatiquement quand il la lancera depuis son iPhone...



    Après, quand je vais passer à  des projets purs Swift, ça sera une autre histoire car je ne pourrais pas appliquer ces mêmes principes à  Swift, qui n'a pas de macros. Je n'ai pas encore réfléchi à  comment j'allais faire dans ce cas-là .
  • AliGatorAliGator Membre, Modérateur
    août 2015 modifié #7
    Bon, j'ai posé la question sur Slack dans une room dédié aux devs iOS, et j'ai eu quelques réponses sont intéressantes.

    Dave Delong (qui est Apple Evangelist donc il devrait savoir de quoi il cause ^^) a proposé ceci :

    i'm a big fan of the strategy pattern: https://en.wikipedia.org/wiki/Strategy_pattern.
    it's doesn't _exactly_ answer your question, but it's the approach I take over `#define`'d macros

    I have an object that exposes the values, like a `DDEnvironment` or something
    and then I initialize it with the proper `EnvironmentVariableProvider`
    the `EnvironmentVariableProvider` has multiple implementations, depending on the configuration, and I instantiate the right one to give to the `DDEnvironment` at init time
    and then everywhere else in my code I use `DDEnvironment`

    as for how you get this, you could have your `.xcconfig` files set a value about which environment to use, and then set that into your Info.plist, or you just conditionally build in the right `environment.plist` or something


    Cependant il admet que ce n'est pas non plus simple ni idéal, et a déjà  remonté un bug à  ses collègues de la team Xcode à  ce propos (rdar://problem/20616717) pour proposer comme solution de pouvoir appliquer plusieurs Configurations croisées plutôt que de devoir choisir qu'une seule configuration Debug/Release, comme ça tu pourrais choisir la config "Debug" + la config "Preprod" et ça combine les deux, plutôt que d'avoir à  faire 4 configurations "Debug-Preprod", "Release-Prepêrod", "Debug-Prod", "Release-Prod" et risquer de t'emmêler les pinceaux à  les maintenir. Mais bon, pour l'instant ce problème n'a toujours pas été adressé et Xcode ne propose toujours pas de tel mécanisme :-/


    Sinon, un autre propose sensiblement la même chose:

    We do something similar to what @davedelong is suggesting, and its configured based on some keys in the info plist. Our CI uses plistbuddy to change those at build time.

    There's really only two, one for the backend environment and one for what “kind” of app it is (dev | qa | beta | app store) which sets things like crashlytics, mixpanel, etc


    Un autre propose une approche avec des fichiers PLIST aussi, intéressante :

    This approach has worked well for me http://code.tutsplus.com/tutorials/ios-quick-tip-managing-configurations-with-ease--mobile-18324



    Quand j'ai évoqué que le problème avec ce genre d'approches c'est qu'on avait alors dans l'application à  tout moment toutes les implémentations / valeurs pour tous les environnements, et qu'on prenait la décision au Runtime plutôt qu'à  la compilation, Dave a proposé une alternative : extraire les différentes implémentations possibles de son EnvironmentVariablesProviders dans un framework (weak-linké) et choisir avec lequel on link à  la compilation, par exemple via des configurations différentes ayant une valeur différente pour `Other Linker Flags`.

    Mais il admet que c'est qd mm s'embêter beaucoup juste pour éviter que les infos de preprod partent sur l'AppStore et que ça requiert beaucoup de configuration, donc la solution PLIST est peut-être la plus simple à  mettre en oeuvre.
  • Merci pour tous ces retours.


  • J'utilise une approche différente, qui mixe un peu tout ça.


    Mon objectif était de pouvoir :


    - définir le mode de fonctionnement au build, notamment pour les tests unitaires et fonctionnels


    - avoir la possibilité dans certains cas de définir le mode de fonctionnement à  l'exécution, en particulier pouvoir basculer entre prod et pre-prod pendant les tests de validation (sous TestFlight)


     


    J'ai une classe OperationMode qui gère ce que j'appelle le mode de fonctionnement (test, pré-prod, prod, et pourquoi pas, tout autre mode que l'on peut inventer, en l'occurence j'ai ajouté un mode Demo qui sert aux commerciaux ; ils utilisent un environnement type bac à  sable). Cette classe fournit :


    - une méthode +mode qui permet à  chaque autre classe qui voudrait adapter son comportement de connaà®tre le mode courant


    - des méthodes setter/getter (de classe aussi) pour modifier/obtenir les User Defaults, car certains peuvent dépendre du mode de fonctionnement


     


    Le mode de fonctionnement fourni par OperationMode était dans un premier temps défini par une macro dont la valeur était fixée à  la compilation avec la technique d'Aligator citée ci-dessus. Mais j'ai abandonné cette technique pour adopter le fonctionnement suivant :


    - le mode de fonctionnement est fixé par un paramètre au lancement (utilisé concrètement dans le Schème Debug)


    - sinon le mode est donné par un User Default spécifique (OperationMode tout simplement)


    - sinon, en l'absence de ce User Default, le mode de fonctionnement est "Production"


     


    Pour compléter le tout, j'ai un Settings.bundle qui définit une valeur par défaut pour OperationMode :


    - ainsi l'utilisateur peut changer le mode de fonctionnement par l'application Préférences (mon bidule est un peut rustre pour l'instant, l'utilisateur doit arrêter et relancer l'application pour que le changement soit effectif)


    - ce Settings.bundle est absent de ma Target Delivery, qui sert pour la publication de l'application


  • C'est pas mal, c'est vrai qu'on ne pense pas aux paramètres de lancement sur iOS. L'avantage du setting c'est qu'il est "par device". Pas besoin de changer de scheme si tu as des devices en test et en pre-prod.
Connectez-vous ou Inscrivez-vous pour répondre.