Some refactoring of package manager, new features

* Moved tlSystemPaths into lay namespace where it belongs
* Doc updates
* New command line switch -y and -yd for unattended installation
* Download URL's can be relative to salt.mine URL
* KLAYOUT_HOME environment variable to make ~/.klayout configurable
* Better error messages on XML parser on file/stream read errors
  (specifically from http/https)
This commit is contained in:
Matthias Koefferlein 2017-10-03 14:19:01 +02:00
parent 407c967de4
commit c077feb3d5
17 changed files with 536 additions and 224 deletions

View File

@ -26,7 +26,6 @@
#include "dbEdgeProcessor.h"
#include "dbReader.h"
#include "tlStream.h"
#include "tlSystemPaths.h"
#include <QDir>
#include <QResource>

View File

@ -30,9 +30,12 @@
<p>
Packages are identified by name. A package name needs to be unique in the package universe.
You can use a prefixed name like <tt>www.mydomain.org/nameofpackage</tt> to create a non-ambiguous name.
The choice of the prefix is entirely up to you. You can use a domain name that is owned by
yourself for example.
You can use a prefixed name like <tt>www.mydomain.org/nameofpackage</tt> to create a non-ambiguous name.
Use a slash to separate the prefix from the actual package name.
The choice of the prefix is entirely up to you as long as it contains letters, digits, underscores, hypthens or dots.
You can use a domain name that is owned by
yourself for example. You can use multiple prefixes to further differentiate the packages
inside your namespace.
</p>
<p>

View File

@ -56,7 +56,8 @@ HEADERS = \
laySignalHandler.h \
layLibraryController.h \
layFontController.h \
layNativePlugin.h
layNativePlugin.h \
laySystemPaths.h
FORMS = \
ClipDialog.ui \
@ -159,7 +160,8 @@ SOURCES = \
laySignalHandler.cc \
layLibraryController.cc \
layFontController.cc \
layNativePlugin.cc
layNativePlugin.cc \
laySystemPaths.cc
RESOURCES = layBuildInMacros.qrc \
layHelpResources.qrc \

View File

@ -37,6 +37,7 @@
#include "layMacroController.h"
#include "layTechnologyController.h"
#include "laySaltController.h"
#include "laySystemPaths.h"
#include "lymMacro.h"
#include "gtf.h"
#include "gsiDecl.h"
@ -52,7 +53,6 @@
#include "tlAssert.h"
#include "tlLog.h"
#include "tlString.h"
#include "tlSystemPaths.h"
#include "tlExpression.h"
#include "tlExceptions.h"
#include "tlInternational.h"
@ -280,13 +280,13 @@ Application::Application (int &argc, char **argv, bool non_ui_mode)
bool gtf_save_incremental = false;
// get and create the klayout appdata folder if required
m_appdata_path = tl::get_appdata_path ();
m_appdata_path = lay::get_appdata_path ();
// get the installation path
m_inst_path = tl::get_inst_path ();
m_inst_path = lay::get_inst_path ();
// get the KLayout path
m_klayout_path = tl::get_klayout_path ();
m_klayout_path = lay::get_klayout_path ();
if (mp_qapp_gui) {
@ -408,6 +408,9 @@ Application::Application (int &argc, char **argv, bool non_ui_mode)
std::string tech;
std::string tech_file;
std::vector <std::string> package_inst;
bool packages_with_dep = false;
bool editable_set = false;
for (int i = 1; i < argc; ++i) {
@ -593,7 +596,15 @@ Application::Application (int &argc, char **argv, bool non_ui_mode)
} else if (a == "-x") {
m_sync_mode = true;
} else if (a == "-y" && (i + 1) < argc) {
package_inst.push_back (args [++i]);
} else if (a == "-yd") {
packages_with_dep = true;
} else if (a == "-v") {
tl::info << lay::Version::name () << " " << lay::Version::version ();
@ -692,7 +703,16 @@ Application::Application (int &argc, char **argv, bool non_ui_mode)
sc->add_path (*p);
}
sc->set_salt_mine_url (tl::salt_mine_url ());
sc->set_salt_mine_url (lay::salt_mine_url ());
// Do package installation if requested.
if (!package_inst.empty ()) {
if (! sc->install_packages (package_inst, packages_with_dep)) {
exit (1);
} else {
exit (0);
}
}
}
@ -1065,8 +1085,11 @@ Application::usage ()
r += tl::to_string (QObject::tr (" -v Print program version and exit")) + "\n";
r += tl::to_string (QObject::tr (" -wd <name>=<value> Define a variable within expressions")) + "\n";
r += tl::to_string (QObject::tr (" -x Synchronous drawing mode")) + "\n";
r += tl::to_string (QObject::tr (" -zz Non-GUI mode (database only, implies -nc)")) + "\n";
r += tl::to_string (QObject::tr (" -y <package> Package installation: install package(s) and exit - can be used more than once")) + "\n";
r += tl::to_string (QObject::tr (" ('package' is a name, an URL and optionally a version in round brackets)")) + "\n";
r += tl::to_string (QObject::tr (" -yd With -y: include dependencies")) + "\n";
r += tl::to_string (QObject::tr (" -z Non-GUI mode (hidden views)")) + "\n";
r += tl::to_string (QObject::tr (" -zz Non-GUI mode (database only, implies -nc)")) + "\n";
return r;
}

View File

@ -22,6 +22,7 @@
#include "laySaltController.h"
#include "laySaltManagerDialog.h"
#include "laySaltDownloadManager.h"
#include "layConfig.h"
#include "layMainWindow.h"
#include "layQtTools.h"
@ -152,12 +153,14 @@ SaltController::show_editor ()
void
SaltController::sync_file_watcher ()
{
m_file_watcher->clear ();
m_file_watcher->enable (false);
for (lay::Salt::flat_iterator g = m_salt.begin_flat (); g != m_salt.end_flat (); ++g) {
m_file_watcher->add_file ((*g)->path ());
if (m_file_watcher) {
m_file_watcher->clear ();
m_file_watcher->enable (false);
for (lay::Salt::flat_iterator g = m_salt.begin_flat (); g != m_salt.end_flat (); ++g) {
m_file_watcher->add_file ((*g)->path ());
}
m_file_watcher->enable (true);
}
m_file_watcher->enable (true);
}
void
@ -167,6 +170,54 @@ SaltController::sync_files ()
emit salt_changed ();
}
bool
SaltController::install_packages (const std::vector<std::string> &packages, bool with_dep)
{
lay::SaltDownloadManager manager;
lay::Salt salt_mine;
if (! m_salt_mine_url.empty ()) {
tl::log << tl::to_string (tr ("Downloading package repository from %1").arg (tl::to_qstring (m_salt_mine_url)));
salt_mine.load (m_salt_mine_url);
}
for (std::vector<std::string>::const_iterator p = packages.begin (); p != packages.end (); ++p) {
if (p->empty ()) {
continue;
}
std::string v, n = *p;
size_t br = p->find ("(");
if (br != std::string::npos) {
n = std::string (*p, 0, br);
v = std::string (*p, br + 1);
size_t brr = v.find (")");
if (brr != std::string::npos) {
v = std::string (v, 0, brr);
}
}
if (n.find ("http:") == 0 || n.find ("https:") == 0 || n.find ("file:") == 0 || n[0] == '/' || n[0] == '\\') {
// it's a URL
manager.register_download (std::string (), n, v);
} else {
// it's a plain name
manager.register_download (n, std::string (), v);
}
}
if (with_dep) {
manager.compute_dependencies (m_salt, salt_mine);
} else {
manager.compute_packages (m_salt, salt_mine);
}
return manager.execute (0, m_salt);
}
void
SaltController::add_path (const std::string &path)
{

View File

@ -121,11 +121,35 @@ public:
*/
void add_path (const std::string &path);
/**
* @brief Installs the packages from the given list
*
* The list is a list of names or URL's, optionally with a version in round brackets.
* If a package with the given name or URL is installed already, it is skipped.
* Otherwise, it's downloaded and installed. If URL's are given, the URL is
* used for download. If names are given, the URL's are taken from the
* salt mine index.
*
* If "with_dep" is used, dependencies are also installed as taken from the
* salt mine index.
*
* The method returns true if all packages could be installed successfully.
*/
bool install_packages (const std::vector<std::string> &packages, bool with_dep);
/**
* @brief Specifies the salt mine (package repository) URL
*/
void set_salt_mine_url (const std::string &url);
/**
* @brief Gets the salt mine (package repository) URL
*/
const std::string &salt_mine_url () const
{
return m_salt_mine_url;
}
/**
* @brief Gets the salt
*/
@ -163,7 +187,7 @@ private:
lay::SaltManagerDialog *mp_salt_dialog;
lay::MainWindow *mp_mw;
std::string m_salt_mine_url;
lay::Salt m_salt, m_salt_mine;
lay::Salt m_salt;
tl::FileSystemWatcher *m_file_watcher;
tl::DeferredMethod<SaltController> dm_sync_file_watcher;
tl::DeferredMethod<SaltController> dm_sync_files;

View File

@ -121,47 +121,65 @@ SaltDownloadManager::SaltDownloadManager ()
void
SaltDownloadManager::register_download (const std::string &name, const std::string &url, const std::string &version)
{
m_registry.insert (std::make_pair (name, Descriptor (url, version)));
m_registry.push_back (Descriptor (name, url, version));
}
void
SaltDownloadManager::compute_dependencies (const lay::Salt &salt, const lay::Salt &salt_mine)
{
compute_list (salt, salt_mine, true);
}
void
SaltDownloadManager::compute_packages (const lay::Salt &salt, const lay::Salt &salt_mine)
{
compute_list (salt, salt_mine, false);
}
void
SaltDownloadManager::compute_list (const lay::Salt &salt, const lay::Salt &salt_mine, bool with_dep)
{
tl::AbsoluteProgress progress (tl::to_string (QObject::tr ("Computing package dependencies ..")));
std::map<std::string, Descriptor> registry;
// remove those registered entries which don't need to be updated
registry = m_registry;
for (std::map<std::string, Descriptor>::const_iterator p = registry.begin (); p != registry.end (); ++p) {
const SaltGrain *g = salt.grain_by_name (p->first);
if (g && SaltGrain::compare_versions (p->second.version, g->version ()) == 0 && p->second.url == g->url ()) {
m_registry.erase (p->first);
}
}
// add further entries as derived from the dependencies
while (needs_iteration ()) {
fetch_missing (salt_mine, progress);
fetch_missing (salt, salt_mine, progress);
registry = m_registry;
for (std::map<std::string, Descriptor>::const_iterator p = registry.begin (); p != registry.end (); ++p) {
if (! with_dep) {
break;
}
for (std::vector<SaltGrain::Dependency>::const_iterator d = p->second.grain.dependencies ().begin (); d != p->second.grain.dependencies ().end (); ++d) {
std::map<std::string, size_t> reg_by_name;
for (std::vector<Descriptor>::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) {
reg_by_name.insert (std::make_pair (p->name, p - m_registry.begin ()));
}
std::map<std::string, Descriptor>::iterator r = m_registry.find (d->name);
if (r != m_registry.end ()) {
size_t n = m_registry.size ();
for (size_t i = 0; i < n; ++i) {
if (SaltGrain::compare_versions (r->second.version, d->version) < 0) {
const Descriptor &p = m_registry [i];
for (std::vector<SaltGrain::Dependency>::const_iterator d = p.grain.dependencies ().begin (); d != p.grain.dependencies ().end (); ++d) {
std::map<std::string, size_t>::iterator r = reg_by_name.find (d->name);
if (r != reg_by_name.end ()) {
// Dependency is already scheduled for installation - check if we need a newer package
Descriptor &pd = m_registry [r->second];
if (SaltGrain::compare_versions (pd.version, d->version) < 0) {
// Grain is present, but too old -> update version and reload in the next iteration
r->second.downloaded = false;
r->second.version = d->version;
r->second.url = d->url;
r->second.downloaded = false;
if (tl::verbosity() >= 20) {
tl::log << "Upgrading installation request as required by package " << p.name << ": " << d->name << " (" << d->version << ") with URL " << d->url;
}
pd.downloaded = false;
pd.version = d->version;
pd.url = d->url;
}
@ -172,11 +190,25 @@ SaltDownloadManager::compute_dependencies (const lay::Salt &salt, const lay::Sal
// Grain is installed already, but too old -> register for update
if (SaltGrain::compare_versions (g->version (), d->version) < 0) {
register_download (d->name, d->url, d->version);
if (tl::verbosity() >= 20) {
tl::log << "Considering for update as dependency: " << d->name << " (" << d->version << ") with URL " << d->url;
}
m_registry.push_back (Descriptor (d->name, d->url, d->version));
} else {
if (tl::verbosity() >= 20) {
tl::log << "Dependency already satisfied: " << d->name << "(" << d->version << ")";
}
}
} else {
register_download (d->name, d->url, d->version);
if (tl::verbosity() >= 20) {
tl::log << "Considering for download as dependency: " << d->name << " (" << d->version << ") with URL " << d->url;
}
m_registry.push_back (Descriptor (d->name, d->url, d->version));
}
}
@ -191,8 +223,8 @@ SaltDownloadManager::compute_dependencies (const lay::Salt &salt, const lay::Sal
bool
SaltDownloadManager::needs_iteration ()
{
for (std::map<std::string, Descriptor>::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) {
if (! p->second.downloaded) {
for (std::vector<Descriptor>::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) {
if (! p->downloaded) {
return true;
}
}
@ -200,35 +232,95 @@ SaltDownloadManager::needs_iteration ()
}
void
SaltDownloadManager::fetch_missing (const lay::Salt &salt_mine, tl::AbsoluteProgress &progress)
SaltDownloadManager::fetch_missing (const lay::Salt &salt, const lay::Salt &salt_mine, tl::AbsoluteProgress &progress)
{
for (std::map<std::string, Descriptor>::iterator p = m_registry.begin (); p != m_registry.end (); ++p) {
std::vector<Descriptor> registry;
if (! p->second.downloaded) {
// drop entries with same name but lower version
registry.swap (m_registry);
std::sort (registry.begin (), registry.end ());
for (std::vector<Descriptor>::const_iterator p = registry.begin (); p != registry.end (); ++p) {
if (p + 1 != registry.end ()) {
if (p->name != p[1].name) {
m_registry.push_back (*p);
}
} else {
m_registry.push_back (*p);
}
}
// download the items that need to be downloaded
for (std::vector<Descriptor>::iterator p = m_registry.begin (); p != m_registry.end (); ++p) {
if (! p->downloaded) {
++progress;
// If no URL is given, utilize the salt mine to fetch it
if (p->second.url.empty ()) {
const lay::SaltGrain *g = salt_mine.grain_by_name (p->first);
if (SaltGrain::compare_versions (g->version (), p->second.version) < 0) {
throw tl::Exception (tl::to_string (QObject::tr ("Package '%1': package in repository is too old (%2) to satisfy requirements (%3)").arg (tl::to_qstring (p->first)).arg (tl::to_qstring (g->version ())).arg (tl::to_qstring (p->second.version))));
if (p->url.empty ()) {
tl_assert (! p->name.empty ());
const lay::SaltGrain *g = salt_mine.grain_by_name (p->name);
if (! g) {
throw tl::Exception (tl::to_string (QObject::tr ("Package '%1' not found in index - cannot resolve download URL").arg (tl::to_qstring (p->name))));
}
p->second.version = g->version ();
p->second.url = g->url ();
if (tl::verbosity() >= 20) {
tl::log << "Resolved package URL for package " << p->name << ": " << g->url ();
}
p->url = g->url ();
}
try {
p->second.grain = SaltGrain::from_url (p->second.url);
p->grain = SaltGrain::from_url (p->url);
} catch (tl::Exception &ex) {
throw tl::Exception (tl::to_string (QObject::tr ("Error fetching spec file for package '%1': %2").arg (tl::to_qstring (p->first)).arg (tl::to_qstring (ex.msg ()))));
throw tl::Exception (tl::to_string (QObject::tr ("Error fetching spec file for package '%1': %2").arg (tl::to_qstring (p->name)).arg (tl::to_qstring (ex.msg ()))));
}
p->second.downloaded = true;
if (p->version.empty ()) {
p->version = p->grain.version ();
}
p->name = p->grain.name ();
p->downloaded = true;
if (SaltGrain::compare_versions (p->grain.version (), p->version) < 0) {
throw tl::Exception (tl::to_string (QObject::tr ("Package '%1': package in repository is too old (%2) to satisfy requirements (%3)").arg (tl::to_qstring (p->name)).arg (tl::to_qstring (p->grain.version ())).arg (tl::to_qstring (p->version))));
}
}
}
// remove those registered entries which don't need to be updated (we do this after download since now the
// names should be known when only the URL is given)
registry.clear ();
registry.swap (m_registry);
for (std::vector<Descriptor>::const_iterator p = registry.begin (); p != registry.end (); ++p) {
const SaltGrain *g = salt.grain_by_name (p->name);
if (g) {
if (SaltGrain::compare_versions (p->version, g->version ()) <= 0 && p->url == g->url ()) {
if (tl::verbosity() >= 20) {
tl::log << "Package already present with sufficient version - not installed again: " << p->name << " (" << p->version << ")";
}
} else {
if (tl::verbosity() >= 20) {
tl::log << "Considering package for upgrade or URL switch: " << p->name << ", from " << g->url () << "(" << g->version () << ") to " << p->url << "(" << p->version << ")";
}
m_registry.push_back (*p);
}
} else {
m_registry.push_back (*p);
}
}
}
lay::ConfirmationDialog *
@ -236,20 +328,22 @@ SaltDownloadManager::make_confirmation_dialog (QWidget *parent, const lay::Salt
{
lay::ConfirmationDialog *dialog = new lay::ConfirmationDialog (parent);
std::sort (m_registry.begin (), m_registry.end ());
// First the packages to update
for (std::map<std::string, Descriptor>::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) {
const lay::SaltGrain *g = salt.grain_by_name (p->first);
for (std::vector<Descriptor>::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) {
const lay::SaltGrain *g = salt.grain_by_name (p->name);
if (g) {
// \342\206\222 is UTF-8 "right arrow"
dialog->add_info (p->first, true, g->version () + " \342\206\222 " + p->second.version, p->second.url);
dialog->add_info (p->name, true, g->version () + " \342\206\222 " + p->version, p->url);
}
}
// Then the packages to install
for (std::map<std::string, Descriptor>::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) {
const lay::SaltGrain *g = salt.grain_by_name (p->first);
for (std::vector<Descriptor>::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) {
const lay::SaltGrain *g = salt.grain_by_name (p->name);
if (!g) {
dialog->add_info (p->first, false, p->second.version, p->second.url);
dialog->add_info (p->name, false, p->version, p->url);
}
}
@ -259,52 +353,78 @@ SaltDownloadManager::make_confirmation_dialog (QWidget *parent, const lay::Salt
bool
SaltDownloadManager::execute (QWidget *parent, lay::Salt &salt)
{
// Stop with a warning if there is nothing to do
if (m_registry.empty()) {
QMessageBox::warning (parent, tr ("Nothing to do"), tr ("No packages need update or are marked for installation"));
return true;
}
std::auto_ptr<lay::ConfirmationDialog> dialog (make_confirmation_dialog (parent, salt));
dialog->setModal (true);
dialog->show ();
while (! dialog->is_confirmed ()) {
QCoreApplication::processEvents (QEventLoop::AllEvents | QEventLoop::WaitForMoreEvents, 100);
if (dialog->is_cancelled () || ! dialog->isVisible ()) {
return false;
}
}
dialog->start ();
bool result = true;
for (std::map<std::string, Descriptor>::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) {
if (parent) {
lay::SaltGrain target;
target.set_name (p->first);
lay::SaltGrain *g = salt.grain_by_name (p->first);
if (g) {
target.set_path (g->path ());
// Stop with a warning if there is nothing to do
if (m_registry.empty()) {
QMessageBox::warning (parent, tr ("Nothing to do"), tr ("No packages need update or are marked for installation"));
return true;
}
if (! salt.create_grain (p->second.grain, target)) {
dialog->mark_error (p->first);
result = false;
} else {
dialog->mark_success (p->first);
std::auto_ptr<lay::ConfirmationDialog> dialog (make_confirmation_dialog (parent, salt));
dialog->setModal (true);
dialog->show ();
while (! dialog->is_confirmed ()) {
QCoreApplication::processEvents (QEventLoop::AllEvents | QEventLoop::WaitForMoreEvents, 100);
if (dialog->is_cancelled () || ! dialog->isVisible ()) {
return false;
}
}
dialog->separator ();
dialog->start ();
}
std::sort (m_registry.begin (), m_registry.end ());
dialog->finish ();
for (std::vector<Descriptor>::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) {
lay::SaltGrain target;
target.set_name (p->name);
lay::SaltGrain *g = salt.grain_by_name (p->name);
if (g) {
target.set_path (g->path ());
}
if (! salt.create_grain (p->grain, target)) {
dialog->mark_error (p->name);
result = false;
} else {
dialog->mark_success (p->name);
}
dialog->separator ();
}
dialog->finish ();
while (! dialog->is_closed () && dialog->isVisible ()) {
QCoreApplication::processEvents (QEventLoop::AllEvents | QEventLoop::WaitForMoreEvents, 100);
}
} else {
for (std::vector<Descriptor>::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) {
lay::SaltGrain target;
target.set_name (p->name);
lay::SaltGrain *g = salt.grain_by_name (p->name);
if (g) {
target.set_path (g->path ());
}
if (! salt.create_grain (p->grain, target)) {
tl::error << tl::to_string (QObject::tr ("Installation failed for package %1").arg (tl::to_qstring (target.name ())));
result = false;
} else {
tl::log << tl::to_string (QObject::tr ("Package %1 installed successfully").arg (tl::to_qstring (target.name ())));
}
}
while (! dialog->is_closed () && dialog->isVisible ()) {
QCoreApplication::processEvents (QEventLoop::AllEvents | QEventLoop::WaitForMoreEvents, 100);
}
return result;

View File

@ -110,35 +110,67 @@ public:
*/
void compute_dependencies (const lay::Salt &salt, const Salt &salt_mine);
/**
* @brief Computes the list of packages after all required packages have been registered
*
* This method will removes packages that are already installed and satisfy the version requirements.
* Packages not present in the list of packages ("salt" argument), will be scheduled for download too.
* No dependencies are checked in this version.
*/
void compute_packages (const lay::Salt &salt, const Salt &salt_mine);
/**
* @brief Actually execute the downloads
*
* This method will show a confirmation dialog and start installation
* if this dialog is confirmed. It will return false if
* the dialog was cancelled and an exception if something goes
* wrong.
* It will return true if the packages were installed successfully.
* If parent is non-null, this method will show a confirmation dialog and start installation
* if this dialog is confirmed. It will return false if the dialog was cancelled and an exception
* if something goes wrong.
*
* If parent is null, no confirmation dialog will be shown and installation happens in non-GUI
* mode.
*
* The return value will be true if the packages were installed successfully.
*/
bool execute (QWidget *parent, lay::Salt &salt);
private:
struct Descriptor
{
Descriptor (const std::string &_url, const std::string &_version)
: url (_url), version (_version), downloaded (false)
Descriptor (const std::string &_name, const std::string &_url, const std::string &_version)
: name (_name), url (_url), version (_version), downloaded (false)
{ }
bool operator< (const Descriptor &other) const
{
if (name != other.name) {
return name < other.name;
} else {
return lay::SaltGrain::compare_versions (version, other.version) < 0;
}
}
bool operator== (const Descriptor &other) const
{
if (name != other.name) {
return false;
} else {
return lay::SaltGrain::compare_versions (version, other.version) == 0;
}
}
std::string name;
std::string url;
std::string version;
bool downloaded;
lay::SaltGrain grain;
};
std::map<std::string, Descriptor> m_registry;
std::vector<Descriptor> m_registry;
bool needs_iteration ();
void fetch_missing (const lay::Salt &salt_mine, tl::AbsoluteProgress &progress);
void fetch_missing (const lay::Salt &salt, const lay::Salt &salt_mine, tl::AbsoluteProgress &progress);
lay::ConfirmationDialog *make_confirmation_dialog (QWidget *parent, const lay::Salt &salt);
void compute_list (const lay::Salt &salt, const lay::Salt &salt_mine, bool with_dep);
};
}

View File

@ -21,6 +21,7 @@
*/
#include "laySaltGrain.h"
#include "laySaltController.h"
#include "tlString.h"
#include "tlXMLParser.h"
#include "tlHttpStream.h"
@ -259,6 +260,11 @@ SaltGrain::valid_name (const std::string &n)
tl::Extractor ex (n);
// a package name must not start with a dot.
if (ex.test (".")) {
return false;
}
std::string s;
if (! ex.try_read_word (s, "_.")) {
return false;
@ -269,6 +275,10 @@ SaltGrain::valid_name (const std::string &n)
if (! ex.test ("/")) {
return false;
}
// a prefix must not start with a dot.
if (ex.test (".")) {
return false;
}
if (! ex.try_read_word (s, "_.")) {
return false;
}
@ -464,6 +474,11 @@ SaltGrain::from_url (const std::string &url)
std::auto_ptr<tl::InputStream> stream;
std::string spec_url = SaltGrain::spec_url (url);
// base relative URL's on the salt mine URL
if (spec_url.find ("http:") != 0 && spec_url.find ("https:") != 0 && spec_url.find ("file:") != 0 && !spec_url.empty() && spec_url[0] != '/' && spec_url[0] != '\\' && lay::SaltController::instance ()) {
spec_url = lay::SaltController::instance ()->salt_mine_url () + "/" + spec_url;
}
if (spec_url.find ("http:") == 0 || spec_url.find ("https:") == 0) {
stream.reset (tl::WebDAVObject::download_item (spec_url));
} else {

View File

@ -511,7 +511,8 @@ BEGIN_PROTECTED
--i;
QModelIndex index = model->index (i, 0, QModelIndex ());
SaltGrain *g = model->grain_from_index (index);
if (g && model->is_marked (g->name ())) {
// NOTE: checking for valid_name prevents bad entries inside the download list
if (g && model->is_marked (g->name ()) && SaltGrain::valid_name (g->name ())) {
manager.register_download (g->name (), g->url (), g->version ());
any = true;
}

View File

@ -21,7 +21,7 @@
*/
#include "tlSystemPaths.h"
#include "laySystemPaths.h"
#include "tlString.h"
#include <QDir>
@ -36,12 +36,24 @@
#include <cstdlib>
namespace tl
namespace lay
{
std::string
get_appdata_path ()
{
#ifdef _WIN32
wchar_t *env = _wgetenv (L"KLAYOUT_HOME");
if (env) {
return tl::to_string (QString ((const QChar *) env));
}
#else
char *env = getenv ("KLAYOUT_HOME");
if (env) {
return (tl::system_to_string (env));
}
#endif
QDir appdata_dir = QDir::homePath ();
QString appdata_folder;
#ifdef _WIN32
@ -50,23 +62,7 @@ get_appdata_path ()
appdata_folder = QString::fromUtf8 (".klayout");
#endif
// create the basic folder hierarchy
if (! appdata_dir.exists (appdata_folder)) {
appdata_dir.mkdir (appdata_folder);
}
QString appdata_klayout_path = appdata_dir.absoluteFilePath (appdata_folder);
QDir appdata_klayout_dir (appdata_klayout_path);
const char *folders[] = { "macros", "drc", "libraries", "tech" };
for (size_t i = 0; i < sizeof (folders) / sizeof (folders [0]); ++i) {
QString folder = QString::fromUtf8 (folders [i]);
if (! appdata_klayout_dir.exists (folder)) {
appdata_klayout_dir.mkdir (folder);
}
}
return tl::to_string (appdata_klayout_path);
return tl::to_string (appdata_dir.absoluteFilePath (appdata_folder));
}
static std::string

View File

@ -21,55 +21,61 @@
*/
#ifndef HDR_tlSystemPaths
#define HDR_tlSystemPaths
#ifndef HDR_laySystemPaths
#define HDR_laySystemPaths
#include "tlCommon.h"
#include "layCommon.h"
#include <string>
#include <vector>
namespace tl
namespace lay
{
/**
* @brief Gets the application data path
* The application data path is the path where the application stores it's
* data for each user.
* By default this is HOME/.klayout or HOME/KLayout (Windows). The value
* can be overridden by the KLAYOUT_HOME environment variable.
*/
TL_PUBLIC std::string get_appdata_path ();
LAY_PUBLIC std::string get_appdata_path ();
/**
* @brief Gets the installation path
* The installation path is the path where the current application is installed.
*/
TL_PUBLIC std::string get_inst_path ();
LAY_PUBLIC std::string get_inst_path ();
/**
* @brief Gets the KLayout path
* This is a path (in the sense of a search path - i.e. multiple paths)
* where KLayout (and derived applications) looks for configuration files.
* The path is created from the appdata and inst path by default. It can
* be overridden by the KLAYOUT_PATH enviroment variable.
*/
TL_PUBLIC std::vector<std::string> get_klayout_path ();
LAY_PUBLIC std::vector<std::string> get_klayout_path ();
/**
* @brief Sets the KLayout path
* This method is mainly used for test purposes. It will force the application
* is use a specific KLAYOUT_PATH. Use reset_klayout_path to restore the
* to use a specific path. Use reset_klayout_path to restore the
* default behavior.
*/
TL_PUBLIC void set_klayout_path (const std::vector<std::string> &path);
LAY_PUBLIC void set_klayout_path (const std::vector<std::string> &path);
/**
* @brief Resets the KLayout path
* See "set_klayout_path" for a description.
*/
TL_PUBLIC void reset_klayout_path ();
LAY_PUBLIC void reset_klayout_path ();
/**
* @brief Gets the package manager URL
* The default value can be overridden by the KLAYOUT_SALT_MINE environment
* variable.
*/
TL_PUBLIC std::string salt_mine_url ();
LAY_PUBLIC std::string salt_mine_url ();
}

View File

@ -164,10 +164,14 @@ TEST (2)
EXPECT_EQ (lay::SaltGrain::valid_version ("1.2x"), false);
EXPECT_EQ (lay::SaltGrain::valid_name (""), false);
EXPECT_EQ (lay::SaltGrain::valid_name ("x"), true);
EXPECT_EQ (lay::SaltGrain::valid_name (".x"), false);
EXPECT_EQ (lay::SaltGrain::valid_name (".."), false);
EXPECT_EQ (lay::SaltGrain::valid_name ("x1"), true);
EXPECT_EQ (lay::SaltGrain::valid_name ("x1 "), false);
EXPECT_EQ (lay::SaltGrain::valid_name ("x$1"), false);
EXPECT_EQ (lay::SaltGrain::valid_name ("x/y"), true);
EXPECT_EQ (lay::SaltGrain::valid_name ("x/.y"), false);
EXPECT_EQ (lay::SaltGrain::valid_name ("x/.."), false);
EXPECT_EQ (lay::SaltGrain::valid_name ("x_y"), true);
EXPECT_EQ (lay::SaltGrain::compare_versions ("", ""), 0);
EXPECT_EQ (lay::SaltGrain::compare_versions ("1", "2"), -1);

View File

@ -33,7 +33,6 @@
#include "tlLog.h"
#include "tlTimer.h"
#include "tlExpression.h"
#include "tlSystemPaths.h"
#include "rba.h"
#include "rbaInspector.h"

View File

@ -9,35 +9,34 @@ DEFINES += MAKE_TL_LIBRARY
LIBS += -lz
FORMS = \
PasswordDialog.ui
PasswordDialog.ui
SOURCES = \
tlAssert.cc \
tlClassRegistry.cc \
tlDataMapping.cc \
tlDeferredExecution.cc \
tlDeflate.cc \
tlException.cc \
tlExceptions.cc \
tlExpression.cc \
tlEvents.cc \
tlGlobPattern.cc \
tlHeap.cc \
tlHttpStream.cc \
tlInternational.cc \
tlLog.cc \
tlObject.cc \
tlProgress.cc \
tlScriptError.cc \
tlStaticObjects.cc \
tlStream.cc \
tlString.cc \
tlSystemPaths.cc \
tlThreadedWorkers.cc \
tlTimer.cc \
tlVariant.cc \
tlXMLParser.cc \
tlXMLWriter.cc \
tlAssert.cc \
tlClassRegistry.cc \
tlDataMapping.cc \
tlDeferredExecution.cc \
tlDeflate.cc \
tlException.cc \
tlExceptions.cc \
tlExpression.cc \
tlEvents.cc \
tlGlobPattern.cc \
tlHeap.cc \
tlHttpStream.cc \
tlInternational.cc \
tlLog.cc \
tlObject.cc \
tlProgress.cc \
tlScriptError.cc \
tlStaticObjects.cc \
tlStream.cc \
tlString.cc \
tlThreadedWorkers.cc \
tlTimer.cc \
tlVariant.cc \
tlXMLParser.cc \
tlXMLWriter.cc \
tlFileSystemWatcher.cc \
tlFileUtils.cc \
tlWebDAV.cc \
@ -46,44 +45,43 @@ SOURCES = \
tlUnitTest.cc
HEADERS = \
tlAlgorithm.h \
tlAssert.h \
tlClassRegistry.h \
tlDataMapping.h \
tlDeferredExecution.h \
tlDeflate.h \
tlException.h \
tlExceptions.h \
tlExpression.h \
tlEvents.h \
tlFixedVector.h \
tlGlobPattern.h \
tlHeap.h \
tlHttpStream.h \
tlInternational.h \
tlIntervalMap.h \
tlIntervalSet.h \
tlKDTree.h \
tlLog.h \
tlObject.h \
tlObjectCollection.h \
tlProgress.h \
tlReuseVector.h \
tlScriptError.h \
tlStableVector.h \
tlStaticObjects.h \
tlStream.h \
tlString.h \
tlSystemPaths.h \
tlThreadedWorkers.h \
tlTimer.h \
tlTypeTraits.h \
tlUtils.h \
tlVariant.h \
tlVariantUserClasses.h \
tlVector.h \
tlXMLParser.h \
tlXMLWriter.h \
tlAlgorithm.h \
tlAssert.h \
tlClassRegistry.h \
tlDataMapping.h \
tlDeferredExecution.h \
tlDeflate.h \
tlException.h \
tlExceptions.h \
tlExpression.h \
tlEvents.h \
tlFixedVector.h \
tlGlobPattern.h \
tlHeap.h \
tlHttpStream.h \
tlInternational.h \
tlIntervalMap.h \
tlIntervalSet.h \
tlKDTree.h \
tlLog.h \
tlObject.h \
tlObjectCollection.h \
tlProgress.h \
tlReuseVector.h \
tlScriptError.h \
tlStableVector.h \
tlStaticObjects.h \
tlStream.h \
tlString.h \
tlThreadedWorkers.h \
tlTimer.h \
tlTypeTraits.h \
tlUtils.h \
tlVariant.h \
tlVariantUserClasses.h \
tlVector.h \
tlXMLParser.h \
tlXMLWriter.h \
tlFileSystemWatcher.h \
tlCommon.h \
tlMath.h \

View File

@ -59,14 +59,16 @@ class StreamIODevice
public:
StreamIODevice (tl::InputStream &stream)
: m_stream (stream),
mp_progress (0)
mp_progress (0),
m_has_error (false)
{
open (QIODevice::ReadOnly);
}
StreamIODevice (tl::InputStream &stream, const std::string &progress_message)
: m_stream (stream),
mp_progress (new AbsoluteProgress (progress_message, 100))
mp_progress (new AbsoluteProgress (progress_message, 100)),
m_has_error (false)
{
mp_progress->set_format (tl::to_string (QObject::tr ("%.0f MB")));
mp_progress->set_unit (1024 * 1024);
@ -106,16 +108,49 @@ public:
return n0 - n;
} catch (tl::Exception &) {
// TODO: is there another way of reporting errors? This reports an "unexpected EOF" when the "Cancel" button is pressed.
// However, throwing a simple tl::Exception does not pass through the Qt library.
return 0;
} catch (tl::Exception &ex) {
setErrorString (tl::to_qstring (ex.msg ()));
m_has_error = true;
return -1;
}
}
bool has_error () const
{
return m_has_error;
}
private:
tl::InputStream &m_stream;
tl::AbsoluteProgress *mp_progress;
bool m_has_error;
};
// --------------------------------------------------------------------
// ErrorAwareQXmlInputSource definition and implementation
class ErrorAwareQXmlInputSource
: public QXmlInputSource
{
public:
ErrorAwareQXmlInputSource (StreamIODevice *dev)
: QXmlInputSource (dev), mp_dev (dev)
{
// .. nothing yet ..
}
virtual void fetchData ()
{
QXmlInputSource::fetchData ();
// This feature is actually missing in the original implementation: throw an exception on error
if (mp_dev->has_error ()) {
throw tl::Exception (tl::to_string (mp_dev->errorString ()));
}
}
private:
StreamIODevice *mp_dev;
};
// --------------------------------------------------------------------
@ -124,15 +159,17 @@ private:
XMLFileSource::XMLFileSource (const std::string &path, const std::string &progress_message)
: mp_source (0), mp_io (0), m_stream (path)
{
mp_io = new StreamIODevice (m_stream, progress_message);
mp_source = new QXmlInputSource (mp_io);
StreamIODevice *io = new StreamIODevice (m_stream, progress_message);
mp_io = io;
mp_source = new ErrorAwareQXmlInputSource (io);
}
XMLFileSource::XMLFileSource (const std::string &path)
: mp_source (0), mp_io (0), m_stream (path)
{
mp_io = new StreamIODevice (m_stream);
mp_source = new QXmlInputSource (mp_io);
StreamIODevice *io = new StreamIODevice (m_stream);
mp_io = io;
mp_source = new ErrorAwareQXmlInputSource (io);
}
XMLFileSource::~XMLFileSource ()
@ -148,14 +185,16 @@ XMLFileSource::~XMLFileSource ()
XMLStreamSource::XMLStreamSource (tl::InputStream &s, const std::string &progress_message)
{
mp_io = new StreamIODevice (s, progress_message);
mp_source = new QXmlInputSource (mp_io);
StreamIODevice *io = new StreamIODevice (s, progress_message);
mp_io = io;
mp_source = new ErrorAwareQXmlInputSource (io);
}
XMLStreamSource::XMLStreamSource (tl::InputStream &s)
{
mp_io = new StreamIODevice (s);
mp_source = new QXmlInputSource (mp_io);
StreamIODevice *io = new StreamIODevice (s);
mp_io = io;
mp_source = new ErrorAwareQXmlInputSource (io);
}
XMLStreamSource::~XMLStreamSource ()

View File

@ -27,9 +27,9 @@
#include "tlUnitTest.h"
#include "tlStaticObjects.h"
#include "tlTimer.h"
#include "tlSystemPaths.h"
#include "tlCommandLineParser.h"
#include "layApplication.h"
#include "laySystemPaths.h"
#include "rba.h"
#include "pya.h"
#include "gsiDecl.h"
@ -334,7 +334,7 @@ main_cont (int argc, char **argv)
QStringList name_filters;
name_filters << QString::fromUtf8 ("*.ut");
QDir inst_dir (tl::to_qstring (tl::get_inst_path ()));
QDir inst_dir (tl::to_qstring (lay::get_inst_path ()));
QStringList inst_modules = inst_dir.entryList (name_filters);
inst_modules.sort ();
@ -366,7 +366,7 @@ main_cont (int argc, char **argv)
}
// No side effects
tl::set_klayout_path (std::vector<std::string> ());
lay::set_klayout_path (std::vector<std::string> ());
int ac = 2;
static char av0[] = "unit_test";