web-dev-qa-db-fra.com

Xcode: exécuter un script avant chaque build qui modifie directement le code source

Ce que j'ai fait:

J'ai un script qui

  1. Lire certains fichiers de configuration pour générer des extraits de code source
  2. Trouvez les fichiers source Objective-C pertinents et
  3. Remplacez certaines parties du code source par le code généré à l'étape 1.

et un Makefile qui a un fichier d'horodatage spécial comme cible de création et les fichiers de configuration comme sources cibles:

SRC = $(Shell find ../config -iname "*.txt")
STAMP = $(PROJECT_TEMP_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME).stamp
$(STAMP): $(SRC)
    python inject.py
    touch $(STAMP)

J'ai ajouté ce Makefile en tant que "Exécuter la phase de génération de script" au-dessus de la pile des phases de génération pour la cible du projet.

Qu'est-il arrivé:

La phase de construction du script a été exécutée avant la compilation de la source.

Cependant, étant donné que le script modifie le code source lors de son exécution, j'ai dû générer deux fois pour obtenir la version la plus récente du produit de génération. Voici ce que j'imagine se produire:

  1. 1ère exécution: Xcode collecte les informations de dépendance ---> pas de changement
  2. 1ère exécution: Xcode exécute "Exécuter la phase de création de script" ---> la source est modifiée derrière le dos de Xcode
  3. 1ère manche: Xcode termine la construction, pensant que rien ne doit être mis à jour
  4. 2ème exécution: Xcode collecte des informations de dépendance ---> la source a changé, a besoin d'être reconstruite!
  5. 2ème exécution: Xcode exécute Run Script Build Phase "---> tout est à jour
  6. 2ème exécution: Xcode procède à la compilation

Après avoir lu documentation Xcode sur les phases de construction , j'ai essayé d'ajouter un fichier source qui est connu pour être mis à jour chaque fois que le script est exécuté en tant que sortie de "Exécuter les phases de construction de script", mais rien n'a changé. Étant donné que le nombre de fichiers de configuration peut varier dans mon projet, je ne veux pas spécifier chaque fichier d'entrée et de sortie.

Question:

Comment puis-je informer Xcode des modifications du fichier source effectuées pendant la "phase d'exécution de script"?

Éditer:

  • Ajout du fait que j'ai placé la phase de construction du script avant les autres phases de construction
55
ento

Chaque technique mentionnée jusqu'à présent est une exagération. Reproduire le commentaire de steve kim pour la visibilité:

Dans l'onglet des phases de construction, faites simplement glisser l'étape "Exécuter le script" vers un emplacement plus élevé (par exemple avant "Compiler les sources").

Testé sur XCode 6

82
marinosb

Cette solution est probablement obsolète. Voir plutôt la réponse votée la plus élevée.


Utilisez "Cible externe":

  1. Sélectionnez "Projet"> "Nouvelle cible ..." dans le menu
  2. Sélectionnez "Mac OS X"> "Autre"> "Cible externe" et ajoutez-le à votre projet
  3. Ouvrez ses paramètres et remplissez la configuration de votre script
  4. Ouvrez l'onglet "Général" des paramètres de la cible principale et ajoutez la nouvelle cible car c'est une dépendance directe

Maintenant, la nouvelle "cible externe" s'exécute avant la cible principale commence même à collecter des informations de dépendance, de sorte que toutes les modifications apportées pendant l'exécution du script doivent être incluses dans la génération.

28
ento

Il existe une autre option, légèrement plus simple, qui ne nécessite pas de cible distincte, mais elle n'est viable que si votre script a tendance à modifier les mêmes fichiers source à chaque fois.

Tout d'abord, voici une brève explication pour quiconque ne sait pas pourquoi Xcode vous oblige parfois à créer deux fois (ou à faire une construction propre) pour voir certains changements reflétés dans votre application cible. Xcode compile un fichier source si le fichier objet qu'il produit est manquant, ou si la date de dernière modification du fichier objet est antérieure à la date de dernière modification du fichier source au début de la première phase de construction. Si votre projet exécute un script qui modifie un fichier source dans une phase de génération de pré-compilation, Xcode ne remarquera pas que la date de dernière modification du fichier source a changé, il ne prendra donc pas la peine de le recompiler. Ce n'est que lorsque vous générez le projet une deuxième fois que Xcode remarquera le changement de date et recompilera le fichier.

Voici une solution simple si votre script modifie à chaque fois les mêmes fichiers source. Ajoutez simplement une phase de construction de Script d'exécution à la fin de votre processus de construction comme ceci:

touch Classes/FirstModifiedFile.m Classes/SecondModifiedFile.m
exit $?

L'exécution de touch sur ces fichiers source à la fin de votre processus de génération garantit qu'ils auront toujours une date de dernière modification plus tardive que leurs fichiers objets, donc Xcode les recompilera à chaque fois.

3
cduhn

À partir de Xcode 4, il semble que si vous ajoutez les fichiers générés à la section de sortie de la phase de construction, il respectera ce paramètre et ne générera pas le ... has been modified since the precompiled header was built messages d'erreur.

C'est une bonne option si votre script ne génère à chaque fois qu'une poignée de fichiers.

2
Senseful

Moi aussi, j'ai lutté longtemps avec ça. La réponse est d'utiliser la solution "External Target" d'ento. Il est POURQUOI ce problème se produit et comment nous l'utilisons dans la pratique ...

Les étapes de construction de Xcode4 ne s'exécutent qu'après la compilation du plist. C'est idiot, bien sûr, car cela signifie que toutes les étapes de pré-construction qui modifient le plist ne prendront pas effet. Mais si vous y réfléchissez, ils prennent effectivement effet ... sur la construction NEXT. C'est pourquoi certaines personnes ont parlé de "mise en cache" des valeurs de plist ou "Je dois faire 2 builds pour le faire fonctionner". Ce qui se passe, c'est que le plist est construit, puis votre script s'exécute. La prochaine fois que vous générez, le plist construit à l'aide de vos fichiers modifiés, d'où la deuxième génération.

la solution d'ento est le seul moyen que j'ai trouvé pour réellement faire une véritable étape de pré-construction. Malheureusement, j'ai également constaté que cela ne provoquait pas la mise à jour du plist sans une construction propre et j'ai corrigé cela. Voici comment nous avons des valeurs utilisateur basées sur les données dans la liste:

  1. Ajoutez un projet External Build System qui pointe vers un script python et transmet certains arguments
  2. Ajoutez des paramètres de génération définis par l'utilisateur à la génération. Ce sont les arguments que vous passez à python (plus sur pourquoi nous le faisons plus tard)
  3. Le script python lit certains fichiers JSON d'entrée et crée un fichier d'en-tête de préprocesseur plist ET touche le plist de l'application principale
  4. Le projet principal a activé les "fichiers plist de prétraitement" et pointe vers ce fichier de préprocesseur

L'utilisation du toucher sur le fichier plist de l'application principale entraîne la cible principale à générer le plist à chaque fois. La raison pour laquelle nous transmettons les paramètres de génération en tant que paramètres est que notre génération de ligne de commande peut remplacer les paramètres:

  1. Ajoutez une variable définie par l'utilisateur "foo" au projet de pré-construction.
  2. Dans votre pré-construction, vous pouvez utiliser $ (foo) pour passer la valeur dans le script python.
  3. Sur la ligne de commande, vous pouvez ajouter foo = test pour passer une nouvelle valeur.

Le script python utilise des fichiers de paramètres de base et permet aux fichiers de paramètres définis par l'utilisateur de remplacer les valeurs par défaut. Vous apportez une modification et elle se retrouve immédiatement dans la liste. Nous n'utilisons cela que pour les paramètres qui DOIVENT être dans le plist. Pour toute autre chose, c'est une perte d'effort .... générer un fichier json ou quelque chose de similaire à la place et le charger au moment de l'exécution :)

J'espère que cela aide ... ça a été quelques jours difficiles à comprendre cela.

1
mstelzer