Так все же, зачем нужен make?



    Все началось, казалось бы, с простого вопроса, который сначала ввел меня в ступор — "Зачем нужен make? Почему нельзя обойтись bash скриптами?". И я подумал — Действительно, зачем нужен make? (и самое важное) Какие проблемы он решает?


    Тут я решил немного подумать — как бы мы собирали свои проекты, если бы у нас не было make. Допустим, мы имеем проект с исходниками. Из них нужно получить исполняемый файл (или библиотеку). На первый взгляд задача вроде простая, но пошли дальше. Пусть на начальном этапе проект состоит из одного файла.



    Для его компиляции достаточно выполнить одну команду:


    $ gcc main.c -o main

    Это было довольно просто. Но проходит некоторое время, проект развивается, в нем появляются некоторые модули и исходных файлов становится больше.



    Для компиляции нужно выполнить условно вот такое количество команд:


    $ gcc -c src0.c
    $ gcc -c src1.c
    $ gcc -c main.c
    $ gcc -o main main.o src0.o src1.o

    Согласитесь, это довольно длительный и кропотливый процесс. Выполнять это вручную я бы не стал. Я бы подумал, что этот процесс можно автоматизировать, просто создав скрипт build.sh, который бы содержал эти команды. Окей, так намного проще:


    $ ./build.sh

    Погнали дальше! Проект растет, количество файлов с исходниками увеличивается и строк в них тоже становится больше. Мы начинаем замечать, что время компиляции заметно возросло. Тут мы видим существенную недоработку нашего скрипта — он выполняет компиляцию всех наших 50 файлов с исходниками, хотя мы модифицировали только один.



    Так не пойдет! Время разработчика слишком ценный ресурс. Что же, мы можем попытаться доработать скрипт сборки таким образом, чтобы перед компиляцией производилась проверка времени модификации исходников и объектных файлов. И компилировать только те исходники, которые были изменены. И условно это может выглядеть как-то так:


    #!/bin/bash
    
    function modification_time
    {
        date -r "$1" '+%s'
    }
    
    function check_time
    {
        local name=$1
        [ ! -e "$name.o" ] && return $?
        [ "$(modification_time "$name.c")" -gt "$(modification_time "$name.o")" ] && return $?
    }
    
    check_time src0 && gcc -c src0.c
    check_time src1 && gcc -c src1.c
    check_time main && gcc -c main.c
    gcc -o main main.o src0.o src1.o

    И теперь компилироваться будут только те исходники, которые были модифицированы.



    Но что произойдет, когда проект превратится во что-нибудь подобное:



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


    Само собой мы видим, что напрашивается какое то общее решение этой проблемы. Инструмент, который бы предоставил механизм для проверки зависимостей. И вот тут мы потихоньку подобрались к изобретению make. И теперь, зная с какими проблемами мы столкнемся в процессе сборки проекта, в конце концов я бы сформулировал следующие требования к make:


    • анализ временных меток зависимостей и целей
    • выполнение минимального объема работы, необходимого для того, чтобы гарантировать актуальность производных файлов
    • (ну и + параллельное выполнение команд)

    Makefile


    Для описания правил сборки проекта используются Makefile'ы. Создавая Makefile, мы декларативно описываем определенное состояние отношений между файлами. Декларативный характер определения состояния удобен тем, что мы говорим, что у нас есть некоторый список файлов и из них нужно получить новый файл, выполнив некоторый список команд. В случае использования какого-либо императивного языка (например, shell) нам приходилось бы выполнять большое количество различных проверок, получая на выходе сложный и запутанный код, тогда как make делает это за нас. Главное построить правильное дерево зависимостей.


    < цель > : < зависимости ... >
                     < команда >
                        ...
                        ...

    Не буду рассказывать про то, как писать Makefile'ы. В интернете очень много мануалов на эту тему и при желании можно обратиться к ним. И к тому же, мало кто пишет Makefile'ы вручную. А очень сложные Makefile'ы могут стать источником осложнений вместо того, чтобы упростить процесс сборки.

    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 74

      +11
      А между прочим, упреждая попытки покритиковать автора за очевидность темы, не все, особенно начинающие разработчики, понимают последовательность сборки проекта, особенно если в дело идут shared library и прочие многоступенчатые зависимости… При том, что понимать, что происходит внутри всех этих Makefile, proj.pro, proj.sln и т.д. и т.п., очень и очень полезно, например при разборе сообщений об ошибках сборки. Так что думаю, то что вы написали — полезно
        +1

        Не такая уж очевидная тема, если ты не проф. разработчик на низкоуровневом языке.
        Я, например, изучал с++ в институте. И это скорее можно было назвать изучением синтаксиса и алгоритмов. Причем писал программы, компилировал. Но пиратская студия, сами понимаете:) нажал зеленую кнопку и все готово.


        А теперь хотя бы есть понимание, зачем я делаю мейк каждый раз, когда ставлю очередной питон или постгрес, например.
        Но понимание, на самом деле, весьма поверхностное. Неужели все так просто — обновить объектные файлы и слинковать их воедино?

          +2
          Неужели все так просто — обновить объектные файлы и слинковать их воедино?

          Отслеживать зависимости можно, собирать промежуточные билиотеки, генерировать промежуточные файлы. Можно даже утилиты собирать промежуточные для генерирования всякого.
            +3
            Неужели все так просто — обновить объектные файлы и слинковать их воедино?
            Ну вы же спустились в кроличью нору всего на метр! Конечно тут ещё светло.

            Представьте, что один из этих файлов генерится утилитой, которая вроде как тоже из объектных файлов — но под другую архитектуру (потому как вы делаете проект под Android, а работаете всё не под Android'ом). У вас начнут появляться «параллельные» объектные файлы под две архитектуры. Или вам нужно собрать две версии библиотеки — «обычную» (для программы) и «инсталляционную». Вызвать bison, flex и бог знает что ещё.

            Так и превращаются концептуально простые вещи вот в таких монстров где десятки файлов и тысячи строк кода…
              0

              CMake с минимальным окружением на питоне отлично справляется с кроссом под 4 архитектуры.
              Параллельные иерархии просто лежат в отдельных папках и подхватываются без проблем.
              Аналогично с дебаг/релиз/инсталл/...

                +6

                CMake внезапно генерирует Makefile, который выполняется тем самым make ;)

                  0

                  Внезапно, но это уже проблемы CMake, а не меня как пользователя системы сборки.
                  И проблем в генерируемом проекте особо не наблюдается.

                  0

                  Подскажите, а зачем нужен питон? Разве CMake сам с описанной задачей не справляется?

                    +2

                    Иногда проще написать костылик на питоне, чем сообразить, как объяснить cmake свое видение задачи.

                      0
                      Да, именно это я и ожидал услышать)))
                +2

                На самом деле всё ещё проще, если забыть об объектных файлах и т.п., и вначале понять принцип действия make.


                А он прост до безумия. Пишем: цель A зависит от целей B, C, D, и для её сборки надо выполнить такие-то действия. Точка.
                То же самое пишем для прочих целей.
                Простейший декларативный язык. Просто записываем дерево целей и способы их сборки, всё.

                  +2
                  Make — это всего лишь Prolog для файловой системы :)
                    0

                    Бинго!

                    +2
                    Простейший декларативный язык.
                    Это он простейший, пока с реальным миром не столкнётся.

                    Пишем: цель A зависит от целей B, C, D, и для её сборки надо выполнить такие-то действия. Точка.
                    Прекрасно. А теперь у вас появляется скрипт, который порождает два файла (скажем .h файл и .cc файл). Что делать будете?

                    Любая система сборки «проста и невинна», пока все вокруг знают о её сущесствовании и весь код пишут сообразно нуждам этой системы сборки.

                    А вот если, вдруг, «парадигма меняется»… вот тут-то и появляются костыли.

                    А она меняется, потому что продукт развивается. Раньше все библиотеки клались в одно место — а теперь, вдруг, из решили разрести. Раньше у вас не было различий между билд-системой и хост-системой — а сегодня, вдруг, появились. И так далее.
                      0

                      Два артефакта одним скриптом – вообще не проблема: "Multiple Targets in a Rule".


                      Но насчёт написания кода сообразно нуждам системы сборки – вы, imho, правы. Это даже не для того, чтобы системе сборки легко было :-), а для облегчения поддержки кодовой базы.

                        0
                        Два артефакта одним скриптом – вообще не проблема: «Multiple Targets in a Rule».
                        Вообще-то проблема. Вы я вижу, гуглить уже научились — а теперь вспомните про то, что иногда нужно ещё и думать… прочитайте то, что вы нагуглили и поймите — в чём беда.

                        тут ответ
                        Беда в том, что, цитирую: A rule with multiple targets is equivalent to writing many rules, each with one target, and all identical aside from that.

                        То есть вы не решаете проблему. Вместо того, чтобы научить Make порождать из одного файла два — вы учите его порождать из одного файла первый файл и второй. Что не одно и то же: при параллельной сборке у вас начинуются гонки и билд то проходит, то нет. Необычайно увлекательно.

                        Замечу, кстати, что «два файла из одного исходника» — это норма для весьма немалого количества языков. Скажем GNAT штатно требует два файла (.ads и .adb) и порождает два файла на выходе — .ali и .o. Та же ситуация возникает с модулями в C++20… да даже ассмеблеры 60х годов могут порождать в дополнение к объектому файлу дополнительные файлы с листигнами, списками символов и прочим!
                        Но насчёт написания кода сообразно нуждам системы сборки – вы, imho, правы. Это даже не для того, чтобы системе сборки легко было :-), а для облегчения поддержки кодовой базы.
                        Вот только если у вас проект мало-мальски осмысленного размера — то вы либо вынуждены поднимть большой такой флаг NIH — с соотвествующими требованиями к поставщикам, либо… подстраиваться как-то под то, что компоенты, поставляемые другими, могут и не ложиться в вашу красивую и «умную» схему…
                          0

                          С чего бы тут возникло состояние гонки? Да, make -j может порождать несколько процессов для параллельной сборки – но с чего вы взяли, что и makefile (точнее, построенный по нему DAG таргетов) он обрабатывает в несколько потоков?


                          Впрочем, я не призываю использовать make. А вот понять – очень полезно.

                            +1
                            Впрочем, я не призываю использовать make. А вот понять – очень полезно.
                            А почему вы призывате других сделать то, чего вы сами «ниасилили»?

                            Да, make -j может порождать несколько процессов для параллельной сборки – но с чего вы взяли, что и makefile (точнее, построенный по нему DAG таргетов) он обрабатывает в несколько потоков?
                            Ну потому что я, в отличие от вас, знаю как работает make. И умею читать. Да, редкое сегодня качество, признаю.

                            Вот вам пример, может заставит задуматься:

                            $ cat Makefile 
                            Усё: Жопа Попа
                            
                            Жопа Попа: Ха-ха.txt
                            	@echo Попа-попа
                            	@sleep 1
                            	@echo Ой-какая-попа
                            
                            $ make -j
                            Попа-попа
                            Попа-попа
                            Ой-какая-попа
                            Ой-какая-попа
                            

                            Не объясните с какого перепугу тут генератор Жопыпопы (sleep 1 для чистоты эксперимента) вызвался дважды? Причём впараллель? И чем это может закончится если он будет действительно что-нибудь генерировать?

                            Да, вокруг этой задачи есть несколько костылей разных сортов и размеров. Но самоё весёлое даже не в них. Факт остаётся фактом: удивительным убразом Make умеет в несколько файлов из одного правила одновременно… более того — там есть такие шняги по умолчанию… вот только это описано вот не в разделе «Multiple Targets in a Rule» и вот нифига не очевидно, в чём проблема, пока не начнёшь разбираться с «flake build»ом, который «уже всех задолбал».

                            P.S. Вот те рассуждения про GNAT — на самом деле были подсказкой. Но вы, похоже, ниасилили…
                              0

                              Спасибо!

                                0

                                Вы про Handling Tools that Produce Many Outputs?
                                Да уж, не сталкивался, действительно жопа-попа.

                                  0
                                  Самое весёлое, что самый разумный вариант там упомянут вскользь, в самом-самом конце, мимоходом так. Даже без примера.

                                  А вот вариант, который напрашивается, который «очевиден», который «легко гуглится»… не работает. Вернее работает через раз и периодически будет вам красить боты в красный цвет «непонятно почему»…
                                    0

                                    pattern rule? ну да, в реальной жизни оно покрывает, наверное 99% всех случаев, но вот именно с жопа-попа как-то не особо применимо ИМХО )

                                      0
                                      Ну натянуть-то можно. Главная беда — как раз даже не в том, что поправить сложно — там, как вы обнаружили, даже и отдельная глава есть в мануале с подробным описанием.

                                      Самая большая беда — с осознанием: если вы про эту конкретную «жопу» не в курсе — то догадываться почему у вас боты падают можно буквально месяцами.

                                      И вот в этом — вся беда Make: он так-то инструмент несложный, но чтобы им пользоваться нужно очень глубоко в него «въехать», фактически весь мануал в себя «впитать»… карго-культ чреват очень неприятными последствиями… даже не знаю как к этому относиться.

                                      Для инструментов «старой школы» (Bash там или TeX) — это весьма характерно. Главное задаёшь вопрос почему ни черта не работает — а в ответ получаешь ссылку на «замечание к примечанию»… всё ж просто — бери и работай… только прочитай вначале всё от корки до корки.
                                        +1
                                        Для инструментов «старой школы» (Bash там или TeX) — это весьма характерно. Главное задаёшь вопрос почему ни черта не работает — а в ответ получаешь ссылку на «замечание к примечанию»… всё ж просто — бери и работай… только прочитай вначале всё от корки до корки.

                                        Обратная сторона: "инструменты старой школы" подразумевают ознакомление с документацией перед применением. Подразумевается, что человек будет понимать (хотя бы в общих чертах) что происходит.


                                        "Новая школа", зачастую, — это просто набор скриншотов "некст/некст/готово". И даже на хабре (и даже в статьях про unix/linux) это в полный рост, куча статей вроде "сегодня мы настроим XXX. запишем YYY в этот файл, запустим ZZZ — готово". Такой недоделанный плейбук ансибла.


                                        У вас есть хорошие примеры "новой школы"?

                          0
                          А теперь у вас появляется скрипт, который порождает два файла

                          У меня такое видение проблемы. Если скрипт генерирует несколько целей, и важно этот факт отслеживать в ходе сборки, то можно поступить так. (Я поступал).

                          Выделяем «главный порождаемый файл», скажем, .h, либо порождаем искусственный touch-файл в начале скрипта, скажем, .hcc42 (естественно, при ошибке трём). Собственно правило, которое и дёргает этот скрипт и есть единственное правило для главного порождаемого файла. Все остальные генерируемые файлы (.cc в вашем случае) называем «второстепенными». Для них ставим зависимость от главного порождаемого файла. В команде генерации [каждого] второстепенного файла вываливаемся с ошибкой, если он [второстепенный файл] старше главного. Больше ничего для второстепенных файлов не делаем.

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

                          Причём всё это следует стараться описать правилами-шаблонами, иначе слишком многословно получается.

                          Конечно, возникает вопрос — зачем. Для себя я ответил на него следующим образом: так всё без проблем продолжает работать с парадигмой gnu make. Ну а такое расширение, почти вроде-как и не «костыль» вовсе ;)
                            0
                            Собственно все ваши пляски с бубнами описаны в документации.

                            Причём всё это следует стараться описать правилами-шаблонами, иначе слишком многословно получается.
                            А правила-шаблоны, как раз, умеют всё, что нужно «из коробки» — там этих проблем вообще нет.

                            Для себя я ответил на него следующим образом: так всё без проблем продолжает работать с парадигмой gnu make.
                            В GNU make как раз этого не нужно. Это нужно для BSD make или для make на каком-нибудь HP-UX'е… вы реально часто свою программу на HP-UX'е собираете? Вам можно только посочувствовать…
                              0
                              Собственно все ваши пляски с бубнами описаны в документации.
                              О, вот откуда это у меня в голове!
                              вы реально часто свою программу на HP-UX'е собираете?
                              Да нет, это было некоторое количество лет назад, внезапно… только под windows :) Так что некоторые детали подзабыл.
                                0
                                .
                      +1
                      Теперь напрашивается следующий вопрос: автоматизация конфигурирования make-файла.
                        +5

                        Уже придумали CMake.

                          +2

                          А до него Autotools, а после него Meson и Ninja.

                            +1
                            Ninja вообще не для людей. Это скорее обрезанный по самые уши make, у которого по умолчанию сборка идет в несколько потоков.
                              +2
                              Там главная фишка не в том, что сборка в несколько потоков (Make тоже так умеет). Там дело в том, что если вы вы засунете в Make конфиг-файл на гиг, то только на то, чтобы его разобрать — Make потратит не одну минуту. А ninja — за пару секунд разберётся.

                              И не надо думать, что я, это, чуток переборщил с размерами: Android, к примеру, имеет примерно 150 мегабайт .mk-файлов, которые превращаются в .ninja объёмом в полтора гигабайта (этим Kati занимается). И вот это преобразование занимает больше минуты, а ninja потом свой файл читает секунды две (если он в кеше, конечно).
                                +1

                                Ну, никто же не заставлял делать make на 150 мегабайт.
                                У меня есть подозрения, что минута или пару секунд на чтение погоды никакой не делают, глобально.

                                  +2
                                  Обычно делают, если меняется один файл и пересобирается тест, с Makefile 1 минута на make, + 3 с на сборку, с nunja — 1 секунда на ninja + 3 с на сборку.
                                    0

                                    Один make файл или исходник?
                                    Если исходник — очень странно там тогда написаны скрипты.
                                    Я, конечно, на чуть более меньшем масштабе работаю с make файлами(которые генерирует CMake, а не самописный), но и при миллионах строк плюсового кода оверхед от make при изменении в файлах — минимален.


                                    Так что, не всё, что гугл делает является best practice и с андроидом, похоже, как раз это и доказывает.

                                      +1
                                      Один make файл или исходник?
                                      Исходник, конечено.

                                      Если исходник — очень странно там тогда написаны скрипты.
                                      Если у вас в проекте сто тысяч файлов и могут быть созданы миллионы промежуточных объектов (потенциально — это не значит, что каждый билд будет их создавать… какие-то могут и ненужными оказаться), то вам приходится либо из make запускать make (практика, признанная порочной ешё в прошлом веке), либо make придётся вот всё это загрузить и «понять» — и делать это придётся при каждом запуске.

                                      Так что, не всё, что гугл делает является best practice и с андроидом, похоже, как раз это и доказывает.
                                      С андроидом уже всё хорошо: от чистого Make там давно отказались, сейчас .mk файлы преобразуются в ninja формат и если ни один из них не менялся — этап преобразования из .mk файлов в .ninja пропускается.

                                      Так что минута и больше парсинга случается только тогда, когда сами .mk файлы приходится менять.

                                      Я, конечно, на чуть более меньшем масштабе работаю
                                      Размер-таки имеет значение. Можете скачать ранные исходники DOS и убедиться — там даже и без Make обошлись. И без командных файлов. В поздних версиях DOS… там уже командные файлы…

                                      А для проектов масштаба Android или Windows… уже и Make неудобным становится… кстати CMake «отваливается» раньше — потому что он довольно-таки неэффективные Makefile генерит (хотя он может уже генерить напрямую ninja и, наверное, на сегодня это лучший вариант… но когда Android начинали делать ninja ещё не было).
                        +7
                        Открываешь Makefile, а там почти тот же bash-скрипт, но сложнее и непонятнее…
                          0

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


                          Вместо make я рекомендую начать использовать таск-раннер, например rake, а в случае активной работы с параметрами и вовсе на самостоятельные скрипты.

                            +2

                            "Обычный скрипт решает типичные задачи сборки лучше"


                            Типичные задачи как раз проще делать готовыми инструментами, make вполне годен. Да и альтернативных систем сборки чуть больше чем 100500, есть на любой вкус. Хотел бы я глянуть на тот скрипт, который лучше ;)

                              0
                              Всё зависит от масштабов, на самом деле. Когда у вас 10 файлов в проекте, то любая система сборки отлично справляется и Make — не исключение… А вот когда из становится 100, 1000, 10000, 100000… вот тут-то и появляются проблемы.
                                +1

                                На таких масштабах, где make не справляется, я бы посмотрел на тот обычный скрипт ;)

                          +3

                          А поддержки make файлы в сложных проектах не требуют?
                          По собственному опыту — надо переходить сразу на что-то еще более высокоуровневое.
                          Неплохо подходит CMake с небольшой обвязкой на питоне.
                          Он прекрасно генерирует и make, и те же студийные проекты(если такая необходимость есть).


                          А копаться и саппортить сборку на самописных make-файлах в сложных проектах — лучше оставить храбрым.

                            +4

                            Я два года работал над большой кодовой базой полностью построенной на голых makefile. Никому не посоветую :)

                              +5
                              Make-файлы make-файлам рознь. Какими напишешь — такими и будут. Долгое время — больше пяти лет — жили с системой сборки развесистого c++ проекта, построенной на базе gnu make. Всё было понятно, лаконично, быстро.

                              Потом перешёл на другую работу, поэтому, как эта система сборки развивалась дальше — не знаю.
                                0

                                Ну, оно работало. В целом были шаблонные человекочитаемые файлы, с десяток изящных костылей, упрощающих внедрение нового кода. В целом, все было неплохо, но make в мире сборочных систем — это почти ассемблер в мире программирования: оч круто если знаешь, но лучше выучи CMake)

                              0
                              linux kernel и buildroot/openwrt сделаны же на make, недостаточно сложные проекты?
                                +2

                                Есть и на autoconf проекты, и на шелл скриптах, никто не спорит.
                                Но, по опыту, квалификация для написания качественного make нужна гораздо выше, чем того же CMake.

                              –1
                              Мы первое время для с-проектов сидели на .pro, затем достаточно ощутимый период времени на самописных bash скриптах с основной фичей — автогенерацией library header по исходному коду, которое всегда пересобирало весь код, сейчас перелезли на самописное решение на c, которое автоматически детектит какие файлы поменялись, раскручивает весь стек зависимостей и пересобирает только те библиотеки и проекты, которые реально надо пересобирать. Параллельно говорит bamboo, какие артефакты надо будет забрать
                                +1

                                То есть вы сделали ещё одну программу, которая умеет make и make install :)

                                +1
                                Я бы предпочел чтобы были не make и не bash-скрипты, а декларативный файл проекта, формат которого является частью стандарта языка программирования. Причем формат должен быть по возможности простым, прозрачным и человекочитаемым.
                                Почему? Потому что проект нужен не только для сборки, но и например для загрузки в среду разработки. Хорошо когда все эти функции совмещены в одном файле и не нужны никакие промежуточные генерации. Не говоря уже о такой странной вещи, как «конфигурирование» (cmake и прочие), которое само по себе является грандиозным костылем у языка с полным отсутствием модульности. Модульность — это ведь не только замена древних инклудов, но и интеграция модулей-библиотек в среду/окружение разработки.

                                make слишком низкоуровневый, слишком явно заточенный под сборку, и в нем слишком много от скриптов. А в идеале это должен быть просто список файлов исходников (возможно с какими-то атрибутами) и информация о проекте в целом. Формат vcxproj например немного ближе к идеалу, но он очень перегружен и в нем слишком много костылей и Microsoft-специфичных вещей.
                                  +1

                                  gradle?

                                    +2

                                    Как насчёт задач где часть файлов генерируется на лету? Я ковырялся в системах сборки Linux kernel, xen, op-tee. Везде часть кода генерируется на основе каких-то данных. Везде нужно два-три прохода линковщика чтобы сгенерировать список symbols, который будет доступен из самого кода. Иногда нужно вызывать bison или flex, иногда — самописный скрипт, который сгенерирует код.

                                      0
                                      Я думал об этом. Все достаточно просто решается.
                                      Во-первых, в файле проекта просто может быть указано что файл генерируемый — соответствующим атрибутом и даже можно указать номер прохода и прочую метаинформацию.
                                      Во-вторых, в файле проекта не обязательно указывать конкретные файлы. Можно придумать указание папки с файлами — и тогда все файлы в указанной папке будут включаться в проект.
                                      Вариантов много. Главное — чтобы был древовидный структурированный декларативный формат, а не скрипт. Скрипты — это частный случай кодогенерации и в особых случаях должны указываться отдельно. Наверное и в IDE такие файлы должны отображаться особым образом.
                                        0

                                        Так Makefile — это и не скрипт. Он декларативным образом описывает как собрать заданную цель.


                                        Что до генерируемых файлов — то они обычно генерируются на основе других файлов. То есть их нужно перегенерировать при изменении исходного файла — появляется потребность в описании зависимостей. Т.е. опять приходим к make — декларативно описываем зависимости, получаем результат.

                                          +1
                                          Так Makefile — это и не скрипт. Он декларативным образом описывает как собрать заданную цель.
                                          К сожалению не декларативным. Makefile — вполне себе тьюринг-полный язык программирования и, соответственно, единственный способ понять как что-то будет собираться — это-таки попытаться это «что-то» собрать. Вернее, строго говоря, вторая фаза как раз декларативна (что и позволило заменить её на Ninja в Kati), но первая — нет.

                                          Чтобы достичь счастья и получить полностью декларативное описание нужно её тоже на что-то заменить… скажем на Blueprint… но тогда у вас от Make не останется ничего!
                                            +1
                                            Ну полнота по Тьюрингу вылазит в любой более-менее мощной системе. Если уж CSS является Тьюринг-полным…

                                            А вместе с полнотой по Тьюрингу мы получаем и проблему остановки. Так что да, единственный способ узнать что же делает Makefile — исполнить его.

                                            GNU Make стал таким огромным и сложным потому что на него возложили не свойственные ему задачи по конфигурации собираемого приложения. Сборка какой-нибудь несложной прошивки легко описывается самым примитивным Makefile. Его легко читать и легко писать. Но потом нам захочется иметь несколько сборок — с дебагом и без, под этот камень и под тот, с таким набором фич и с другим. Вместо того, чтобы использовать какой-нибудь внешний инструмент, мы начинаем лепить это в Makefile… Ну и дальше вы знаете.

                                            В этом плане ninja конечно лучше. Она достаточно антигуманна, чтобы писать .ninja файлы руками. Это приводит к совершенно правильному желанию генерировать эти файлы на основе какой-то более высокоуровневой спецификации.
                                      +3

                                      А если в проекте используется более одного языка программирования? Под Ваши требования подходит CMake, но он тоже тот еще монстр.

                                        0
                                        Ant? Но он заточен для java хотя и для других языков наверно может
                                        +7
                                        Честно говоря, ожидал гораздо более длинную статью, которая заканчивается такими словами:

                                        Посмотрите, какой огромный, но красивый вышел скрипт! И да, поздравляю, мы только что написали make!
                                          –1

                                          А зачем тратить как свое, так и чужое время? Главное поднять вопрос и попытаться кратко и наглядно ответить.

                                            +1

                                            Если вы имели в виду вот этот комментарий
                                            https://habr.com/ru/post/477656/comments/?mobile=no#comment_20975760
                                            то трудно не согласиться.

                                              +1

                                              Всё зависит от цели статьи, на самом деле. Можно писать с целью кратко и наглядно ответить, а можно — с целью детально разобраться и дать разобраться другим. Скажу честно, будь у меня в запасе несколько свободных дней, я бы, пожалуй, сказал "hold my beer" и накатал ответ с этим самым "огромным, но красивым скриптом" — именно по принципу "сделай сам, чтобы понять".

                                            +10
                                            TL;DR; make нужен затем, что в нем реализован алгоритм топологической сортировки, что позволяет ему принимать на вход зависимости между файлами исходников, и выдавать на выход порядок, в котором их надо компилировать.
                                              0

                                              Рискую словить минусов, но все-таки в 2019 году все эти make, скрипты и пр выглядит так же архаично, как и редактор типа vim. Считаю, что вести разработку, сборку и отладку проектов гораздо удобнее в нормальных IDE, включающих в себя отладчик, редактор кода с инструментами рефакторинга, систему сборки, профилировщик и кучу других полезных инструментов, а главное всё это с нормальным графическим пользовательским интерфейсом, т. е. когда сделано для людей, а не для роботов. Примерами могут служить PyCharm, Android Studio, Visual Studio, IDEA, Eclipse, Embarcadero RAD Studio.

                                                +5

                                                Минусить — дурное дело, а вот контрагументировать — пожалуйста! Возражу насчёт вести сборку. Дело в том, что пока проект живёт в пределах ноутбука, можно собирать и с помощью IDE. А когда над ним трудятся несколько человек (а ещё и если команда распределённая), то без CI-сервера уже не обойтись. И никакого IDE там нет и быть не может. Дальше. Если сборка идёт скриптом, то обеспечивается воспроизводимость результата сборки. Т.е., если сборщик (make) собрал и получил нужный результат, то как локальную IDE ни настраивай, результат сборки не испортишь и у каждого (и на сервере) он будет одинаковым.

                                                  +1

                                                  Ну в случае VS за сборку отвечает отдельный инструмент — MSBuild, соответственно на сборочные агенты ставится Build Tools, а не полноценная IDE. Хотя никто не запрещает ставить и студию. На счёт других сред разработки не в курсе.

                                                    0

                                                    Наверное всё так и есть, не спорю. Но автор коммента просто сделал акцент на GUI и удобстве для пользователя, хотя упомянутая задача в промышленных масштабах решается исключительно с помощью автоматизированных средств.

                                                  +3

                                                  Как только ваш проект станет немного сложнее чем helloworld, ему понадобится система сборки.

                                                    +1
                                                    архаично, как и редактор типа vim

                                                    Ну начина-а-ается. А то, что чуть ли не все перечисленные имеют vim-режим вас не смущает? Или последняя волна LanguageServer'ов, которые как раз и рассчитаны, во многом, на работу с вимом и другими продвинутыми редакторами?

                                                    +1
                                                    Почему нельзя обойтись bash скриптами?

                                                    bash-скриптами и правда лучше не обходиться, bash это интерактивный шелл для линукса (в других *nix — обычно не баш), а скрипты на нём писать это либо только для своего личного использования, либо плохой тон. Особенно если скрипт с расширением .sh как у вас, а внутри #!/bin/bash


                                                    И к тому же, мало кто пишет Makefile'ы вручную.

                                                    А вот это очень плохо и портит первоначальную идею make — утилиты которая по человекопонятным описаниям сделает всё что нужно, то что скриптом было бы сложно и нечитабельно. Если уж генерировать Makefile то можно сразу шелл-скрипт генерировать — и то и то уже нечитабельно. Все эти autotools/cmake и им подобное — гадость.


                                                    x67


                                                    А теперь хотя бы есть понимание, зачем я делаю мейк каждый раз, когда ставлю очередной питон или постгрес, например.
                                                    Но понимание, на самом деле, весьма поверхностное. Неужели все так просто — обновить объектные файлы и слинковать их воедино?

                                                    При установке — скорее всего незачем, там могла быть и обычная простыня из вызовов gcc одного за другим — ведь исходники никто после компиляции менять и перекомпилировать не собирается а скорее всего вообще удалят. А вот для модели "обновить исходники через VCS — перекомпилировать то что изменилось — поставить", так сказать инкрементальные апдейты версий (которую почти никто не использует) было бы полезно. Ну и разработчикам конечно крайне полезно после мелкой правки не ждать полчаса пересборку всего.

                                                      0

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

                                                        +1

                                                        Использовать make только для сборки бинарников — это, ну… типичный подход сишного программиста, который ничего не видел за пределами gcc [ирония]


                                                        На одном из проектов у нас makefile используется как для примитивного make build, make test, make install, так и для нестандартных: make sync_db, make prepare_environment


                                                        Ах да, это веб-проект.


                                                        А еще я встречал развесистый мейк-файл для управления докером:
                                                        make build — стартует сборку докер-контейнера, внутрь которого заворачивается сайт, который где-то тут же.
                                                        make start — стартует докер-контейнер, собранный ранее
                                                        make stop, make clean, make destroy и так далее.


                                                        Очень удобно, как по мне, главное помнить несколько неочевидных вещей (в частности — каждая строчка в сценарии — отдельная команда в отдельном процессе шелла)

                                                          0

                                                          Теперь понял зачем это всё. Очень интересно. Спасибо!

                                                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                          Самое читаемое