- PVSM.RU - https://www.pvsm.ru -
Qbs [1] (Qt Build System) — система сборки, позволяющая описывать процесс сборки проектов на простом языке QML (javascript-подобный декларативный язык), ускоряющий процесс сборки продуктов за счет построения подробного графа зависимостей. Хоть эта система и создана разработчиками Qt, но она жестко не привязана к Qt и позволяет собирать любые продукты, для любых языков программирования и даже использоваться не для программирования, а например для администрирования. Как заявлено в официальной документации:
A product is the target of a build process, typically an application, library or maybe a tar ball
Сегодня и рассмотрим процесс создания своих продуктов. Поехали…
Знакомство с это системой сборки хорошо описал mapron [2] в данной статье [3], мы же не будем на этом останавливаться и приступим сразу к делу.
К сожалению в документации и сети слишком мало информации о деталях работы Qbs и значении свойств элементов. По этому я попробую восполнить некоторые пробелы в этом, опираясь на свой опыт работы с данной системой сборки. И буду благодарен читателям за дополнение или указания неточностей в данном материале.
Итак Qbs выполняет преобразования одних данных в другие с помощью элемента Rule [4]. Данный элемент создает правило преобразования, которое преобразует входные данные inputs в выходные outputs при помощи набора команд, созданных в скрипте prepare. Каждая команда может как сама выполнять преобразования, так и делегировать эту работу внешней программе.
В качестве входных артефактов для правила преобразования выступают файлы собираемого продукта с тегом указанным в свойстве inputs, а также артефакты полученные из зависимостей собираемого продукта с тегом указанным в свойстве inputsFromDependencies (неявным является тег "installable", который присваивается всем файлам которые необходимо проинсталлировать, т.е. qbs.install: true).
Например:
---product1.qbs---
Application {
name: "simpleApplication";
targetName: "appSimple"
files: ["main.cpp", "app.h", "app.cpp"]
Depends {name: "cpp"}
}
---product2.qbs---
Product {
type: "exampleArtefact"
name: "example"
targetName: "myArtefact"
Depends {name: "simpleApplication" }
Depends {name: "exampleModule" }
Group {
files: ["description.txt", "readme.txt"]
fileTags: ["txtFiles"]
}
Group {
files: ["imgA.png", "imgB.png", "imgC.png"]
fileTags: ["pngFiles"]
}
Group {
files: ["fontA.ttf", "fontB.ttf"]
fileTags: ["fontFiles"]
}
}
---exampleModule.qbs---
Module {
Rule {
inputs: ["pngFiles", "fontFiles"]
inputsFromDependencies: ["application"]
Artifact {
filePath: product.targetName + ".out"
fileTags: ["exampleArtefact"]
}
...
}
...
}
В данном примере у нас есть 2 продукта и 1 модуль:
При сборке продукта будет определен список модулей от которых зависит продукт и сами модули. В них будет осуществлен поиск правил преобразования файлов помеченных входными тегами в выходные артефакты, которые соответствуют типу собираемого продукта. Сначала собирается продукт simpleApplication, т.к. он имеет зависимость только от модуля cpp. В нем ищутся правила преобразования файлов продукта в тип application. В модуле cpp есть элементы FileTagger [5] которые по шаблону задают для файлов продукта теги. Преобразование входных файлов в выходной(ые) может быть выполнено как сразу, так и по цепочке преобразований файлов одного типа в другой, а затем в итоговый. На выходе обработки продукта simpleApplication, мы получим приложение appSimple имеющее тип (тег) application.
Затем начнется сборка продукта example. Для его файлов будет искаться правило, на выходе дающее артефакты типа exampleArtefact. Это правило требует для входа файлы типа (с тегом) pngFiles, fontFiles и application. При этом файлы типа application ищутся только в продуктах от которых зависит собираемый продукт. Так как продукт example уже содержит такие файлы, то на вход правила поступают файлы: imgA.png, imgB.png, imgC.png, fontA.ttf, fontB.ttf и appSimple.exe. А на выходе получим файл myArtefact.out типа exampleArtefact, который и будет являться нашим конечным продуктом.
В качестве выходных артефактов для правила могут быть как один, так и несколько артефактов. Для описания выходных артефактов используется элемент Artifact [6]:
Artifact {
filePath: input.fileName + ".out"
fileTags: ["txt_output"]
}
Этот элемент описывает, какой артефакт получается на выходе правила. Через свойство filePath — указывается имя выходного файла. Если указывать относительный путь, то Qbs будет создавать этот артефакт относительно каталога сборки текущего собираемого продукта. Через свойство fileTags указывается список тегов, которые будет иметь артефакт после его создания. Это необходимо, для того чтобы другие правила сборки могли использовать выходные артефакты данного правила, как свои входные артефакты. Также если продукт будет иметь этот же тег в качестве своего типа, то эти артефакты будут являться результатом сборки этого продукта.
У каждого правила должен быть хоть один выходной тег, иначе правило работать не будет. Если артефактов несколько, то можно описать несколько элементов Artifact либо можно воспользоваться свойствами outputArtifacts и outputFileTags для элемента Rule.
Свойство outputArtifacts описывает список JavaScript объектов имеющих свойства, как у элемента Artifact. Используется это свойство для случаев, когда набор выходов не фиксирован, а зависит от содержания входных данных. Например:
outputArtifacts: [{
var artifactNames = inputs["pngFiles"].map(function(file){
return "pictures/"+file.fileName;
});
artifactNames = artifactNames.concat(inputs["fontFiles"].map(function(file){
return "fonts/"+file.fileName;
}));
artifactNames = artifactNames.concat(inputs["application"].map(function(file){
return "app/"+file.fileName;
}));
var artifacts = artifactNames.map(function(art){
var a = {
filePath: art,
fileTags: ["exampleArtefact"]
}
return a;
});
return artifacts;
}]
В данном примере показано, что для входных файлов с тегом pngFiles, правило подготовит выходной артефакт с таким же названием и поместит его в папку pictures. Для тегов fontFiles и application, также поместив их соответственно в папки fonts и app. При этом т.к. пути будут относительные, то эти папки создадутся в папке сборки продукта.
Если мы решили использовать свойство outputArtifacts, то необходимо указать и свойство outputFileTags, которое является списком выходных тегов, которые правило потенциально производит. Для нашего примера:
outputFileTags:
["exampleArtefact"]
Все полученные при сборке продукта артефакты, помеченные выходным тегом совпадающим с типом продукта, при установке продукта, будут скопированы из каталога сборки в каталог установки.
Когда определены входные и выходные артефакты, необходимо подготовить саму последовательность команд для выполнения преобразования. Для этого используется свойство prepare элемента Rule. Это свойство является JavaScript сценарием, который возвращает список команд для преобразования входов в выходы. Код в этом скрипте рассматривается как функция с сигнатурой function(project, product, inputs, outputs, input, output).
Параметры input и output не определены (undefined), если для этого правила имеется несколько артефактов ввода (и вывода соответственно). Служат они как синтаксический сахар: input = inputs[0] и output = outputs[0] и являются списками с одним элементом. Параметры project и product, являются JavaScript объектами, через которые доступны свойства текущего проекта и продукта соответственно. Особый интерес вызывают объекты inputs и outputs. Рассмотрим их более подробно.
Параметры inputs и outputs являются объектами JavaScript, ключи свойств которых являются файловыми тегами, а значениями свойств — являются списки объектов, представляющих артефакты, соответствующие этим тегам. В нашем примере переменная inputs имеет 3 ключа: pngFiles, fontFiles, application. А каждый входной артефакт доступен через inputs[«pngFiles»] (или inputs.pngFiles что равнозначно). Каждый артефакт в этом списке имеет следующие свойства:
Помимо этого артефакты содержат в себе все свойства для каждого модуля, который используется в продукте. Эта особенность может использоваться для доступа к свойствам модуля. Например, свойство inputs.application[0].cpp.defines вернет для артефакта simpleApplication список определений, который будет передан при компиляции соответствующего файла. Это очень удобный и важный момент, позволяющий артефактам задать через свойства какого-нибудь модуля свои значения и группировать такие артефакты или обрабатывать их как-то по особенному.
* Было подмечено на Qbs версии 1.7.2, что если продукт подменяет свойства модуля в котором находится правило сборки, то эти свойства недоступны в артефакте. По этому эти свойства я выносил в отдельный модуль.
* Также inputs.application[0].cpp.defines не всегда срабатывает, по этому я использую функцию inputs.application[0].moduleProperty(«cpp», «defines»). Если эту функцию применять к входному артефакту, то будут возвращаться свойства которые использует артефакт в указанном модуле. Если же применять ее к продукту (например product.moduleProperty(«cpp», «defines»), то возвращаться будут свойства указанного модуля, которые использует собираемый в данный момент конечный продукт.
* Очень удобной функцией является dumpObject(object) из модуля ModUtils, которая выводит в консоль информацию о свойствах переданного в него параметра. Правда и она не всегда показывает свойства используемых в артефактах модулей.
В качестве результата выполнения скрипта выступает список команд. Команда — это то, что Qbs выполняет во время сборки. Команда всегда создается в сценарии подготовки правила. Команды бывают двух типов:
Для обоих типов команд доступны свойства:
Для использования Command, необходимо создать объект передав в его конструктор полный путь до исполняемой программы и список аргументов.
var myCommand = new Command("dir", ["/B", "/O", ">>", output.filePath]);
Например данная команда запишет вывод команды dir в выходной файл правила сборки.
Полезными свойствами, позволяющими обойти ограничение Windows на длину командной строки являются свойства:
Для обработки вывода выполняемой команды могут применяться свойства:
JavaScriptCommand команда — представляет собой JavaScript функцию, которая будет выполняться при сборке. Задается это функция в свойстве sourceCode. В исходном коде функции доступны project, product, inputs и outputs (дающий доступ к свойствам проекта, продукта, входных и выходных артефактов соответственно). Чтобы передать в функцию произвольные данные, то для команды надо добавить произвольные свойства и присвоить им нужные значения. Например так:
var cmd = new JavaScriptCommand();
cmd.myFirstData = "This is 1 string";
cmd.mySecondData = "This is 2 string";
cmd.sourceCode = function() {
console.info("String from source code"); // -->> "String from source code"
console.info("Property 1: "+myFirstData); // -->> "Property 1: This is 1 string"
console.info("Property 2: "+mySecondData); // -->> "Property 2: This is 2 string"
};
В функции, также можно использовать разные доступные сервисы [7]:
У элемента Rule также есть несколько дополнительных свойств:
Свойство multiplex, необходимо для указания порядка обработки входных артефактов. При multiplex=true, создается одна копия правила для всех входных артефактов и они обрабатываются все скопом. Таким образом в свойстве inputs будут все входные артефакты. Применяется в случаях, когда надо производить групповую обработку входных артефактов. Если же multiplex=false, то для каждого входного артефакта будет создаваться отдельная копия правила преобразования и будет создаваться свой выходной артефакт. Таким образом свойство inputs будет содержать всегда один элемент. По умолчанию это свойство имеет значение false.
Свойство condition, указывает на условие выполнения правила. Например, указать, что преобразование будет выполняться только в режиме release и для платформы windows.
qbs.buildVariant === "release" && qbs.targetOS.contains("windows")
Свойство explicitlyDependsOn представляет собой список тегов. Каждый артефакт, тег которого совпадает с тегом перечисленным в этом свойстве, ставится в зависимость для каждого выходного артефакта. Таким образом на выходе мы можем получить выходные артефакты с уже подготовленными зависимостями для дальнейшей обработки.
Свойство alwaysRun указывает, на условия выполнения команд в правиле. Если alwaysRun=true, то команды будут всегда выполняться, даже не взирая на то, что выходные артефакты уже обновлены. По умолчанию равно false.
В качестве примера правила приведу следующую задачу:
Имеется проект, из нескольких программ и библиотек, для которого необходимо скопировать все необходимые для работы программ Qt библиотеки и плагины в заданную директорию. Для этого создаем модуль с правилом преобразования и самостоятельный продукт, который будет зависеть от всех продуктов проекта, для которых и необходимо подготовить Qt библиотеки. Дополнительно надо подготовить тестовый файл с указанием всех подготовленных Qt библиотек.
-- MyWindowsDeploy.qbs --
import qbs
import qbs.ModUtils
Module {
Rule {
condition: qbs.targetOS.contains("windows") //запускается только для windows
multiplex: true //обрабатываем все входные артефакты одним трансформером
alwaysRun: true //всегда выполнять правило
inputsFromDependencies: ["installable"] //брать устанавливаемые артефакты от зависимостей собираемого продукта
Artifact {
filePath: "Copied_qt_libs.txt";
fileTags: ["deployQt"];
}
prepare: {
var cmdQt = new JavaScriptCommand();
//определяем путь до windeployqt.exe
cmdQt.windeployqt = FileInfo.joinPaths(product.moduleProperty("Qt.core", "binPath"), "windeployqt.exe");
//задаем строку для вывода в консоли
cmdQt.description = "Copy Qt libs and generate text file: "+output.fileName;
//указываем развернутую информацию по выполняемой команде
cmdQt.extendedDescription = cmdQt.windeployqt + ".exe " +
["--json"].concat(args).concat(binaryFilePaths).join(" ");
//путь до папки, куда надо копировать qt библиотеки ("<папка установки>/QtLibs")
var deployDir = FileInfo.joinPaths(product.moduleProperty("qbs","installRoot"),
product.moduleProperty("qbs","installDir"));
deployDir = FileInfo.joinPaths(deployDir, "QtLibs");
cmdQt.qtLibsPath = deployDir;
//определяем аргументы запуска программы
cmdQt.args = [];
cmdQt.args.push("--libdir", deployDir);
cmdQt.args.push("--plugindir", deployDir);
cmdQt.args.push("--no-translations");
cmdQt.args.push("--release");
//полное имя файла для записи вывода.
cmdQt.outputFilePath = output.filePath;
//определяем список путей установки программ и библиотек, от которых зависит продукт
cmdQt.binaryFilePaths = inputs.installable.filter(function (artifact) {
return artifact.fileTags.contains("application")
|| artifact.fileTags.contains("dynamiclibrary");
}).map(function(a) { return ModUtils.artifactInstalledFilePath(a); });
cmdQt.sourceCode = function(){
var process;
var tf;
try {
//выводим значения параметров
console.info("windeployqtRule: outputFilePath: "+outputFilePath);
console.info("windeployqtRule: qtLibsPath: "+qtLibsPath);
console.info("windeployqtRule: windeployqt: "+windeployqt);
console.info("windeployqtRule: windeployqtArgs: "+windeployqtArgs.join(", "));
console.info("windeployqtRule: binaryFilePaths: "+binaryFilePaths.join(", "));
//создаем папку куда будут скопированы библиотеки Qt
File.makePath(qtLibsPath);
//создаем процесс
process = new Process();
//запускаем процесс
process.exec(windeployqt,
["--json"].concat(windeployqtArgs).concat(binaryFilePaths), true);
//читаем вывод программы
var out = process.readStdOut();
//парсим выходной JSON
var inputFilePaths = JSON.parse(out)["files"].map(function (obj) {
//определяем полный путь доя скопированной библиотеки
var fn = FileInfo.joinPaths(
FileInfo.fromWindowsSeparators(obj.target),
FileInfo.fileName(
FileInfo.fromWindowsSeparators(
obj.source)));
return fn;
});
//создаем файл
tf = new TextFile(outputFilePath, TextFile.WriteOnly);
//пишем заголовок
tf.writeLine("Copied Qt files:");
inputFilePaths.forEach(function(qtLib){
tf.writeLine(qtLib); //записываем в выходной файл полный путь до скопированной библиотеки
});
} finally {
if (process)
process.close();
if (tf)
tf.close();
}
}
return [cmdQt];
}
}
}
-- ProductDeploy.qbs --
import qbs
Product {
//продукт представляет собой результат деплоя Qt библиотек для продуктов от которых он
//зависит в виде скопированных библиотек и текстового файла
type: ["deployQt"]
// указываем зависимость от модуля в котором есть правило преобразования
Depends { name: "MyWindowsDeploy" }
// указываем зависимость от продуктов, для которых
// надо подготовить Qt библиотеки, плагины и пр.
Depends { name: "libratyA" }
Depends { name: "libratyB" }
Depends { name: "applicationA" }
Depends { name: "applicationB" }
Depends { name: "applicationC" }
condition: qbs.targetOS.contains("windows") // собирать только для windows
builtByDefault: false // собирать ли продукт при общей сборке проекта
qbs.install: true
qbs.installDir: "MyProject/qtDeploy"
}
Автор: hooligan
Источник [11]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/qt-2/257258
Ссылки в тексте:
[1] Qbs: https://doc.qt.io/qbs/index.html
[2] mapron: https://habrahabr.ru/users/mapron/
[3] данной статье: https://habrahabr.ru/post/144127/
[4] Rule: https://doc.qt.io/qbs/rule-item.html
[5] FileTagger: https://doc.qt.io/qbs/filetagger-item.html
[6] Artifact: https://doc.qt.io/qbs/artifact-item.html
[7] доступные сервисы: https://doc.qt.io/qbs/list-of-builtin-services.html
[8] www.opennet.ru/opennews/art.shtml?num=33102: https://www.opennet.ru/opennews/art.shtml?num=33102
[9] stackoverflow.com/questions/tagged/qbs: https://stackoverflow.com/questions/tagged/qbs
[10] github.com/qt-labs/qbs: https://github.com/qt-labs/qbs
[11] Источник: https://habrahabr.ru/post/330218/
Нажмите здесь для печати.