Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
/*
* LibrePCB - Professional EDA for everyone!
* Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
* https://librepcb.org/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*******************************************************************************
* Includes
******************************************************************************/
#include "commandlineinterface.h"
#include <librepcb/core/application.h>
#include <librepcb/core/attribute/attributesubstitutor.h>
#include <librepcb/core/debug.h>
#include <librepcb/core/export/bom.h>
#include <librepcb/core/export/bomcsvwriter.h>
#include <librepcb/core/export/graphicsexport.h>
#include <librepcb/core/export/pickplacecsvwriter.h>
#include <librepcb/core/fileio/csvfile.h>
#include <librepcb/core/fileio/fileutils.h>
#include <librepcb/core/fileio/transactionalfilesystem.h>
#include <librepcb/core/library/cat/componentcategory.h>
#include <librepcb/core/library/cat/packagecategory.h>
#include <librepcb/core/library/cmp/component.h>
#include <librepcb/core/library/dev/device.h>
#include <librepcb/core/library/library.h>
#include <librepcb/core/library/pkg/package.h>
#include <librepcb/core/library/sym/symbol.h>
#include <librepcb/core/project/board/board.h>
#include <librepcb/core/project/board/boardfabricationoutputsettings.h>
#include <librepcb/core/project/board/boardgerberexport.h>
#include <librepcb/core/project/board/boardpickplacegenerator.h>
#include <librepcb/core/project/bomgenerator.h>
#include <librepcb/core/project/erc/ercmsg.h>
#include <librepcb/core/project/erc/ercmsglist.h>
#include <librepcb/core/project/project.h>
#include <librepcb/core/project/projectmetadata.h>
#include <librepcb/core/project/schematic/schematicpainter.h>
#include <QtCore>
#include <algorithm>
/*******************************************************************************
* Namespace
******************************************************************************/
namespace librepcb {
namespace cli {
/*******************************************************************************
* Constructors / Destructor
******************************************************************************/
CommandLineInterface::CommandLineInterface(const Application& app) noexcept
: mApp(app) {
}
/*******************************************************************************
* General Methods
******************************************************************************/
int CommandLineInterface::execute() noexcept {
QStringList positionalArgNames;
QMap<QString, QPair<QString, QString>> commands = {
{"open-project",
{tr("Open a project to execute project-related tasks."),
tr("open-project [command_options]")}},
{"open-library",
{tr("Open a library to execute library-related tasks."),
tr("open-library [command_options]")}},
};
// Add global options
QCommandLineParser parser;
parser.setApplicationDescription(tr("LibrePCB Command Line Interface"));
// Don't use the built-in addHelpOption() since it also adds the "--help-all"
// option which we don't need, and the OS-dependent option "-?".
const QCommandLineOption helpOption({"h", "help"}, tr("Print this message."));
parser.addOption(helpOption);
const QCommandLineOption versionOption = parser.addVersionOption();
QCommandLineOption verboseOption("verbose", tr("Verbose output."));
parser.addOption(verboseOption);
parser.addPositionalArgument("command",
tr("The command to execute (see list below)."));
positionalArgNames.append("command");
// Define options for "open-project"
QCommandLineOption ercOption(
"erc",
tr("Run the electrical rule check, print all non-approved "
"warnings/errors and "
"report failure (exit code = 1) if there are non-approved messages."));
QCommandLineOption exportSchematicsOption(
"export-schematics",
tr("Export schematics to given file(s). Existing files will be "
"overwritten. Supported file extensions: %1")
.arg(GraphicsExport::getSupportedExtensions().join(", ")),
tr("file"));
QCommandLineOption exportBomOption(
"export-bom",
tr("Export generic BOM to given file(s). Existing files will be "
"overwritten. Supported file extensions: %1")
.arg("csv"),
tr("file"));
QCommandLineOption exportBoardBomOption(
"export-board-bom",
tr("Export board-specific BOM to given file(s). Existing files "
"will be overwritten. Supported file extensions: %1")
.arg("csv"),
tr("file"));
QCommandLineOption bomAttributesOption(
"bom-attributes",
tr("Comma-separated list of additional attributes to be exported "
"to the BOM. Example: \"%1\"")
.arg("MANUFACTURER, MPN"),
tr("attributes"));
QCommandLineOption exportPcbFabricationDataOption(
"export-pcb-fabrication-data",
tr("Export PCB fabrication data (Gerber/Excellon) according the "
"fabrication "
"output settings of boards. Existing files will be overwritten."));
QCommandLineOption pcbFabricationSettingsOption(
"pcb-fabrication-settings",
tr("Override PCB fabrication output settings by providing a *.lp file "
"containing custom settings. If not set, the settings from the boards "
"will be used instead."),
tr("file"));
QCommandLineOption exportPnpTopOption(
"export-pnp-top",
tr("Export pick&place file for automated assembly of the top board side. "
"Existing files will be overwritten. Supported file extensions: %1")
.arg("csv, gbr"),
tr("file"));
QCommandLineOption exportPnpBottomOption(
"export-pnp-bottom",
tr("Export pick&place file for automated assembly of the bottom board "
"side. Existing files will be overwritten. Supported file extensions: "
"%1")
.arg("csv, gbr"),
tr("file"));
QCommandLineOption boardOption("board",
tr("The name of the board(s) to export. Can "
"be given multiple times. If not set, "
"all boards are exported."),
tr("name"));
QCommandLineOption boardIndexOption("board-index",
tr("Same as '%1', but allows to specify "
"boards by index instead of by name.")
.arg("--board"),
tr("index"));
QCommandLineOption removeOtherBoardsOption(
"remove-other-boards",
tr("Remove all boards not specified with '%1' from the project before "
"executing all the other actions. If '%1' is not passed, all boards "
"will be removed. Pass '%2' to save the modified project to disk.")
.arg("--board[-index]")
.arg("--save"));
QCommandLineOption saveOption(
"save",
tr("Save project before closing it (useful to upgrade file format)."));
QCommandLineOption prjStrictOption(
"strict",
tr("Fail if the project files are not strictly canonical, i.e. "
"there would be changes when saving the project. Note that "
"this option is not available for *.lppz files."));
// Define options for "open-library"
QCommandLineOption libAllOption(
"all",
tr("Perform the selected action(s) on all elements contained in "
"the opened library."));
QCommandLineOption libSaveOption(
"save",
tr("Save library (and contained elements if '--all' is given) "
"before closing them (useful to upgrade file format)."));
QCommandLineOption libStrictOption(
"strict",
tr("Fail if the opened files are not strictly canonical, i.e. "
"there would be changes when saving the library elements."));
// Build help text.
const QStringList args = mApp.arguments();
const QString executable = args.value(0);
QString helpText = parser.helpText() % "\n" % tr("Commands:") % "\n";
for (auto it = commands.constBegin(); it != commands.constEnd(); ++it) {
helpText += " " % it.key().leftJustified(15) % it.value().first % "\n";
}
helpText += "\n" % tr("List command-specific options:") % "\n " %
executable % " <command> --help";
QString usageHelpText = helpText.split("\n").value(0);
const QString helpCommandTextPrefix = tr("Help:") % " ";
QString helpCommandText = helpCommandTextPrefix % executable % " --help";
// First parse to get the supplied command (ignoring errors because the parser
// does not yet know the command-dependent options).
parser.parse(args);
// Add command-dependent options
const QString command = parser.positionalArguments().value(0);
parser.clearPositionalArguments();
if (command == "open-project") {
parser.addPositionalArgument(command, commands[command].first,
commands[command].second);
parser.addPositionalArgument("project",
tr("Path to project file (*.lpp[z])."));
positionalArgNames.append("project");
parser.addOption(ercOption);
parser.addOption(exportSchematicsOption);
parser.addOption(exportBomOption);
parser.addOption(exportBoardBomOption);
parser.addOption(bomAttributesOption);
parser.addOption(exportPcbFabricationDataOption);
parser.addOption(pcbFabricationSettingsOption);
parser.addOption(exportPnpTopOption);
parser.addOption(exportPnpBottomOption);
parser.addOption(boardOption);
parser.addOption(boardIndexOption);
parser.addOption(removeOtherBoardsOption);
parser.addOption(saveOption);
parser.addOption(prjStrictOption);
} else if (command == "open-library") {
parser.addPositionalArgument(command, commands[command].first,
commands[command].second);
parser.addPositionalArgument("library",
tr("Path to library directory (*.lplib)."));
positionalArgNames.append("library");
parser.addOption(libAllOption);
parser.addOption(libSaveOption);
parser.addOption(libStrictOption);
} else if (!command.isEmpty()) {
printErr(tr("Unknown command '%1'.").arg(command));
printErr(usageHelpText);
printErr(helpCommandText);
return 1;
}
// If a command is given, make the help texts command-specific now.
if (!command.isEmpty()) {
helpText = parser.helpText().trimmed(); // Remove the list of commands.
usageHelpText = helpText.split("\n").value(0);
helpCommandText =
helpCommandTextPrefix % executable % " " % command % " --help";
}
// Parse the actual command line arguments given by the user
if (!parser.parse(args)) {
printErr(parser.errorText());
printErr(usageHelpText);
printErr(helpCommandText);
return 1;
}
// --verbose
if (parser.isSet(verboseOption)) {
Debug::instance()->setDebugLevelStderr(Debug::DebugLevel_t::All);
}
// --help (also shown if no arguments supplied)
if (parser.isSet(helpOption) || (args.count() <= 1)) {
print(helpText);
return 0;
}
// --version
if (parser.isSet(versionOption)) {
print(tr("LibrePCB CLI Version %1").arg(mApp.applicationVersion()));
print(tr("File Format %1").arg(mApp.getFileFormatVersion().toStr()) % " " %
(mApp.isFileFormatStable() ? tr("(stable)") : tr("(unstable)")));
print(tr("Git Revision %1").arg(mApp.getGitRevision()));
print(tr("Qt Version %1 (compiled against %2)")
.arg(qVersion(), QT_VERSION_STR));
print(tr("Built at %1").arg(mApp.getBuildDate().toString(Qt::LocalDate)));
return 0;
}
// Check number of passed positional command arguments.
const QStringList positionalArgs = parser.positionalArguments();
if (positionalArgs.count() < positionalArgNames.count()) {
const QStringList names = positionalArgNames.mid(positionalArgs.count());
printErr(tr("Missing arguments:") % " " % names.join(" "));
printErr(usageHelpText);
printErr(helpCommandText);
return 1;
} else if (positionalArgs.count() > positionalArgNames.count()) {
const QStringList args = positionalArgs.mid(positionalArgNames.count());
printErr(tr("Unknown arguments:") % " " % args.join(" "));
printErr(usageHelpText);
printErr(helpCommandText);
return 1;
}
// Execute command
bool cmdSuccess = false;
if (command == "open-project") {
cmdSuccess = openProject(
positionalArgs.value(1), // project filepath
parser.isSet(ercOption), // run ERC
parser.values(exportSchematicsOption), // export schematics
parser.values(exportBomOption), // export generic BOM
parser.values(exportBoardBomOption), // export board BOM
parser.value(bomAttributesOption), // BOM attributes
parser.isSet(exportPcbFabricationDataOption), // export PCB fab. data
parser.value(pcbFabricationSettingsOption), // PCB fab. settings
parser.values(exportPnpTopOption), // export PnP top
parser.values(exportPnpBottomOption), // export PnP bottom
parser.values(boardOption), // board names
parser.values(boardIndexOption), // board indices
parser.isSet(removeOtherBoardsOption), // remove other boards
parser.isSet(saveOption), // save project
parser.isSet(prjStrictOption) // strict mode
);
} else if (command == "open-library") {
cmdSuccess = openLibrary(positionalArgs.value(1), // library directory
parser.isSet(libAllOption), // all elements
parser.isSet(libSaveOption), // save
parser.isSet(libStrictOption) // strict mode
);
} else {
printErr("Internal failure."); // No tr() because this cannot occur.
}
if (cmdSuccess) {
print(tr("SUCCESS"));
return 0;
} else {
print(tr("Finished with errors!"));
return 1;
}
}
/*******************************************************************************
* Private Methods
******************************************************************************/
bool CommandLineInterface::openProject(
const QString& projectFile, bool runErc,
const QStringList& exportSchematicsFiles, const QStringList& exportBomFiles,
const QStringList& exportBoardBomFiles, const QString& bomAttributes,
bool exportPcbFabricationData, const QString& pcbFabricationSettingsPath,
const QStringList& exportPnpTopFiles,
const QStringList& exportPnpBottomFiles, const QStringList& boardNames,
const QStringList& boardIndices, bool removeOtherBoards, bool save,
bool strict) const noexcept {
try {
bool success = true;
QMap<FilePath, int> writtenFilesCounter;
// Open project
FilePath projectFp(QFileInfo(projectFile).absoluteFilePath());
print(tr("Open project '%1'...").arg(prettyPath(projectFp, projectFile)));
std::shared_ptr<TransactionalFileSystem> projectFs;
QString projectFileName;
if (projectFp.getSuffix() == "lppz") {
projectFs = TransactionalFileSystem::openRO(projectFp.getParentDir());
projectFs->removeDirRecursively(); // 1) get a clean initial state
projectFs->loadFromZip(projectFp); // 2) load files from ZIP
foreach (const QString& fn, projectFs->getFiles()) {
if (fn.endsWith(".lpp")) {
projectFileName = fn;
}
}
} else {
projectFs = TransactionalFileSystem::open(projectFp.getParentDir(), save);
projectFileName = projectFp.getFilename();
}
Project project(std::unique_ptr<TransactionalDirectory>(
new TransactionalDirectory(projectFs)),
projectFileName); // can throw
// Parse list of boards.
QList<Board*> boards;
foreach (const QString& boardName, boardNames) {
if (Board* board = project.getBoardByName(boardName)) {
if (!boards.contains(board)) {
boards.append(board);
}
} else {
printErr(
tr("ERROR: No board with the name '%1' found.").arg(boardName));
success = false;
}
}
foreach (const QString& boardIndex, boardIndices) {
bool ok = false;
const int index = boardIndex.trimmed().toInt(&ok);
Board* board = project.getBoardByIndex(index);
if (ok && board) {
if (!boards.contains(board)) {
boards.append(board);
}
} else {
printErr(tr("ERROR: Board index '%1' is invalid.").arg(boardIndex));
success = false;
}
}
// Remove other boards (note: do this at the very beginning to make all
// the other commands, e.g. the ERC, working without the removed boards).
if (removeOtherBoards) {
print(tr("Remove other boards..."));
foreach (Board* board, project.getBoards()) {
if (!boards.contains(board)) {
print(QString(" - '%1'").arg(*board->getName()));
project.removeBoard(*board);
}
}
}
// If no boards are specified, export all boards.
if (boardNames.isEmpty() && boardIndices.isEmpty()) {
boards = project.getBoards();
}
// Check for non-canonical files (strict mode)
if (strict) {
print(tr("Check for non-canonical files..."));
if (projectFp.getSuffix() == "lppz") {
printErr(" " %
tr("ERROR: The option '--strict' is not available for "
"*.lppz files!"));
success = false;
} else {
project.save(); // can throw
QStringList paths = projectFs->checkForModifications(); // can throw
// ignore user config files
paths = paths.filter(QRegularExpression("^((?!\\.user\\.lp).)*$"));
// sort file paths to increases readability of console output
std::sort(paths.begin(), paths.end());
foreach (const QString& path, paths) {
printErr(
QString(" - Non-canonical file: '%1'")
.arg(prettyPath(projectFs->getAbsPath(path), projectFile)));
}
if (paths.count() > 0) {
success = false;
}
}
}
// ERC
if (runErc) {
print(tr("Run ERC..."));
QStringList messages;
int approvedMsgCount = 0;
foreach (const ErcMsg* msg, project.getErcMsgList().getItems()) {
if (!msg->isVisible()) continue;
if (msg->isIgnored()) {
++approvedMsgCount;
} else {
QString severity;
switch (msg->getMsgType()) {
case ErcMsg::ErcMsgType_t::CircuitWarning:
case ErcMsg::ErcMsgType_t::SchematicWarning:
case ErcMsg::ErcMsgType_t::BoardWarning:
severity = tr("WARNING");
break;
default:
severity = tr("ERROR");
break;
}
messages.append(
QString(" - [%1] %2").arg(severity, msg->getMsg()));
}
}
print(" " % tr("Approved messages: %1").arg(approvedMsgCount));
print(" " % tr("Non-approved messages: %1").arg(messages.count()));
// sort messages to increases readability of console output
std::sort(messages.begin(), messages.end());
foreach (const QString& msg, messages) { printErr(msg); }
if (messages.count() > 0) {
success = false;
}
}
// Export schematics
foreach (const QString& destStr, exportSchematicsFiles) {
print(tr("Export schematics to '%1'...").arg(destStr));
QString destPathStr = AttributeSubstitutor::substitute(
destStr, &project, [&](const QString& str) {
return FilePath::cleanFileName(
str, FilePath::ReplaceSpaces | FilePath::KeepCase);
});
FilePath destPath(QFileInfo(destPathStr).absoluteFilePath());
GraphicsExport graphicsExport;
graphicsExport.setDocumentName(*project.getMetadata().getName());
QObject::connect(
&graphicsExport, &GraphicsExport::savingFile,
[&destPathStr, &writtenFilesCounter](const FilePath& fp) {
print(QString(" => '%1'").arg(prettyPath(fp, destPathStr)));
writtenFilesCounter[fp]++;
});
std::shared_ptr<GraphicsExportSettings> settings =
std::make_shared<GraphicsExportSettings>();
GraphicsExport::Pages pages;
foreach (const Schematic* schematic, project.getSchematics()) {
pages.append(std::make_pair(
std::make_shared<SchematicPainter>(*schematic), settings));
}
graphicsExport.startExport(pages, destPath);
const QString errorMsg = graphicsExport.waitForFinished();
if (!errorMsg.isEmpty()) {
printErr(" " % tr("ERROR") % ": " % errorMsg);
success = false;
}
}
// Export BOM
if (exportBomFiles.count() + exportBoardBomFiles.count() > 0) {
QList<QPair<QString, bool>> jobs; // <OutputPath, BoardSpecific>
foreach (const QString& fp, exportBomFiles) {
jobs.append(qMakePair(fp, false));
}
foreach (const QString& fp, exportBoardBomFiles) {
jobs.append(qMakePair(fp, true));
}
QStringList attributes;
foreach (const QString str,
bomAttributes.simplified().split(',', QString::SkipEmptyParts)) {
attributes.append(str.trimmed());
}
foreach (const auto& job, jobs) {
QList<Board*> boardsToExport;
const QString& destStr = job.first;
bool boardSpecific = job.second;
if (boardSpecific) {
print(tr("Export board-specific BOM to '%1'...").arg(destStr));
boardsToExport = boards;
} else {
print(tr("Export generic BOM to '%1'...").arg(destStr));
boardsToExport = {nullptr};
}
foreach (const Board* board, boardsToExport) {
const AttributeProvider* attrProvider = board;
if (!board) {
attrProvider = &project;
}
QString destPathStr = AttributeSubstitutor::substitute(
destStr, attrProvider, [&](const QString& str) {
return FilePath::cleanFileName(
str, FilePath::ReplaceSpaces | FilePath::KeepCase);
});
FilePath fp(QFileInfo(destPathStr).absoluteFilePath());
BomGenerator gen(project);
gen.setAdditionalAttributes(attributes);
std::shared_ptr<Bom> bom = gen.generate(board);
if (board) {
print(QString(" - '%1' => '%2'")
.arg(*board->getName(), prettyPath(fp, destPathStr)));
} else {
print(QString(" => '%1'").arg(prettyPath(fp, destPathStr)));
}
QString suffix = destStr.split('.').last().toLower();
if (suffix == "csv") {
BomCsvWriter writer(*bom);
std::shared_ptr<CsvFile> csv = writer.generateCsv(); // can throw
csv->saveToFile(fp); // can throw
writtenFilesCounter[fp]++;
} else {
printErr(" " % tr("ERROR: Unknown extension '%1'.").arg(suffix));
success = false;
}
}
}
}
// Export PCB fabrication data
if (exportPcbFabricationData) {
print(tr("Export PCB fabrication data..."));
tl::optional<BoardFabricationOutputSettings> customSettings;
QList<Board*> boardsToExport = boards;
if (!pcbFabricationSettingsPath.isEmpty()) {
try {
qDebug() << "Load custom fabrication output settings:"
<< pcbFabricationSettingsPath;
FilePath fp(QFileInfo(pcbFabricationSettingsPath).absoluteFilePath());
customSettings = BoardFabricationOutputSettings(
SExpression::parse(FileUtils::readFile(fp), fp),
qApp->getFileFormatVersion()); // can throw
} catch (const Exception& e) {
printErr(
tr("ERROR: Failed to load custom settings: %1").arg(e.getMsg()));
success = false;
boardsToExport.clear(); // avoid exporting any boards
}
}
foreach (const Board* board, boardsToExport) {
print(" " % tr("Board '%1':").arg(*board->getName()));
BoardGerberExport grbExport(*board);
grbExport.exportPcbLayers(
customSettings
? *customSettings
: board->getFabricationOutputSettings()); // can throw
foreach (const FilePath& fp, grbExport.getWrittenFiles()) {
print(QString(" => '%1'").arg(prettyPath(fp, projectFile)));
writtenFilesCounter[fp]++;
}
}
}
// Export pick&place files
if ((exportPnpTopFiles.count() + exportPnpBottomFiles.count()) > 0) {
struct Job {
QString boardSideStr;
PickPlaceCsvWriter::BoardSide boardSideCsv;
BoardGerberExport::BoardSide boardSideGbr;
QString destStr;
};
QVector<Job> jobs;
foreach (const QString& fp, exportPnpTopFiles) {
jobs.append(Job{tr("top"), PickPlaceCsvWriter::BoardSide::TOP,
BoardGerberExport::BoardSide::Top, fp});
}
foreach (const QString& fp, exportPnpBottomFiles) {
jobs.append(Job{tr("bottom"), PickPlaceCsvWriter::BoardSide::BOTTOM,
BoardGerberExport::BoardSide::Bottom, fp});
}
foreach (const auto& job, jobs) {
print(tr("Export %1 assembly data to '%2'...")
.arg(job.boardSideStr)
.arg(job.destStr));
foreach (const Board* board, boards) {
const QString destPathStr = AttributeSubstitutor::substitute(
job.destStr, board, [&](const QString& str) {
return FilePath::cleanFileName(
str, FilePath::ReplaceSpaces | FilePath::KeepCase);
});
const FilePath fp(QFileInfo(destPathStr).absoluteFilePath());
print(QString(" - '%1' => '%2'")
.arg(*board->getName(), prettyPath(fp, destPathStr)));
const QString suffix = job.destStr.split('.').last().toLower();
if (suffix == "csv") {
BoardPickPlaceGenerator gen(*board);
std::shared_ptr<PickPlaceData> data = gen.generate();
PickPlaceCsvWriter writer(*data);
writer.setIncludeMetadataComment(true);
writer.setBoardSide(job.boardSideCsv);
std::shared_ptr<CsvFile> csv = writer.generateCsv(); // can throw
csv->saveToFile(fp); // can throw
writtenFilesCounter[fp]++;
} else if (suffix == "gbr") {
BoardGerberExport gen(*board);
gen.exportComponentLayer(job.boardSideGbr, fp); // can throw
writtenFilesCounter[fp]++;
} else {
printErr(" " % tr("ERROR: Unknown extension '%1'.").arg(suffix));
success = false;
}
}
}
}
// Save project
if (save) {
print(tr("Save project..."));
if (failIfFileFormatUnstable()) {
success = false;
} else {
project.save(); // can throw
if (projectFp.getSuffix() == "lppz") {
projectFs->exportToZip(projectFp); // can throw
} else {
projectFs->save(); // can throw
}
}
}
// Fail if some files were written multiple times
bool filesOverwritten = false;
QMapIterator<FilePath, int> writtenFilesIterator(writtenFilesCounter);
while (writtenFilesIterator.hasNext()) {
writtenFilesIterator.next();
if (writtenFilesIterator.value() > 1) {
filesOverwritten = true;
printErr(tr("ERROR: The file '%1' was written multiple times!")
.arg(prettyPath(writtenFilesIterator.key(), projectFile)));
}
}
if (filesOverwritten) {
printErr(tr("NOTE: To avoid writing files multiple times, make "
"sure to pass unique filepaths to all export "
"functions. For board output files, you could either "
"add the placeholder '%1' to the path or specify the "
"boards to export with the '%2' argument.")
.arg("{{BOARD}}", "--board"));
success = false;
}
return success;
} catch (const Exception& e) {
printErr(tr("ERROR: %1").arg(e.getMsg()));
return false;
}
}
bool CommandLineInterface::openLibrary(const QString& libDir, bool all,
bool save, bool strict) const noexcept {
try {
bool success = true;
// Open library
FilePath libFp(QFileInfo(libDir).absoluteFilePath());
print(tr("Open library '%1'...").arg(prettyPath(libFp, libDir)));
std::shared_ptr<TransactionalFileSystem> libFs =
TransactionalFileSystem::open(libFp, save); // can throw
Library lib(std::unique_ptr<TransactionalDirectory>(
new TransactionalDirectory(libFs))); // can throw
processLibraryElement(libDir, *libFs, lib, save, strict,
success); // can throw
// Open all component categories
if (all) {
QStringList elements = lib.searchForElements<ComponentCategory>();
print(tr("Process %1 component categories...").arg(elements.count()));
foreach (const QString& dir, elements) {
FilePath fp = libFp.getPathTo(dir);
qInfo() << tr("Open '%1'...").arg(prettyPath(fp, libDir));
std::shared_ptr<TransactionalFileSystem> fs =
TransactionalFileSystem::open(fp, save); // can throw
ComponentCategory element(std::unique_ptr<TransactionalDirectory>(
new TransactionalDirectory(fs))); // can throw
processLibraryElement(libDir, *fs, element, save, strict,
success); // can throw
}
}
// Open all package categories
if (all) {
QStringList elements = lib.searchForElements<PackageCategory>();
print(tr("Process %1 package categories...").arg(elements.count()));
foreach (const QString& dir, elements) {
FilePath fp = libFp.getPathTo(dir);
qInfo() << tr("Open '%1'...").arg(prettyPath(fp, libDir));
std::shared_ptr<TransactionalFileSystem> fs =
TransactionalFileSystem::open(fp, save); // can throw
PackageCategory element(std::unique_ptr<TransactionalDirectory>(
new TransactionalDirectory(fs))); // can throw
processLibraryElement(libDir, *fs, element, save, strict,
success); // can throw
}
}
// Open all symbols
if (all) {
QStringList elements = lib.searchForElements<Symbol>();
print(tr("Process %1 symbols...").arg(elements.count()));
foreach (const QString& dir, elements) {
FilePath fp = libFp.getPathTo(dir);
qInfo() << tr("Open '%1'...").arg(prettyPath(fp, libDir));
std::shared_ptr<TransactionalFileSystem> fs =
TransactionalFileSystem::open(fp, save); // can throw
Symbol element(std::unique_ptr<TransactionalDirectory>(
new TransactionalDirectory(fs))); // can throw
processLibraryElement(libDir, *fs, element, save, strict,
success); // can throw
}
}
// Open all packages
if (all) {
QStringList elements = lib.searchForElements<Package>();
print(tr("Process %1 packages...").arg(elements.count()));
foreach (const QString& dir, elements) {
FilePath fp = libFp.getPathTo(dir);
qInfo() << tr("Open '%1'...").arg(prettyPath(fp, libDir));
std::shared_ptr<TransactionalFileSystem> fs =
TransactionalFileSystem::open(fp, save); // can throw
Package element(std::unique_ptr<TransactionalDirectory>(
new TransactionalDirectory(fs))); // can throw
processLibraryElement(libDir, *fs, element, save, strict,
success); // can throw
}
}
// Open all components
if (all) {
QStringList elements = lib.searchForElements<Component>();
print(tr("Process %1 components...").arg(elements.count()));
foreach (const QString& dir, elements) {
FilePath fp = libFp.getPathTo(dir);
qInfo() << tr("Open '%1'...").arg(prettyPath(fp, libDir));
std::shared_ptr<TransactionalFileSystem> fs =
TransactionalFileSystem::open(fp, save); // can throw
Component element(std::unique_ptr<TransactionalDirectory>(
new TransactionalDirectory(fs))); // can throw
processLibraryElement(libDir, *fs, element, save, strict,
success); // can throw
}
}
// Open all devices
if (all) {
QStringList elements = lib.searchForElements<Device>();
print(tr("Process %1 devices...").arg(elements.count()));
foreach (const QString& dir, elements) {
FilePath fp = libFp.getPathTo(dir);
qInfo() << tr("Open '%1'...").arg(prettyPath(fp, libDir));
std::shared_ptr<TransactionalFileSystem> fs =
TransactionalFileSystem::open(fp, save); // can throw
Device element(std::unique_ptr<TransactionalDirectory>(
new TransactionalDirectory(fs))); // can throw
processLibraryElement(libDir, *fs, element, save, strict,
success); // can throw
}
}
return success;
} catch (const Exception& e) {
printErr(tr("ERROR: %1").arg(e.getMsg()));
return false;
}
}
void CommandLineInterface::processLibraryElement(const QString& libDir,
TransactionalFileSystem& fs,
LibraryBaseElement& element,
bool save, bool strict,
bool& success) const {
// Save element to transactional file system, if needed
if (strict || save) {
element.save(); // can throw
}
// Check for non-canonical files (strict mode)
if (strict) {
qInfo() << tr("Check '%1' for non-canonical files...")
.arg(prettyPath(fs.getPath(), libDir));
QStringList paths = fs.checkForModifications(); // can throw
// sort file paths to increases readability of console output
std::sort(paths.begin(), paths.end());
foreach (const QString& path, paths) {
printErr(QString(" - Non-canonical file: '%1'")
.arg(prettyPath(fs.getAbsPath(path), libDir)));
}
if (paths.count() > 0) {
success = false;
}
}
// Save element to file system, if needed
if (save) {
qInfo() << tr("Save '%1'...").arg(prettyPath(fs.getPath(), libDir));
if (failIfFileFormatUnstable()) {
success = false;
} else {
fs.save(); // can throw
}
}
// Do not propagate changes in the transactional file system to the
// following checks
fs.discardChanges();
}
QString CommandLineInterface::prettyPath(const FilePath& path,
const QString& style) noexcept {
if (QFileInfo(style).isAbsolute()) {
return path.toNative(); // absolute path
} else if (path == FilePath(QDir::currentPath())) {
return path.getFilename(); // name of current directory
} else {
return QDir::toNativeSeparators(
path.toRelative(FilePath(QDir::currentPath()))); // relative path
}
}
bool CommandLineInterface::failIfFileFormatUnstable() noexcept {
if ((!qApp->isFileFormatStable()) &&
(qgetenv("LIBREPCB_DISABLE_UNSTABLE_WARNING") != "1")) {
printErr(
tr("This application version is UNSTABLE! Option '%1' is disabled to "
"avoid breaking projects or libraries. Please use a stable "
"release instead.")
.arg("--save"));
return true;
} else {
qInfo() << "Application version is unstable, but warning is disabled with "
"environment variable LIBREPCB_DISABLE_UNSTABLE_WARNING.";
return false;
}
}
void CommandLineInterface::print(const QString& str) noexcept {
QTextStream s(stdout);
s << str << endl;
}
void CommandLineInterface::printErr(const QString& str) noexcept {
QTextStream s(stderr);
s << str << endl;
}
/*******************************************************************************
* End of File
******************************************************************************/
} // namespace cli
} // namespace librepcb
c136b73e0This file is pinned to the selected release. Download the original to open it in your design tool.