Russian Qt Forum

Программирование => Общий => Тема начата: Авварон от Сентябрь 12, 2020, 12:17



Название: Провайдеры
Отправлено: Авварон от Сентябрь 12, 2020, 12:17
Не всё же Igors'у вопросы задавать. Если из темы ничего не выйдет, не беда, форум уточкой поработает.

Итак, сперва контекст. В Qbs у нас есть модули, от них зависит ваша цель сборки (продукт). Модуль может быть чем угодно - языком программирования, фреймворком, библиотекой. Примеры - cpp (https://doc.qt.io/qbs/qml-qbsmodules-cpp.html), Qt.core (https://doc.qt.io/qbs/qml-qbsmodules-qt-core.html). Модули инжектят в продукт разные свойства, например флаги компиляции или библиотеки к которым надо линковаться.

Также есть высокоуровневая абстракция - модуль провайдеры, которые позволяют генерить модули. Например, провайдер "Qt" спрашивает у qmake какие модули установлены и генерит только их.

Когда в коде пишется Depends { name: "foo" } Qbs сперва ищет "реальный" модуль "foo", если такового нет, то она ищет провайдер foo и пробует создать модуль им, если и такового нет, то используется "fallback" провайдер, который спрашивает у утилиты pkg-config пакет "foo".

У провайдеров можно задавать различные свойства, например чтобы подсказать где искать qmake, надо сделать так:
Код:
qbs module-providers.Qt.qmakeSearchPaths:/opt/bin
.
В файловой системе файлы провайдеров лежат примерно так:
Код:
<qbs search path>/module-providers/Qt/provider.qbs
<qbs search path>/module-providers/__fallback/provider.qbs

Теперь ближе к вопросу.
Я хочу разрешить описывать несколько провайдеров для одного модуля для того чтобы поддерживать разные "источники" - помимо pkgconfig хочу добавить Conan, vcpkg и manual (ручной, где пути к любам надо вбивать ручками) провайдеры. Для этого можно завести новый тип MetaProvider у которого будет (пока) одно свойство - wantedProvider (или список, wantedProviders) - тот провайдер, который мы хотим использовать.
В файловой системе это будет выглядеть теперь так (ну разве что meta-provider.qbs можно звать просто provider.qbs, зачем плодить сущности?):
Код:
<qbs search path>/module-providers/zlib/meta-provider.qbs // MetaProvider { wantedProvider: "conan" }
<qbs search path>/module-providers/zlib/pkgconfig.qbs // Provider { ... }
<qbs search path>/module-providers/zlib/conan.qbs // Provider { ... }
<qbs search path>/module-providers/zlib/manual.qbs // Provider { ... }

Аналогично, fallback провайдер выглядит так
Код:
<qbs search path>/module-providers/__fallback/meta-provider.qbs // MetaProvider { wantedProvider: "conan" }
<qbs search path>/module-providers/__fallback/pkgconfig.qbs // Provider { ... }
<qbs search path>/module-providers/__fallback/conan.qbs // Provider { ... }
<qbs search path>/module-providers/__fallback/manual.qbs // Provider { ... }
Это позволит избежать копи-пасты как в примере выше - pkgconfig может жить только в папочке __fallback потому что единственное его свойство - имя пакета - можно вычислить по дефолту из имени желаемого модуля (спойлер - они совпадают). Ну типа в редких случаях (когда они не совпадают, скажем "zlib" vs "libz") придется описать явно, ну не суть, это делали.

Собственно вопрос в том как синтаксически задавать свойства всего этого зоопарка. Раньше (напомню) мы писали module-providers.<provider-name>.property:value (e.g. module-providers.Qt.qmakeSearchPaths:/opt/bin).
Теперь у нас есть разные уровни провайдеров, есть "главный" и есть "вложенные", "дочерние". Ну, допустим:
Код:
module-providers.zlib.wantedProvider:pkgconfig // задаем свойство мета провайдера
module-providers.zlib.pkgconfig.static:true // задаем свойство конкретного провайдера
Окей, с конкретным модулем разобрались, но ведь неудобно для каждой библиотеки перечислять всё это, нужен "глобальный" синтаксис который ставит значения по умолчанию. Ну допустим:

Код:
module-providers.wantedProvider:pkgconfig // глобально задаем свойство мета провайдера
module-providers.pkgconfig.static:true // глобально задаем свойство конкретного провайдера

Опта, коллизия!
Код:
module-providers.zlib.wantedProvider:pkgconfig
module-providers.pkgconfig.static:true
Эти две записи имеют синтаксис module-providers.<foo>.<bar>:baz, а значит потенциально влекут кучу проблем (ну например теперь внутренние провайдеры не могут называться как модули)

Собственно, как решить? Проблема тут чисто в именовании, в остальном схема выглядит весьма продуманно.
Например, такое именование (в предыдущей итерации где была другая, но похожая, схема) решает проблему:
Код:
packages.zlib.wantedSource:pkgconfig // локальный синтаксис
packages.zlib.sources.pkgconfig.static:true // локальный синтаксис
packages.wantedSource:pkgconfig // глобальный синтаксис
packages.sources.pkgconfig.static:true // глобальный синтаксис
Внимательный читатель возмутится - что за пакеты, что за источники? Их больше нет, теперь это MetaProvider и просто Provider. Если мы обобщим схему и вместо "sources" будем использовать "providers" то получается известная субстанция:
Код:
module-providers.wantedProviders:pkgconfig // глобальный синтаксис
module-providers.providers.pkgconfig.static:true // глобальный синтаксис
Провайдеры? - Провайдеры - Провайдеры! (напевать на мотив developers! developers! developers! (https://www.youtube.com/watch?v=KMU0tzLwhbE))
В общем, я сломал всю голову, никак цветок каменный не выходит. Вроде, схема красивая, но есть нюансы.


Название: Re: Провайдеры
Отправлено: kuzulis от Сентябрь 12, 2020, 13:38
Я все-же не понял, а в чем разница между:

Цитировать
module-providers.zlib.wantedProvider:pkgconfig

и

Цитировать
module-providers.wantedProvider:pkgconfig

? Первый - ищет zlib.. ну а второй для чего?

И вообще, непонятны эти нововведения (без 100 грамм ну никак)..

Можешь еще на примерах "реальных" проекта (например в сем же zlib) разобрать на пальцах?


Название: Re: Провайдеры
Отправлено: Авварон от Сентябрь 12, 2020, 14:24
Я все-же не понял, а в чем разница между

Допустим, у нас проект зависит от нескольких библиотек (zlib, lzma, boost.spirit). И мы хотим использовать Conan при сборке. Тогда мы можем написать
Код:
qbs module-providers.zlib.wantedProvider:conan module-providers.lzma.wantedProvider:conan module-providers.boost.spirit.wantedProvider:conan 

Это слишком многословно и такая гранулярность не нужна (точнее нужна, но в редких кейзах когда 1 модуль ищется через pkgconfig, а второй через Conan, ну потому что аффтары либы не написали .pc файл и pkg-config просто нету). Поэтому хочется уметь задавать значение "глобально", для всех искомых модулей сразу, например, так:
Код:
module-providers.wantedProvider:conan

Ну или (тот редкий случай) "по умолчанию" conan но для lzma - pkgconfig:
Код:
module-providers.wantedProvider:conan module-providers.lzma.wantedProvider:pkgconfig

Цитировать
И вообще, непонятны эти нововведения (без 100 грамм ну никак)..

Хе-хе, мне тоже=) Это далеко не первая версия, возможно третья. Идея в том чтобы позволять выбирать разные "источники" для библиотек. Сейчас поддерживается только один - pkg-config, но его нету на венде, вендузятники страдают.


Название: Re: Провайдеры
Отправлено: kuzulis от Сентябрь 12, 2020, 15:30
Цитировать
Это слишком многословно и такая гранулярность не нужна

А почему бы не сократить доступ еще, например так:

Цитировать
qbs module-providers:conan module-providers.lzma:conan module-providers.boost.spirit:conan
qbs module-providers:conan
qbs module-providers:conan module-providers.lzma:pkgconfig

т.е. убрать 'wanted' ?

Так, а теперь приведи пример с самой проблемой, я не понял, в чем она?  :)

В чем тут коллизия?

Цитировать
module-providers.zlib:pkgconfig                // Задаем свойство провайдера zlib искать zlib через pkgconfig.
module-providers.zlib:pkgconfig.static:true // Задаем свойство провайдера zlib искать только статическую zlib через pkgconfig.

module-providers:pkgconfig                     // Глобально задаем свойство для всех провайдеров использовать pkgconfig для поиска (тут какие то его дефолтные настройки, например динамик по умолчанию).
module-providers:pkgconfig.static:true      // Глобально задаем свойство для всех провайдеров использовать pkgconfig для поиска статических библиотек.

Тут по умолчанию глобально используется pkgconfig и ищет, например, динамик либы по умолчанию. Но мы переопределяем ему св-во static=true, и теперь он ищет только статик либы глобально для всех через pkgconfig.

Но для zlib смысла ставить static нету, т.к. оно итак ищет статику. Мы могли бы для zlib установить:

Цитировать
module-providers.zlib:pkgconfig.static:false

и тогда бы оно искало только динамические либы... И могли бы опустить вот это:

Цитировать
module-providers.zlib:pkgconfig                // Задаем свойство провайдера zlib искать zlib через pkgconfig.

вообще, т.к. глобально оно и так использует pkgconfig.

Или я не понял?


Название: Re: Провайдеры
Отправлено: kuzulis от Сентябрь 12, 2020, 15:53
Например, можем "переиграть" (вот это ниже должно вызывать ошибку):

Цитировать
module-providers.zlib:conan.static:true // Задаем свойство провайдера zlib искать только статическую zlib через conan (но мы еще не указали конана для zlib, а уже лезем менять его св-ва).

module-providers:pkgconfig                 // Глобально задаем свойство для всех провайдеров использовать pkgconfig для поиска

т.к. тут коллизия, для zlib мы не установили дефолтного провайдера 'conan' и попытались присвоить свойство к еще несуществующему конану.

Вот так бы не было ошибки:

Цитировать
module-providers.zlib:conan                // Задаем свойство провайдера zlib искать zlib через conan (типа создаем локального конана для zlib).
module-providers.zlib:conan.static:true // Задаем свойство провайдера zlib искать только статическую zlib через conan.

module-providers:pkgconfig                 // Глобально задаем свойство для всех провайдеров использовать pkgconfig для поиска

Тут для всех остальных модулей будет юзаться pkgconfig (ищущий что-то с дефолтными настройками, например, динамические либы), а вот для zlib будет использоваться только conan и линковать статикой.


Название: Re: Провайдеры
Отправлено: Авварон от Сентябрь 12, 2020, 16:30

А почему бы не сократить доступ еще, например так:

Ну потому что там могут появиться еще проперти. Я уже даже знаю одну - надо ли делать fallback, то есть искать в папке __fallback. Типа MetaProvider (MultiplexProvider, Package, you name it) выглядит так

Код:
Item { // обе проперти "встроенные", такого базового типа нет. Но можно наследовать Provider (не вижу зачем)
    property string wantedProvider: "pkgconfig"
    property bool fallback: true
}

Код:
qbs module-providers:conan module-providers.lzma:conan module-providers.boost.spirit:conan 
qbs module-providers:conan
qbs module-providers:conan module-providers.lzma:pkgconfig
Вообще, парсер так устроен что он складывает prefix.a.b.c.prop:value в мапу вот так: map["a.b.с"]["prop"] == value. Префикс не может быть пустой, иначе все остальные ключи (вида modules.foo.bar.baz) полетят в эту мапу. Второй ключ (a.b.c) - может быть пустым, то есть "module-providers.wantedProviders" попадёт в map[""]["wantedProviders"]. Prefix сейчас может иметь значения "product", "modules" или "module-providers", соответсвенно оно кладется в 3 разные мапы. Можно добавить новый префикс, если что - см. пример с "packages." выше.

Так, а теперь приведи пример с самой проблемой, я не понял, в чем она?  :)
В чем тут коллизия?

Код:
module-providers.zlib:pkgconfig                // Задаем свойство провайдера zlib искать zlib через pkgconfig. 
module-providers.zlib:pkgconfig.static:true // Задаем свойство провайдера zlib искать только статическую zlib через pkgconfig.

module-providers:pkgconfig                     // Глобально задаем свойство для всех провайдеров использовать pkgconfig для поиска (тут какие то его дефолтные настройки, например динамик по умолчанию).
module-providers:pkgconfig.static:true      // Глобально задаем свойство для всех провайдеров использовать pkgconfig для поиска статических библиотек.

Ну так работать не будет, см выше. Я думал о добавлении сложного синтаксиса типа module-providers.zlib[pkgconfig].static:true, но это надо чтобы парсер QML поддерживал это. Плюс кмк это плохая идея - gyp пошел по этому пути и там было всякое разное разыменование переменных типа <(foo), >(foo), <@(foo), >@(foo) и это имело разное значение. Так себе подход, без пол-литры не запомнишь.


Название: Re: Провайдеры
Отправлено: Авварон от Сентябрь 12, 2020, 16:37
Например, можем "переиграть" (вот это ниже должно вызывать ошибку):



А вот тут будут возмущаться некие немцы. В идеале, wantedProvider превратиться в список wantedProviders и тебе надо будет ставить проперти для нескольких провайдеров сразу. Ну и это надо уже сейчас, например ты можешь захардкодить эти проперти в проекте/продукте сразу:
Код:
Product {
    Depends { name: "zlib" }
    module-providers.zlib.pkgconfig.static:true
    module-providers.zlib.conan.static:true
}
а выбор провайдера делать с консоли:
Код:
qbs module-providers.wantedProvider:pkgconfig

Самая засада что сейчас ни у pkgconfig ни у manual провайдеров своих пропертей сейчас нет (пример со static синтетический, скорее всего оно будет не так), но синтаксис придумать надо - они могут внезапно появиться, скажем у Conan.


Название: Re: Провайдеры
Отправлено: Авварон от Сентябрь 12, 2020, 16:48
Ну что пока я придумал (не буду Игорсом который размусолит на неделю, напишу сразу) - не выделять "главный" (мета) провайдер синтаксически и указывать его явно:

Код:
// local syntax
module-providers.zlib.main.wantedProviders:pkgconfig
module-providers.zlib.pkgconfig.static:true
module-providers.zlib.conan.static:true
// global syntax
module-providers.main.wantedProviders:pkgconfig
module-providers.pkgconfig.static:true
module-providers.conan.static:true
Тут есть небольшая неконстистентность - айтем зовется MetaProvider, лежит в папке под именем provider.qbs, а обращение к нему через "main". Хрен запомнишь. Обозвать его Package? Вроде неплохо, запомнить что Package - это мета-информация для выбора провайдеров (и всякая другая) вроде не сложно. Тогда вроде консистентно (ну кроме факта что Package лежит рядом с провайдерами но с этим можно жить, наверное).


Название: Re: Провайдеры
Отправлено: kuzulis от Сентябрь 12, 2020, 16:57
А если просто "опускать" main, при этом считаем, что доступ к "корневому" провайдеру тогда будет?

Цитировать
// local syntax
module-providers.zlib.wantedProviders:pkgconfig
module-providers.zlib.pkgconfig.static:true
module-providers.zlib.conan.static:true
// global syntax
module-providers.wantedProviders:pkgconfig
module-providers.pkgconfig.static:true
module-providers.conan.static:true

?

Да и wantedProviders слово не нравится. Может заменить на что-то например на factories/sources?



Название: Re: Провайдеры
Отправлено: kuzulis от Сентябрь 12, 2020, 17:08
Цитировать
// local syntax
package.zlib.providers:pkgconfig (но нельзя установить 'global' и переопределить его св-ва из локального, считаем ошибкой)
provider.zlib.pkgconfig.static:true
provider.zlib.conan.static:true
// global syntax
package.{global|default}.providers:pkgconfig
provider.{global|default}.pkgconfig.static:true
provider.{global|default}.conan.static:true


Название: Re: Провайдеры
Отправлено: Авварон от Сентябрь 15, 2020, 14:16
А если просто "опускать" main, при этом считаем, что доступ к "корневому" провайдеру тогда будет?

Код:
// local syntax
module-providers.zlib.wantedProviders:pkgconfig
module-providers.zlib.pkgconfig.static:true
module-providers.zlib.conan.static:true
// global syntax
module-providers.wantedProviders:pkgconfig
module-providers.pkgconfig.static:true
module-providers.conan.static:true

Ну так тут коллизия - это ты как различаешь случаи
module-providers.zlib.wantedProviders:pkgconfig
module-providers.pkgconfig.static:true
а синтаксически оба случая module-providers.a.prop:value. Как понять, а - имелся ввиду модуль а или провайдер а?

Да и wantedProviders слово не нравится. Может заменить на что-то например на factories/sources?

Да можно просто providers если не будет коллизий с другими свойствами.


Название: Re: Провайдеры
Отправлено: Авварон от Сентябрь 15, 2020, 14:22
Мне пришла идея в голову, почему это не засунуть в продукт/depends?
Вот так писать неудобно, потому что приходится повторять себя (DRY!)
Код:
Product {
    Depends { name: "Qt.core"; providers: ["pkgconfig"] }
}

Поэтому можно сделать, чтобы providers брало значение из аналгичного свойства Продукта, а то - из аналогичного свойтва Проекта
Код:
Project {
    providers: ["pkgconfig"] // global
    Product {
        name: "a"
        Depends { name: "Qt.core"; }
    }
    Product {
        providers: ["conan"]  // product override
        name: "b"
        Depends { name: "Qt.core"; }
    }
    Product {
        name: "c"
        Depends { name: "Qt.core"; providers: ["manual"] } // depends override
    }
}

Тогда можно просто писать
Код:
qbs projects.rootProject.providers:pkgconfig
"Глобально" устанавливать свойства для определенного модуля, можно так (страшненько, да)
Код:
Project {
    providersPerModule: {
         "Qt.core": "pkgconfig"
         "Qt.gui": "manual"
    }
}


Название: Re: Провайдеры
Отправлено: Авварон от Май 17, 2021, 17:54
Апну темку :)

Сейчас я пытаюсь решить проблему совместимости со старым кодом.

Как провайдеры работают сейчас - если Qbs не может найти модуль, скажем, Qt.core, она идет и ищет провайдер "Qt.core", если такого нет, то ищет "Qt", который и создает утешные модули. Таким образом, если проект не зависит от Qt.core, то провайдер не вызывается.

Теперь, мы делаем новое свойство qbsModuleProviders: ["Qt", "conan", "pkgconfig"], которое задает порядок, в котором выполняются провайдеры. Сначала работает "Qt", который создает модули для Qt, потом "conan", потом "pkgconfig". Бонусом имеем то, что разные провайдеры могут создавать одноименные модули, но приоритет имеют те, которые раньше в списке - например, если и "conan", и "pkgconfig" предоставляют модуль "zlib", то будет использован от "conan" - очень удобно делать механизм фоллбека между провайдерами.

Также теперь необязательно выполнять провайдеры, когда модуль не найден, можно выполнить весь список ДО поиска модулей - это упростит код в Qbs.
Однако появляется проблема - как быть с дефолтами? Если сделать значение qbsModuleProviders по умолчанию пустым, то сломается автопоиск Qt и pkgconfig. Правда во втором всё равно ничего нет, так что и не проблема=) но вот с Qt это проблема.
Какие варианты есть?
1а. Если qbsModuleProviders пустой, то можно попробовать выполнить ВСЕ провайдеры, которые есть. Это плохо, так как это ужасно медленно (сетап Qt где-то полсекунды, pkg-config 4 секунды чтобы сгенерить все возможные модули, но тут можно оптимизировать).
1б. То же, что 1а, но провайдер говорит, надо ли его звать по дефолту (enabledByDefault: true). Это позволит выключить особо тяжелые провайдеры, но остается проблема - если проект не зависит от Qt, а у Qt провайдера enabledByDefault: true, то юзеру будет непонятно, почему мы пытаемся сетапить Qt
1в. То же, что 1б, но enabledByDefault вычисляется, скажем, по наличию qmake в PATH. Проблему особо не решает, но если проверять, задано ли свойство провайдера qmakeSearchPaths (оно задаётся при запуске qbs setup-qt), то это решит совместимость при использовании Qt-профиля - если юзер явно указал Qt профиль, то наверное он хочет Qt. Но это не решит проблему при автодетекте qmake в PATH - независимо от наличия qmake в PATH мы не знаем заранее, нужна ли юзеру Qt.
2. Ничего не делать и заставить юзера указывать qbsModuleProviders руками в проекте:
Код:
QtApplication {
    Properties {
         condition: qbs.version >= 1.21
         qbsModuleProviders: ["Qt", "conan", "pkgconfig"]
    }
}
Ломается поведение по умолчанию, но можно пока оставить старые реализации провайдеров Qt и pkg-config и при попытке их использовать, выдавать warning - мол, deprecated, задавай qbsModuleProviders явно.
В целом, явное задавание имеет смысл, потому что провайдеры могут создавать не только плюсовые/кутешные модули и, скажем, делать там ["pkgconfig"] по дефолту для Java или там Typescript проекта не имеет смысл.

Какие мысли?