Убедитесь, что сгенерированные, но проверенные файлы не перестраиваются после синхронизации git - PullRequest
0 голосов
/ 10 ноября 2018

Я не уверен, является ли это мерзавец вопросом или вопросом make, или, может быть, и тем и другим, но здесь идет ...

Представьте, что у вас есть файл foo.cpp, сгенерированный программой-генератором gen.py. Если программа генератора изменена, ее необходимо запустить снова, чтобы сгенерировать свежую версию foo.cpp.

Правило makefile, которое кодирует это поведение, может выглядеть следующим образом 1 :

foo.cpp: gen.py
    gen.py

Для активной разработки на gen.py, я думаю, это идеально.

Однако рассмотрим случай, когда вы делаете свой проект доступным удаленно через git, скажем, на github или на каком-либо подобном сайте. Пользователи скачают и сделают ваш проект.

Традиционный способ обработки сгенерированных файлов - не проверять их все: не включайте foo.cpp в git all (добавьте его в ваш `.gitignore), и когда пользователь создаст ваш проект, файл будет генерироваться.

Однако альтернативный подход - это для включения сгенерированного файла foo.cpp в git. Это повышает вероятность того, что gen.py и foo.cpp могут быть "несинхронизированы" 2 , если один из них изменен без обновления другого, но имеет несколько других преимуществ:

  • Пользователи, которые просто хотят построить проект, но никогда не обновляют gen.py, не нуждаются в предварительных условиях для запуска gen.py (например, Python).
  • Немодифицированная сборка быстрее, потому что шаг генерации не выполняется.
  • Вы получаете прямую обратную связь во время подготовки / фиксации о том, какие изменения foo.cpp подразумеваются gen.py, так как файл будет частью diff.

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

В этом контексте мой вопрос:

Для пользователя, который недавно клонирует проект или синхронизирует коммит, включающий оба файла, как я могу убедиться, что make не пытается сгенерировать foo.cpp? По умолчанию git будет использовать текущее время при синхронизации файлов, поэтому foo.cpp и gen.py будут иметь одинаковые временные метки, а foo.cpp будет перестроен.

Я не могу попросить пользователей изменить их конфигурацию git.


1 Возможно, для foo.cpp будут дополнительные зависимости, которые также будут указываться в качестве предварительного условия, но это "базовый случай".

2 Разумный подход состоит в том, чтобы обеспечить их синхронизацию с помощью git hook.

Ответы [ 2 ]

0 голосов
/ 11 ноября 2018

A post-merge hook может решить вашу проблему:

  1. Добавьте скрипт hooks/post-merge в свой репозиторий, содержащий, например, (с bash версия> 4):

    $ cat hooks/post-merge
    #!/usr/bin/env bash
    printf 'running post-merge\n'
    declare -A generated=()
    generated["foo.cpp"]="gen.py"
    # add other (generated, generator) pairs to the generated associative array
    for g in "${!generated[@]}"; do
        s="${generated[$g]}"
        if ! git diff ORIG_HEAD HEAD --exit-code -- "$g" "$s" &> /dev/null; then
            printf 'touch %s\n' "$g"
            touch "$g"
        fi
    done
    $ chmod +x hooks/post-merge
    
  2. Добавьте сценарий runme.sh и попросите пользователей запустить его вручную после того, как они клонировали репозиторий (им придется действительно доверять вам, но безопасность выходит за рамки этого ответа):

    $ cat runme.sh
    #!/usr/bin/env bash
    for s in hooks/*; do
        ln -sf ../../"$s" .git/hooks
    done
    

Для каждой (generated, generator) пар, если одна из двух модифицируется git pull, хук post-merge коснется generated, так что его отметка времени последней модификации новее, чем generator .

Конечно, это только отправная точка, возможно, есть еще несколько моментов, которые нужно учитывать (rebase, ...).

0 голосов
/ 10 ноября 2018

Вы можете сравнить вывод и обновить цель, только если она отличается. Скажем, например, ваш gen.py запишет свой вывод на стандартный вывод, поэтому обычно у вас будет такое правило:

foo.cpp: gen.py
        ./gen.py > $@

Вы можете изменить это на:

foo.cpp: gen.py
        ./gen.py > $@.tmp
        cmp -s $@ $@.tmp && rm -f $@.tmp || mv -f $@.tmp $@

Недостатком здесь является то, что до тех пор, пока вы DO не обновите foo.cpp, рецепт gen.py всегда будет запущен. Однако никакие цели, которые зависят от foo.cpp, не будут построены, потому что его отметка времени не изменилась.

Если это дорого, и вы хотите обойти это, вам придется сделать что-то гораздо более сложное: если сравнение истинно (без разницы), вам нужно сбросить отметку времени на gen.py, чтобы она была такой же, как цель; это гарантирует, что цель больше не будет считаться устаревшей в будущем. Примерно так:

foo.cpp: gen.py
        ./gen.py > $@.tmp
        if test -e $@ && cmp -s $@ $@.tmp; then \
            rm -f $@.tmp; \
            touch -r $@ gen.py; \
        else \
            mv -f $@.tmp $@; \
        fi

Если вы не можете легко изменить имя выходного файла для gen.py, вам придется сделать что-то еще более отвратительное, например переименовать $@ в $@.old, прежде чем запустить ./gen.py, затем изменить его или использовать новый в зависимости от обстоятельств.

ETA Если вы просто хотите избежать запуска gen.py, если это та же версия, это скорее вопрос Git, чем вопрос make-файла, потому что вы действительно спрашиваете, как узнать, существуют ли эти файлы? находятся в том же Git коммита. Это легко, но, как говорится в моем комментарии ниже, я не думаю, что это на самом деле правильно:

foo.cpp: gen.py
        tver=$$(git log -n1 --oneline $@ 2>/dev/null); \
        pver=$$(git log -n1 --oneline $< 2>/dev/null); \
        if test -z "$$tver" || test -z "$$pver" || test "$$tver" != "$$pver"; then ./gen.py; fi
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...