Merge branch 'lib-file' into bugfix/issue-2305

This commit is contained in:
Matthias Koefferlein 2026-04-03 15:42:00 +02:00
commit ad850f3d83
32 changed files with 694 additions and 167 deletions

View File

@ -857,7 +857,7 @@ public:
void check_locked () const;
/**
* @brief Tell, if this cell is a proxy cell
* @brief Gets a value indicating if this cell is a proxy cell
*
* Proxy cells are such whose layout represents a snapshot of another entity.
* Such cells can be PCell variants or library references for example.
@ -867,6 +867,17 @@ public:
return false;
}
/**
* @brief Gets a value indicating that this cell is a replica that can be skipped
*
* This attribute is evaluated by file writers to skip cell replicas for
* library cells that do not want to replicated.
*/
virtual bool can_skip_replica () const
{
return false;
}
/**
* @brief Sets the cell name
*

View File

@ -692,6 +692,11 @@ public:
return false;
}
virtual bool supports_context () const
{
return false;
}
virtual tl::XMLElementBase *xml_reader_options_element () const
{
return new db::ReaderOptionsXMLElement<db::CommonReaderOptions> ("common",

View File

@ -33,14 +33,17 @@
namespace db
{
Library::Library()
: m_id (std::numeric_limits<lib_id_type>::max ()), m_layout (true)
Library::Library ()
: m_id (std::numeric_limits<lib_id_type>::max ()), m_layout (true), m_replicate (true)
{
m_layout.set_library (this);
}
Library::Library(const Library &d)
: gsi::ObjectBase (), tl::Object (), m_name (d.m_name), m_description (d.m_description), m_id (std::numeric_limits<lib_id_type>::max ()), m_layout (d.m_layout)
Library::Library (const Library &d)
: gsi::ObjectBase (), tl::Object (),
m_name (d.m_name), m_description (d.m_description),
m_id (std::numeric_limits<lib_id_type>::max ()), m_layout (d.m_layout),
m_replicate (d.m_replicate)
{
m_layout.set_library (this);
}
@ -65,6 +68,12 @@ Library::for_technologies () const
return ! m_technologies.empty ();
}
void
Library::set_technologies (const std::set<std::string> &t)
{
m_technologies = t;
}
void
Library::set_technology (const std::string &t)
{
@ -148,6 +157,12 @@ Library::is_retired (const db::cell_index_type library_cell_index) const
return (i != m_refcount.end () && j != m_retired_count.end () && i->second == j->second);
}
void
Library::set_replicate (bool f)
{
m_replicate = f;
}
void
Library::rename (const std::string &name)
{

View File

@ -53,12 +53,12 @@ public:
/**
* @brief The constructor
*/
Library();
Library ();
/**
* @brief Copy constructor
*/
Library(const Library &);
Library (const Library &);
/**
* @brief The destructor
@ -138,6 +138,13 @@ public:
*/
bool for_technologies () const;
/**
* @brief Sets the technology names this library is associated with
*
* This will reset the list of technologies to this set.
*/
void set_technologies (const std::set<std::string> &t);
/**
* @brief Sets the technology name this library is associated with
*
@ -172,6 +179,27 @@ public:
m_description = description;
}
/**
* @brief Sets a value indicating whether the library produces replicas
*
* If this value is true (the default), layout written will include the
* actual layout of a library cell (replica). With this, it is possible
* to regenerate the layout without actually having the library at the
* cost of additional bytes in the file.
*
* Setting this flag to false avoids this replication, but a layout
* cannot be regenerated without having this library.
*/
void set_replicate (bool f);
/**
* @brief Gets a value indicating whether the library produces replicas
*/
bool replicate () const
{
return m_replicate;
}
/**
* @brief Getter for the library Id property
*/
@ -263,6 +291,7 @@ private:
db::Layout m_layout;
std::map<db::Layout *, int> m_referrers;
std::map<db::cell_index_type, int> m_refcount, m_retired_count;
bool m_replicate;
// no copying.
Library &operator=(const Library &);

View File

@ -249,6 +249,13 @@ LibraryProxy::update (db::ImportLayerMapping *layer_mapping)
}
}
bool
LibraryProxy::can_skip_replica () const
{
const Library *lib = LibraryManager::instance ().lib (lib_id ());
return lib && ! lib->replicate ();
}
std::string
LibraryProxy::get_basic_name () const
{

View File

@ -93,6 +93,14 @@ public:
return true;
}
/**
* @brief Gets a value indicating that this cell is a replica that can be skipped
*
* This attribute is evaluated by file writers to skip cell replicas for
* library cells that do not want to replicated.
*/
virtual bool can_skip_replica () const;
/**
* @brief Gets the basic name
*

View File

@ -56,7 +56,7 @@ SaveLayoutOptions::operator= (const SaveLayoutOptions &d)
m_format = d.m_format;
m_layers = d.m_layers;
m_cells = d.m_cells;
m_implied_childred = d.m_implied_childred;
m_implied_children = d.m_implied_children;
m_all_layers = d.m_all_layers;
m_all_cells = d.m_all_cells;
m_dbu = d.m_dbu;
@ -189,7 +189,7 @@ SaveLayoutOptions::add_cell (db::cell_index_type cell_index)
{
m_all_cells = false;
m_cells.insert (cell_index);
m_implied_childred.insert (cell_index);
m_implied_children.insert (cell_index);
}
void
@ -204,7 +204,7 @@ SaveLayoutOptions::clear_cells ()
{
m_all_cells = false;
m_cells.clear ();
m_implied_childred.clear ();
m_implied_children.clear ();
}
void
@ -212,7 +212,7 @@ SaveLayoutOptions::select_all_cells ()
{
m_all_cells = true;
m_cells.clear ();
m_implied_childred.clear ();
m_implied_children.clear ();
}
void
@ -340,21 +340,75 @@ SaveLayoutOptions::get_valid_layers (const db::Layout &layout, std::vector <std:
}
}
static void
collect_called_cells_unskipped (db::cell_index_type ci, const db::Layout &layout, std::set<cell_index_type> &called)
{
const db::Cell &c = layout.cell (ci);
if (c.can_skip_replica ()) {
return;
}
for (auto cc = c.begin_child_cells (); ! cc.at_end (); ++cc) {
if (called.find (*cc) == called.end () && layout.is_valid_cell_index (*cc)) {
called.insert (*cc);
collect_called_cells_unskipped (*cc, layout, called);
}
}
}
void
SaveLayoutOptions::get_cells (const db::Layout &layout, std::set <db::cell_index_type> &cells, const std::vector <std::pair <unsigned int, db::LayerProperties> > &valid_layers, bool require_unique_names) const
{
bool has_context = m_write_context_info;
for (tl::Registrar<db::StreamFormatDeclaration>::iterator fmt = tl::Registrar<db::StreamFormatDeclaration>::begin (); fmt != tl::Registrar<db::StreamFormatDeclaration>::end (); ++fmt) {
if (fmt->format_name () == m_format) {
if (! fmt->supports_context ()) {
has_context = false;
}
break;
}
}
if (m_all_cells) {
for (db::Layout::const_iterator cell = layout.begin (); cell != layout.end (); ++cell) {
cells.insert (cell->cell_index ());
bool has_skipped_replica = false;
// check if we have skipped replicas
if (has_context) {
for (db::Layout::const_iterator cell = layout.begin (); cell != layout.end () && ! has_skipped_replica; ++cell) {
has_skipped_replica = cell->can_skip_replica ();
}
}
// with skipped replicas start again and skip child cells of such cells
// unless they are needed in other places.
if (has_skipped_replica) {
for (db::Layout::const_iterator cell = layout.begin (); cell != layout.end (); ++cell) {
if (cell->is_top ()) {
cells.insert (cell->cell_index ());
collect_called_cells_unskipped (cell->cell_index (), layout, cells);
}
}
} else {
for (db::Layout::const_iterator cell = layout.begin (); cell != layout.end () && ! has_skipped_replica; ++cell) {
cells.insert (cell->cell_index ());
}
}
} else {
for (std::set <db::cell_index_type>::const_iterator c = m_cells.begin (); c != m_cells.end (); ++c) {
cells.insert (*c);
if (m_implied_childred.find (*c) != m_implied_childred.end ()) {
layout.cell (*c).collect_called_cells (cells);
if (m_implied_children.find (*c) != m_implied_children.end ()) {
if (has_context) {
collect_called_cells_unskipped (*c, layout, cells);
} else {
layout.cell (*c).collect_called_cells (cells);
}
}
}

View File

@ -449,7 +449,7 @@ private:
std::string m_format;
std::map<unsigned int, db::LayerProperties> m_layers;
std::set<db::cell_index_type> m_cells;
std::set<db::cell_index_type> m_implied_childred;
std::set<db::cell_index_type> m_implied_children;
bool m_all_layers;
bool m_all_cells;
double m_dbu;

View File

@ -109,6 +109,11 @@ public:
*/
virtual bool can_write () const = 0;
/**
* @brief Returns true, when the format supports a context (e.g. PCell parameters, Library references)
*/
virtual bool supports_context () const = 0;
/**
* @brief Delivers the XMLElement object that represents the reader options within a technology XML tree
*

View File

@ -266,6 +266,26 @@ LibraryClass<db::Library> decl_Library ("db", "LibraryBase",
"\n"
"This method has been introduced in version 0.30.5."
) +
gsi::method ("replicate=", &db::Library::set_replicate, gsi::arg ("flag"),
"@brief Sets a value indicating whether the library produces replicas\n"
"\n"
"If this value is true (the default), layout written will include the\n"
"actual layout of a library cell (replica). With this, it is possible\n"
"to regenerate the layout without actually having the library at the\n"
"cost of additional bytes in the file.\n"
"\n"
"Setting this flag to false avoids this replication, but a layout\n"
"cannot be regenerated without having this library.\n"
"\n"
"This attribute has been introduced in version 0.30.8."
) +
gsi::method ("replicate", &db::Library::replicate,
"@brief Gets a value indicating whether the library produces replicas\n"
"\n"
"See \\replicate= for a description of this attribute.\n"
"\n"
"This attribute has been introduced in version 0.30.8."
) +
gsi::method ("rename", &db::Library::rename, gsi::arg ("name"),
"@brief Renames the library\n"
"\n"

View File

@ -33,6 +33,7 @@
#include "tlLog.h"
#include "tlStream.h"
#include "tlFileUtils.h"
#include "tlEnv.h"
#include <QDir>
@ -152,6 +153,45 @@ LibraryController::sync_files ()
}
}
// scan for library definition files
std::string lib_file = tl::get_env ("KLAYOUT_LIB");
std::vector <std::pair<std::string, std::string> > lib_files;
if (lib_file.empty ()) {
for (std::vector <std::pair<std::string, std::string> >::const_iterator p = paths.begin (); p != paths.end (); ++p) {
std::string lf = tl::combine_path (p->first, "klayout.lib");
if (tl::is_readable (lf)) {
lib_files.push_back (std::make_pair (lf, p->second));
}
}
} else if (tl::is_readable (lib_file)) {
lib_files.push_back (std::make_pair (lib_file, std::string ()));
}
for (auto lf = lib_files.begin (); lf != lib_files.end (); ++lf) {
tl::log << "Reading lib file '" << lf->first << "'";
try {
std::vector<LibFileInfo> libs;
read_lib_file (lf->first, lf->second, libs);
read_libs (libs, new_lib_files);
if (m_file_watcher) {
m_file_watcher->add_file (tl::absolute_file_path (lf->first));
}
} catch (tl::Exception &ex) {
tl::error << tl::to_string (tr ("Error reading lib file")) << " " << lf->first << ":" << tl::endl << ex.msg ();
} catch (...) {
}
}
// scan for libraries
for (std::vector <std::pair<std::string, std::string> >::const_iterator p = paths.begin (); p != paths.end (); ++p) {
@ -169,108 +209,20 @@ LibraryController::sync_files ()
name_filters << QString::fromUtf8 ("*");
// NOTE: this should return a list sorted by name
QStringList libs = lp.entryList (name_filters, QDir::Files);
QStringList entries = lp.entryList (name_filters, QDir::Files);
bool needs_load = false;
std::vector<LibFileInfo> libs;
libs.reserve (entries.size ());
for (QStringList::const_iterator im = libs.begin (); im != libs.end () && ! needs_load; ++im) {
std::string lib_path = tl::to_string (lp.absoluteFilePath (*im));
QFileInfo fi (tl::to_qstring (lib_path));
auto ll = m_lib_files.find (lib_path);
if (ll == m_lib_files.end ()) {
needs_load = true;
} else if (fi.lastModified () > ll->second.time) {
needs_load = true;
for (auto e = entries.begin (); e != entries.end (); ++e) {
libs.push_back (LibFileInfo ());
libs.back ().path = tl::to_string (lp.absoluteFilePath (*e));
if (! p->second.empty ()) {
libs.back ().tech.insert (p->second);
}
}
if (! needs_load) {
// If not reloading, register the existing files as known ones - this allows detecting if
// a file got removed.
for (QStringList::const_iterator im = libs.begin (); im != libs.end () && ! needs_load; ++im) {
std::string lib_path = tl::to_string (lp.absoluteFilePath (*im));
auto ll = m_lib_files.find (lib_path);
if (ll != m_lib_files.end ()) {
new_lib_files.insert (*ll);
}
}
} else {
std::map<std::string, db::FileBasedLibrary *> libs_by_name_here;
// Reload all files
for (QStringList::const_iterator im = libs.begin (); im != libs.end (); ++im) {
std::string lib_path = tl::to_string (lp.absoluteFilePath (*im));
QFileInfo fi (tl::to_qstring (lib_path));
try {
std::unique_ptr<db::FileBasedLibrary> lib (new db::FileBasedLibrary (lib_path));
if (! p->second.empty ()) {
lib->set_technology (p->second);
}
tl::log << "Reading library '" << lib_path << "'";
std::string libname = lib->load ();
// merge with existing lib if there is already one in this folder with the right name
auto il = libs_by_name_here.find (libname);
if (il != libs_by_name_here.end ()) {
tl::log << "Merging with other library file with the same name: " << libname;
il->second->merge_with_other_layout (lib_path);
// now, we can forget the new library as it is included in the first one
} else {
// otherwise register the new library
if (! p->second.empty ()) {
tl::log << "Registering as '" << libname << "' for tech '" << p->second << "'";
} else {
tl::log << "Registering as '" << libname << "'";
}
LibInfo li;
li.name = libname;
li.time = fi.lastModified ();
if (! p->second.empty ()) {
li.tech.insert (p->second);
}
new_lib_files.insert (std::make_pair (lib_path, li));
lib->set_name (libname);
libs_by_name_here.insert (std::make_pair (libname, lib.release ()));
}
} catch (tl::Exception &ex) {
tl::error << ex.msg ();
}
}
// Register the libs (NOTE: this needs to happen after the merge)
for (auto l = libs_by_name_here.begin (); l != libs_by_name_here.end (); ++l) {
try {
db::LibraryManager::instance ().register_lib (l->second);
} catch (tl::Exception &ex) {
tl::error << ex.msg ();
} catch (...) {
}
}
}
read_libs (libs, new_lib_files);
}
@ -320,6 +272,255 @@ LibraryController::sync_files ()
m_lib_files = new_lib_files;
}
namespace
{
struct LibFileFunctionContext
{
std::string lib_file;
std::string tech;
std::vector<LibraryController::LibFileInfo> *lib_files;
};
static void do_read_lib_file (LibFileFunctionContext &fc);
class DefineFunction
: public tl::EvalFunction
{
public:
DefineFunction (LibFileFunctionContext *fc)
: mp_fc (fc)
{ }
virtual bool supports_keyword_parameters () const { return true; }
virtual void execute (const tl::ExpressionParserContext &context, tl::Variant & /*out*/, const std::vector<tl::Variant> &args, const std::map<std::string, tl::Variant> *kwargs) const
{
if (args.size () < 1 || args.size () > 2) {
throw tl::EvalError (tl::to_string (tr ("'define' function needs one or two arguments (a path or a name and path)")), context);
}
std::string lf, name;
if (args.size () == 1) {
lf = args[0].to_string ();
} else {
name = args[0].to_string ();
lf = args[1].to_string ();
}
LibraryController::LibFileInfo fi;
fi.name = name;
fi.path = tl::is_absolute (lf) ? lf : tl::combine_path (tl::absolute_path (mp_fc->lib_file), lf);
if (! mp_fc->tech.empty ()) {
fi.tech.insert (mp_fc->tech);
}
if (kwargs) {
for (auto k = kwargs->begin (); k != kwargs->end (); ++k) {
static const std::string replicate_key ("replicate");
static const std::string technology_key ("technology");
static const std::string technologies_key ("technologies");
if (k->first == replicate_key) {
fi.replicate = k->second.to_bool ();
} else if (k->first == technology_key) {
fi.tech.clear ();
std::string tn = k->second.to_string ();
if (! tn.empty () && tn != "*") {
fi.tech.insert (tn);
}
} else if (k->first == technologies_key) {
fi.tech.clear ();
if (k->second.is_list ()) {
for (auto t = k->second.begin (); t != k->second.end (); ++t) {
fi.tech.insert (t->to_string ());
}
}
} else {
throw tl::EvalError (tl::sprintf (tl::to_string (tr ("Unknown keyword argument '%s' for 'define' function - the only allowed keyword arguments are 'replicate', 'technology' and 'technologies")), k->first), context);
}
}
}
mp_fc->lib_files->push_back (fi);
}
private:
LibFileFunctionContext *mp_fc;
};
class IncludeFunction
: public tl::EvalFunction
{
public:
IncludeFunction (LibFileFunctionContext *fc)
: mp_fc (fc)
{ }
virtual void execute (const tl::ExpressionParserContext &context, tl::Variant & /*out*/, const std::vector<tl::Variant> &args, const std::map<std::string, tl::Variant> * /*kwargs*/) const
{
if (args.size () != 1) {
throw tl::EvalError (tl::to_string (tr ("'include' function needs exactly one argument (the include file path)")), context);
}
std::string lf = args[0].to_string ();
LibFileFunctionContext fc = *mp_fc;
fc.lib_file = tl::is_absolute (lf) ? lf : tl::combine_path (tl::absolute_path (mp_fc->lib_file), lf);
do_read_lib_file (fc);
}
private:
LibFileFunctionContext *mp_fc;
};
static void
do_read_lib_file (LibFileFunctionContext &fc)
{
tl::Eval eval;
eval.define_function ("define", new DefineFunction (&fc));
eval.define_function ("include", new IncludeFunction (&fc));
eval.set_var ("file", fc.lib_file);
eval.set_var ("tech", fc.tech);
tl::InputStream is (fc.lib_file);
std::string lib_file = tl::TextInputStream (is).read_all ();
tl::Extractor ex (lib_file.c_str ());
eval.parse (ex).execute ();
}
}
void
LibraryController::read_lib_file (const std::string &lib_file, const std::string &tech, std::vector<LibraryController::LibFileInfo> &file_info)
{
LibFileFunctionContext fc;
fc.tech = tech;
fc.lib_file = tl::absolute_file_path (lib_file);
fc.lib_files = &file_info;
do_read_lib_file (fc);
}
void
LibraryController::read_libs (const std::vector<LibraryController::LibFileInfo> &libs, std::map<std::string, LibraryController::LibInfo> &new_lib_files)
{
bool needs_load = false;
for (auto im = libs.begin (); im != libs.end () && ! needs_load; ++im) {
const std::string &lib_path = im->path;
QFileInfo fi (tl::to_qstring (lib_path));
auto ll = m_lib_files.find (lib_path);
if (ll == m_lib_files.end ()) {
needs_load = true;
} else if (fi.lastModified () > ll->second.time || im->tech != ll->second.tech || im->replicate != ll->second.replicate) {
needs_load = true;
}
}
if (! needs_load) {
// If not reloading, register the existing files as known ones - this allows detecting if
// a file got removed.
for (auto im = libs.begin (); im != libs.end () && ! needs_load; ++im) {
const std::string &lib_path = im->path;
auto ll = m_lib_files.find (lib_path);
if (ll != m_lib_files.end ()) {
new_lib_files.insert (*ll);
}
}
} else {
std::map<std::string, db::FileBasedLibrary *> libs_by_name_here;
// Reload all files
for (auto im = libs.begin (); im != libs.end (); ++im) {
const std::string &lib_path = im->path;
QFileInfo fi (tl::to_qstring (lib_path));
try {
std::unique_ptr<db::FileBasedLibrary> lib (new db::FileBasedLibrary (lib_path, im->name));
lib->set_technologies (im->tech);
lib->set_replicate (im->replicate);
tl::log << "Reading library '" << lib_path << "'";
std::string libname = lib->load ();
// merge with existing lib if there is already one in this folder with the right name
auto il = libs_by_name_here.find (libname);
if (il != libs_by_name_here.end ()) {
tl::log << "Merging with other library file with the same name: " << libname;
il->second->merge_with_other_layout (lib_path);
// now, we can forget the new library as it is included in the first one
} else {
// otherwise register the new library
if (! im->tech.empty ()) {
tl::log << "Registering as '" << libname << "' for tech '" << tl::join (im->tech.begin (), im->tech.end (), ",") << "'";
} else {
tl::log << "Registering as '" << libname << "'";
}
LibInfo li;
li.name = libname;
li.time = fi.lastModified ();
li.tech = im->tech;
li.replicate = im->replicate;
new_lib_files.insert (std::make_pair (lib_path, li));
lib->set_name (libname);
libs_by_name_here.insert (std::make_pair (libname, lib.release ()));
}
} catch (tl::Exception &ex) {
tl::error << ex.msg ();
}
}
// Register the libs (NOTE: this needs to happen after the merge)
for (auto l = libs_by_name_here.begin (); l != libs_by_name_here.end (); ++l) {
try {
db::LibraryManager::instance ().register_lib (l->second);
} catch (tl::Exception &ex) {
tl::error << ex.msg ();
} catch (...) {
}
}
}
}
void
LibraryController::sync_with_external_sources ()
{

View File

@ -51,12 +51,21 @@ class MainWindow;
* By making the controller a PluginDeclaration it will receive
* initialization and configuration calls.
*/
class LibraryController
class LAY_PUBLIC LibraryController
: public lay::PluginDeclaration, public tl::Object
{
Q_OBJECT
public:
struct LibFileInfo
{
LibFileInfo () : name (), path (), replicate (true) { }
std::string name;
std::string path;
std::set<std::string> tech;
bool replicate;
};
/**
* @brief Default constructor
*/
@ -107,6 +116,11 @@ public:
*/
static LibraryController *instance ();
/**
* @brief Provided for test purposes
*/
static void read_lib_file (const std::string &lib_file, const std::string &tech, std::vector<LibFileInfo> &file_info);
private slots:
/**
* @brief Called when the file watcher detects a change in the file system
@ -121,10 +135,11 @@ private slots:
private:
struct LibInfo
{
LibInfo () : name (), time (), tech () { }
LibInfo () : name (), time (), tech (), replicate (true) { }
std::string name;
QDateTime time;
std::set<std::string> tech;
bool replicate;
};
tl::FileSystemWatcher *m_file_watcher;
@ -132,6 +147,7 @@ private:
std::map<std::string, LibInfo> m_lib_files;
void sync_files ();
void read_libs (const std::vector<LibFileInfo> &file_info, std::map<std::string, LibInfo> &new_lib_files);
};
}

View File

@ -0,0 +1,76 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2026 Matthias Koefferlein
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 2 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, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "layLibraryController.h"
#include "tlUnitTest.h"
#include "tlFileUtils.h"
TEST (1)
{
std::string lib_file = tl::testdata () + "/lay/a.libdef";
std::vector<lay::LibraryController::LibFileInfo> file_info;
lay::LibraryController::read_lib_file (lib_file, "T1", file_info);
tl_assert (file_info.size () == size_t (3));
EXPECT_EQ (file_info[0].name, "");
EXPECT_EQ (file_info[0].path, tl::combine_path (tl::absolute_path (lib_file), "noname.gds"));
EXPECT_EQ (file_info[0].replicate, true);
EXPECT_EQ (tl::join (file_info[0].tech.begin (), file_info[0].tech.end (), ","), "T1");
EXPECT_EQ (file_info[1].name, "L2");
EXPECT_EQ (file_info[1].path, tl::absolute_file_path (lib_file) + ".zzz");
EXPECT_EQ (file_info[1].replicate, true);
EXPECT_EQ (file_info[1].tech.size (), size_t (0));
EXPECT_EQ (file_info[2].name, "L3");
EXPECT_EQ (file_info[2].path, tl::combine_path (tl::absolute_path (lib_file), "subdir/l3.gds"));
EXPECT_EQ (file_info[2].replicate, false);
EXPECT_EQ (tl::join (file_info[2].tech.begin (), file_info[2].tech.end (), ","), "T2,T3");
}
TEST(2)
{
std::string lib_file = tl::testdata () + "/lay/b.libdef";
std::vector<lay::LibraryController::LibFileInfo> file_info;
lay::LibraryController::read_lib_file (lib_file, "TX", file_info);
tl_assert (file_info.size () == size_t (5));
EXPECT_EQ (file_info[0].name, "L0");
EXPECT_EQ (file_info[0].path, tl::combine_path (tl::absolute_path (lib_file), "l0.gds"));
EXPECT_EQ (file_info[0].replicate, true);
EXPECT_EQ (file_info[0].tech.size (), size_t (0));
EXPECT_EQ (file_info[1].name, "");
EXPECT_EQ (file_info[1].path, tl::combine_path (tl::absolute_path (lib_file), "noname.gds"));
EXPECT_EQ (file_info[1].replicate, true);
EXPECT_EQ (tl::join (file_info[1].tech.begin (), file_info[1].tech.end (), ","), "TX");
EXPECT_EQ (file_info[4].name, "L4");
EXPECT_EQ (file_info[4].path, tl::combine_path (tl::absolute_path (lib_file), "l4.gds"));
EXPECT_EQ (file_info[4].replicate, true);
EXPECT_EQ (tl::join (file_info[4].tech.begin (), file_info[4].tech.end (), ","), "TX");
}

View File

@ -7,6 +7,7 @@ TARGET = lay_tests
include($$PWD/../../lib_ut.pri)
SOURCES = \
layLibraryControllerTests.cc \
laySalt.cc \
layHelpIndexTest.cc \
laySaltParsedURLTests.cc \

View File

@ -173,6 +173,11 @@ public:
return true;
}
virtual bool supports_context () const
{
return false;
}
virtual tl::XMLElementBase *xml_reader_options_element () const
{
return new db::ReaderOptionsXMLElement<db::CIFReaderOptions> ("cif",

View File

@ -140,6 +140,11 @@ public:
return true;
}
virtual bool supports_context () const
{
return false;
}
virtual tl::XMLElementBase *xml_reader_options_element () const
{
return new db::ReaderOptionsXMLElement<db::DXFReaderOptions> ("dxf",

View File

@ -89,6 +89,11 @@ class GDS2TextFormatDeclaration
{
return true;
}
virtual bool supports_context () const
{
return true;
}
};
static tl::RegisteredClass<db::StreamFormatDeclaration> format_txt_decl (new GDS2TextFormatDeclaration(), 1, "GDS2Text");

View File

@ -66,6 +66,11 @@ class GDS2FormatDeclaration
return true;
}
virtual bool supports_context () const
{
return true;
}
virtual tl::XMLElementBase *xml_writer_options_element () const
{
return new db::WriterOptionsXMLElement<db::GDS2WriterOptions> ("gds2",

View File

@ -338,7 +338,7 @@ GDS2WriterBase::write_shape (const db::Layout &layout, int layer, int datatype,
}
void
GDS2WriterBase::write_cell (db::Layout &layout, const db::Cell &cref, const std::vector <std::pair <unsigned int, db::LayerProperties> > &layers, const std::set<db::cell_index_type> &cell_set, double sf, short *time_data)
GDS2WriterBase::write_cell (db::Layout &layout, const db::Cell &cref, const std::vector <std::pair <unsigned int, db::LayerProperties> > &layers, const std::set<db::cell_index_type> &cell_set, double sf, short *time_data, bool skip_body)
{
// cell header
@ -363,51 +363,56 @@ GDS2WriterBase::write_cell (db::Layout &layout, const db::Cell &cref, const std:
}
}
// instances
// skip instances and shapes for replicas
if (! skip_body) {
for (db::Cell::const_iterator inst = cref.begin (); ! inst.at_end (); ++inst) {
// instances
// write only instances to selected cells
if (m_keep_instances || cell_set.find (inst->cell_index ()) != cell_set.end ()) {
for (db::Cell::const_iterator inst = cref.begin (); ! inst.at_end (); ++inst) {
// write only instances to selected cells
if (m_keep_instances || cell_set.find (inst->cell_index ()) != cell_set.end ()) {
progress_checkpoint ();
try {
write_inst (sf, *inst, true /*normalize*/, m_resolve_skew_arrays, layout, inst->prop_id ());
} catch (tl::Exception &ex) {
throw tl::Exception (ex.msg () + tl::to_string (tr (", writing instances")));
}
progress_checkpoint ();
try {
write_inst (sf, *inst, true /*normalize*/, m_resolve_skew_arrays, layout, inst->prop_id ());
} catch (tl::Exception &ex) {
throw tl::Exception (ex.msg () + tl::to_string (tr (", writing instances")));
}
}
}
// shapes
// shapes
for (std::vector <std::pair <unsigned int, db::LayerProperties> >::const_iterator l = layers.begin (); l != layers.end (); ++l) {
for (std::vector <std::pair <unsigned int, db::LayerProperties> >::const_iterator l = layers.begin (); l != layers.end (); ++l) {
if (layout.is_valid_layer (l->first) && l->second.layer >= 0 && l->second.datatype >= 0) {
if (layout.is_valid_layer (l->first) && l->second.layer >= 0 && l->second.datatype >= 0) {
int layer = l->second.layer;
if (layer > std::numeric_limits<uint16_t>::max ()) {
throw tl::Exception (tl::sprintf (tl::to_string (tr ("Cannot write layer numbers larger than %d to GDS2 streams")), int (std::numeric_limits<uint16_t>::max ())));
}
int datatype = l->second.datatype;
if (datatype > std::numeric_limits<uint16_t>::max ()) {
throw tl::Exception (tl::sprintf (tl::to_string (tr ("Cannot write datatype numbers larger than %d to GDS2 streams")), int (std::numeric_limits<uint16_t>::max ())));
}
db::ShapeIterator shape (cref.shapes (l->first).begin (db::ShapeIterator::Boxes | db::ShapeIterator::Polygons | db::ShapeIterator::Edges | db::ShapeIterator::EdgePairs | db::ShapeIterator::Paths | db::ShapeIterator::Texts));
while (! shape.at_end ()) {
progress_checkpoint ();
try {
write_shape (layout, layer, datatype, *shape, sf);
} catch (tl::Exception &ex) {
throw tl::Exception (ex.msg () + tl::sprintf (tl::to_string (tr (", writing layer %d/%d")), layer, datatype));
int layer = l->second.layer;
if (layer > std::numeric_limits<uint16_t>::max ()) {
throw tl::Exception (tl::sprintf (tl::to_string (tr ("Cannot write layer numbers larger than %d to GDS2 streams")), int (std::numeric_limits<uint16_t>::max ())));
}
int datatype = l->second.datatype;
if (datatype > std::numeric_limits<uint16_t>::max ()) {
throw tl::Exception (tl::sprintf (tl::to_string (tr ("Cannot write datatype numbers larger than %d to GDS2 streams")), int (std::numeric_limits<uint16_t>::max ())));
}
++shape;
db::ShapeIterator shape (cref.shapes (l->first).begin (db::ShapeIterator::Boxes | db::ShapeIterator::Polygons | db::ShapeIterator::Edges | db::ShapeIterator::EdgePairs | db::ShapeIterator::Paths | db::ShapeIterator::Texts));
while (! shape.at_end ()) {
progress_checkpoint ();
try {
write_shape (layout, layer, datatype, *shape, sf);
} catch (tl::Exception &ex) {
throw tl::Exception (ex.msg () + tl::sprintf (tl::to_string (tr (", writing layer %d/%d")), layer, datatype));
}
++shape;
}
}
@ -580,7 +585,8 @@ GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::S
if (! cref.is_real_ghost_cell () && (! cref.is_proxy () || ! cref.is_top ())) {
try {
write_cell (layout, cref, layers, cell_set, sf, time_data);
bool skip_body = options.write_context_info () && cref.can_skip_replica ();
write_cell (layout, cref, layers, cell_set, sf, time_data, skip_body);
} catch (tl::Exception &ex) {
throw tl::Exception (ex.msg () + tl::sprintf (tl::to_string (tr (", writing cell '%s'")), layout.cell_name (*cell)));
}

View File

@ -181,7 +181,7 @@ private:
void write_context_cell (db::Layout &layout, const short *time_data, const std::vector<cell_index_type> &cells);
void write_context_string (size_t n, const std::string &s);
void write_cell (db::Layout &layout, const db::Cell &cref, const std::vector <std::pair <unsigned int, db::LayerProperties> > &layers,
const std::set <db::cell_index_type> &cell_set, double sf, short *time_data);
const std::set <db::cell_index_type> &cell_set, double sf, short *time_data, bool skip_body);
void write_shape (const db::Layout &layout, int layer, int datatype, const db::Shape &shape, double sf);
};

View File

@ -310,6 +310,11 @@ class LEFDEFFormatDeclaration
return false;
}
virtual bool supports_context () const
{
return false;
}
virtual tl::XMLElementBase *xml_reader_options_element () const
{
return new db::ReaderOptionsXMLElement<LEFDEFReaderOptions> ("lefdef",

View File

@ -105,6 +105,14 @@ class LStreamFormatDeclaration
return true;
}
/**
* @brief Returns a value indicating whether context information is supported
*/
virtual bool supports_context () const
{
return true;
}
virtual tl::XMLElementBase *xml_writer_options_element () const
{
return new db::WriterOptionsXMLElement<lstr::WriterOptions> ("lstream",

View File

@ -150,7 +150,8 @@ Writer::write (db::Layout &layout, tl::OutputStream &stream, const db::SaveLayou
for (auto c = mp_layout->begin_top_down (); c != mp_layout->end_top_down (); ++c) {
if (m_cells_to_write.find (*c) != m_cells_to_write.end ()) {
m_cellname = layout.cell_name (*c);
write_cell (*c, kj_stream);
bool skip_body = options.write_context_info () && mp_layout->cell (*c).can_skip_replica ();
write_cell (*c, kj_stream, skip_body);
m_cellname.clear ();
}
}
@ -911,9 +912,9 @@ Writer::make_meta_data (const db::Cell *cell, stream::metaData::MetaData::Builde
* This method generates a single-view cell message (the view is only a layout view).
*/
void
Writer::write_cell (db::cell_index_type ci, kj::BufferedOutputStream &os)
Writer::write_cell (db::cell_index_type ci, kj::BufferedOutputStream &os, bool skip_body)
{
bool needs_layout_view = ! mp_layout->cell (ci).is_real_ghost_cell ();
bool needs_layout_view = ! mp_layout->cell (ci).is_real_ghost_cell () && ! skip_body;
bool needs_meta_data_view = mp_layout->begin_meta (ci) != mp_layout->end_meta (ci);
capnp::MallocMessageBuilder message;

View File

@ -114,7 +114,7 @@ private:
void make_cell_hierarchy_tree (stream::library::CellHierarchyTree::Builder cell_tree);
void make_meta_data (const db::Cell *cell, stream::metaData::MetaData::Builder meta_data);
void write_cell (db::cell_index_type ci, kj::BufferedOutputStream &os);
void write_cell (db::cell_index_type ci, kj::BufferedOutputStream &os, bool skip_body);
void write_layout_view (db::cell_index_type ci, kj::BufferedOutputStream &os);
void write_meta_data_view (db::cell_index_type ci, kj::BufferedOutputStream &os);
void make_repetition (const RegularArray &array, stream::repetition::Repetition::Builder builder);

View File

@ -81,6 +81,11 @@ public:
return true;
}
virtual bool supports_context () const
{
return false;
}
virtual tl::XMLElementBase *xml_reader_options_element () const
{
return new db::ReaderOptionsXMLElement<db::MAGReaderOptions> ("mag",

View File

@ -161,6 +161,11 @@ public:
return false;
}
virtual bool supports_context () const
{
return false;
}
virtual tl::XMLElementBase *xml_reader_options_element () const
{
return new db::ReaderOptionsXMLElement<db::MALYReaderOptions> ("maly",

View File

@ -464,6 +464,11 @@ public:
return true;
}
virtual bool supports_context () const
{
return true;
}
virtual tl::XMLElementBase *xml_writer_options_element () const
{
return new db::WriterOptionsXMLElement<db::OASISWriterOptions> ("oasis",

View File

@ -1727,18 +1727,23 @@ OASISWriter::write (db::Layout &layout, tl::OutputStream &stream, const db::Save
write_props (cref.prop_id ());
}
// instances
if (cref.cell_instances () > 0) {
write_insts (cell_set);
}
bool skip_body = options.write_context_info () && cref.can_skip_replica ();
if (! skip_body) {
// shapes
for (std::vector <std::pair <unsigned int, db::LayerProperties> >::const_iterator l = layers.begin (); l != layers.end (); ++l) {
const db::Shapes &shapes = cref.shapes (l->first);
if (! shapes.empty ()) {
write_shapes (l->second, shapes);
m_progress.set (mp_stream->pos ());
// instances
if (cref.cell_instances () > 0) {
write_insts (cell_set);
}
// shapes
for (std::vector <std::pair <unsigned int, db::LayerProperties> >::const_iterator l = layers.begin (); l != layers.end (); ++l) {
const db::Shapes &shapes = cref.shapes (l->first);
if (! shapes.empty ()) {
write_shapes (l->second, shapes);
m_progress.set (mp_stream->pos ());
}
}
}
// end CBLOCK if required

View File

@ -1206,6 +1206,11 @@ class GerberFormatDeclaration
{
return false;
}
virtual bool supports_context () const
{
return false;
}
};
static tl::RegisteredClass<db::StreamFormatDeclaration> format_decl (new GerberFormatDeclaration (), 1000, "GerberPCB");

View File

@ -4108,8 +4108,6 @@ Eval::parse (Expression &expr, const std::string &s, bool top)
void
Eval::parse (Expression &expr, tl::Extractor &ex, bool top)
{
ex.skip ();
expr = Expression (this, ex.get ());
tl::Extractor ex0 = ex;

6
testdata/lay/a.libdef vendored Normal file
View File

@ -0,0 +1,6 @@
# A comment
define("noname.gds");
define("L2", file + ".zzz", technology = "");
define("L3", "subdir/l3.gds", technologies = [ "T2", "T3" ], replicate = false);

10
testdata/lay/b.libdef vendored Normal file
View File

@ -0,0 +1,10 @@
define("L0", "l0.gds", technology = "*");
# relative to this file
include("../lay/a.libdef");
var n4 = "L4";
var f4 = "l4.gds";
define(n4, f4);