Xcode: запуск скрипта перед каждой сборкой, которая напрямую изменяет исходный код

Что я сделал:

У меня есть скрипт, который

  1. Прочитайте некоторые файлы конфигурации, чтобы сгенерировать фрагменты исходного кода
  2. Найти соответствующие исходные файлы Objective-C и
  3. Замените некоторые части исходного кода сгенерированным кодом на шаге 1.

и Makefile, который имеет специальный файл меток времени в качестве цели создания и файлы конфигурации в качестве целевых источников:

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

Я добавил этот Makefile в качестве "Фазы сборки Run Script" поверх стека фаз сборки для цели проекта.

Что случилось:

Фаза сборки скрипта была запущена до компиляции исходного кода.

Однако, так как скрипт изменяет исходный код во время его выполнения, мне нужно было собрать дважды, чтобы получить самую последнюю версию продукта сборки. Вот что я представляю, что происходит:

  1. 1-й запуск: Xcode собирает информацию о зависимостях ---> без изменений
  2. 1-й запуск: Xcode запускает "Run Script Build Phase" ---> источник меняется за спиной Xcode
  3. 1-й запуск: Xcode заканчивает сборку, думая, что ничего не нужно обновлять
  4. 2-й запуск: Xcode собирает информацию о зависимостях ---> источник изменился, требуется перестройка!
  5. 2-й запуск: Xcode запускает этап запуска сценариев "---> все обновлено
  6. 2-й прогон: Xcode переходит к компиляции

После прочтения документации Xcode по этапам сборки я попытался добавить исходный файл, который, как известно, обновляется при каждом запуске сценария в качестве результата "Выполнить этапы построения сценария", но ничего не изменилось. Поскольку количество файлов конфигурации может варьироваться в моем проекте, я не хочу указывать каждый входной и выходной файл.

Вопрос:

Как я сообщаю XCode об изменениях исходного файла, сделанных во время "Фазы Построения Сценария Запуска"?

Редактировать:

  • Добавлено, что я поместил фазу сборки скрипта перед другими фазами сборки

7 ответов

Решение

Каждая техника, упомянутая до сих пор, является излишним. Воспроизведение комментария steve kim для наглядности:

На вкладке этапов сборки просто перетащите шаг "Выполнить сценарий" в более высокое место (например, перед "Компилировать источники").

Протестировано на XCode 6

Это решение, вероятно, устарело. Смотрите ответ с более высоким рейтингом.


Используйте "Внешнюю цель":

  1. Выберите "Проект" > "Новая цель..." в меню
  2. Выберите "Mac OS X" > "Другие" > "Внешняя цель" и добавьте ее в свой проект.
  3. Откройте его настройки и заполните настройки скрипта
  4. Откройте вкладку "Общие" настроек основной цели и добавьте новую цель как ее прямую зависимость

Теперь новая "Внешняя цель" запускается до того, как главная цель даже начинает собирать информацию о зависимостях, поэтому любые изменения, сделанные во время выполнения скрипта, должны быть включены в сборку.

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

Во-первых, вот краткое объяснение для тех, кто не понимает, почему Xcode иногда требует от вас сборки дважды (или чистой сборки), чтобы увидеть определенные изменения, отраженные в вашем целевом приложении. Xcode компилирует исходный файл, если созданный им объектный файл отсутствует, или если дата последнего изменения объектного файла раньше, чем дата последнего изменения исходного файла была в начале первой фазы сборки. Если ваш проект запускает скрипт, который изменяет исходный файл на этапе сборки перед компиляцией, XCode не заметит, что дата последнего изменения исходного файла изменилась, поэтому он не потрудится перекомпилировать его. Только когда вы создаете проект во второй раз, Xcode заметит изменение даты и перекомпилирует файл.

Вот простое решение, если ваш скрипт каждый раз изменяет одни и те же исходные файлы. Просто добавьте этап сборки Run Script в конце процесса сборки, например так:

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

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

Я тоже долго боролся с этим. Ответ заключается в использовании решения ento "Внешняя цель". ПОЧЕМУ эта проблема возникает и как мы ее используем на практике...

Шаги сборки Xcode4 не выполняются, пока ПОСЛЕ составления plist. Конечно, это глупо, потому что это означает, что любые шаги перед сборкой, которые изменяют список, не вступят в силу. Но если вы думаете об этом, они действительно вступают в силу... на следующей сборке. Вот почему некоторые люди говорили о "кэшировании" значений plist или "мне нужно сделать 2 сборки, чтобы это работало". Что произойдет, если plist будет создан, то ваш скрипт будет запущен. В следующий раз, когда вы соберете, plist будет собираться с использованием ваших измененных файлов, следовательно, вторая сборка.

Решение ento - единственный способ сделать настоящий шаг перед сборкой. К сожалению, я также обнаружил, что это не приводит к обновлению plist без чистой сборки, и я исправил это. Вот как у нас есть управляемые данными пользовательские значения в списке:

  1. Добавить проект внешней системы сборки, который указывает на скрипт Python и передает некоторые аргументы
  2. Добавить пользовательские настройки сборки в сборку. Это аргументы, которые вы передаете Python (подробнее о том, почему мы делаем это позже)
  3. Скрипт python читает некоторые входные файлы JSON и создает заголовочный файл препроцессора plist И касается основного списка приложений
  4. Основной проект имеет включенные "файлы препроцессора" и указывает на этот файл препроцессора

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

  1. Добавьте пользовательскую переменную "foo" в проект предварительной сборки.
  2. В вашей предварительной сборке вы можете использовать $(foo) для передачи значения в скрипт python.
  3. В командной строке вы можете добавить foo=test, чтобы передать новое значение.

Скрипт python использует базовые файлы настроек и позволяет пользовательским файлам настроек переопределять значения по умолчанию. Вы вносите изменения, и они сразу же попадают в список. Мы используем это только для настроек, которые ДОЛЖНЫ быть в списке. Для всего остального это пустая трата усилий.... вместо этого создайте файл json или что-то подобное и загрузите его во время выполнения:)

Надеюсь, это поможет... это была пара трудных дней, чтобы понять это.

Начиная с Xcode 4, похоже, что если вы добавите сгенерированные файлы в раздел вывода на этапе сборки, он будет учитывать этот параметр, а не генерировать ... has been modified since the precompiled header was built Сообщения об ошибках.

Это хороший вариант, если ваш скрипт генерирует только несколько файлов каждый раз.

Решение External Target от @ento больше не работает с Xcode 11.5. Решение - добавить все файлы, которые будут изменены вOutput Files в сценарии запуска.

Другой вариант - создать структуру подпроекта с вашими сценариями и просто добавить ее в качестве зависимости для всех целей. Фазовые сценарии этого подпроекта теперь должны выполняться перед всеми целями.

Другие вопросы по тегам