23#include <system_error>
31#include <wx/filename.h>
60 wxString
Explain(
const wxString& aMessage )
const
68 return aMessage + wxT(
"\n\n" ) +
detail;
78 if( aValue.find(
'\0' ) != std::string::npos )
83 if( aValue.find(
'"' ) == std::string::npos )
86 aQuoted =
'"' + aValue +
'"';
91 if( !aValue.empty() && aValue.front() !=
'"' && aValue.find_first_of(
"() \t\n\r" ) == std::string::npos )
103 for(
char c : aValue )
107 case '\\': aQuoted +=
"\\\\";
break;
108 case '"': aQuoted +=
"\\\"";
break;
109 case '\n': aQuoted +=
"\\n";
break;
110 case '\r': aQuoted +=
"\\r";
break;
111 case '\t': aQuoted +=
"\\t";
break;
112 default: aQuoted += c;
break;
122 std::vector<std::pair<wxString, wxString>>& aTables, wxString& aError )
125 wxDir::GetAllFiles( aDir, &files );
127 for(
const wxString&
path : files )
129 wxFileName filename(
path );
132 || ( filename.GetFullName() != wxT(
"sym-lib-table" ) && filename.GetFullName() != wxT(
"fp-lib-table" ) ) )
137 wxFFile in(
path, wxT(
"rb" ) );
140 if( !in.IsOpened() || !in.ReadAll( &
text ) )
142 aError = wxString::Format(
_(
"Could not read library table '%s'." ),
path );
154 aError = wxString::Format(
_(
"Library table '%s' is invalid: %s" ),
path, parsed.error().description );
158 std::string output = filename.GetFullName() == wxT(
"sym-lib-table" ) ?
"(sym_lib_table" :
"(fp_lib_table";
159 output +=
" (version 7)\n";
165 aError = wxString::Format(
_(
"Library table '%s' uses a nested table that %s cannot open." ),
path,
172 for(
const auto& [key, value] : { std::pair{
"name", row.
nickname },
173 {
"type", row.
type },
179 if( std::string( key ) ==
"type" && value.empty() )
186 aError = wxString::Format(
187 _(
"Library table '%s' contains a quoted value that %s cannot represent exactly." ),
path,
192 output +=
" (" + std::string( key ) +
' ' + quoted +
')';
196 output +=
" (disabled)";
199 output +=
" (hidden)";
205 aTables.emplace_back(
path, wxString::FromUTF8( output.c_str() ) );
220 wxFFile in( aPath, wxT(
"rb" ) );
222 if( !in.IsOpened() || !in.ReadAll( &
text ) )
230 doc = nlohmann::json::parse(
text.ToStdString( wxConvUTF8 ) );
241 wxFFile out( aPath, wxT(
"wb" ) );
243 return out.IsOpened() && out.Write( wxString::FromUTF8( ( doc.dump( 2 ) +
"\n" ).c_str() ) );
252 wxFFile file( aPath, wxT(
"rb" ) );
254 if( !file.IsOpened() )
258 size_t n = file.Read( buf,
sizeof( buf ) - 1 );
261 size_t idx = head.find(
"(version " );
263 if( idx == std::string::npos )
266 return atoi( head.c_str() + idx + 9 );
274 auto scan = [&](
const wxString& aPattern,
int aMaxVersion ) -> wxString
277 wxDir::GetAllFiles( aDir, &files, aPattern );
279 for(
const wxString& f : files )
285 return wxEmptyString;
303 return scan( wxT(
"*.kicad_sch" ), aTarget.
m_schVersion );
309 std::vector<wxString>* aFoundVariants,
bool aDropInsteadOfApproximate,
310 const wxString& aVariant )
315 if( !boardFn.FileExists() && !schFn.FileExists() )
317 aError =
_(
"This project has no board or schematic to export." );
325 if( target && proFn.FileExists() )
328 wxFFile in( proFn.GetFullPath(), wxT(
"rb" ) );
330 if( in.IsOpened() && in.ReadAll( &
text ) )
334 nlohmann::json doc = nlohmann::json::parse(
text.ToStdString( wxConvUTF8 ) );
350 job.
m_filename = boardFn.FileExists() ? boardFn.GetFullPath() : wxString();
353 job.
m_variant = boardFn.FileExists() ? aVariant : wxString();
359 aError =
reporter.Explain(
_(
"Could not read the board to check compatibility." ) );
369 job.
m_filename = schFn.FileExists() ? schFn.GetFullPath() : wxString();
372 job.
m_variant = schFn.FileExists() ? aVariant : wxString();
378 aError =
reporter.Explain(
_(
"Could not read the schematic to check compatibility." ) );
392 std::vector<std::pair<wxString, wxString>> tables;
399 if( target && druFn.FileExists() )
402 wxFFile file( druFn.GetFullPath(), wxT(
"rb" ) );
404 if( file.IsOpened() && file.ReadAll( &rulesText ) )
408 if( !filtered.
m_error.IsEmpty() )
413 wxString shown =
name.IsEmpty() ?
_(
"unnamed" ) :
name;
416 _(
"The target cannot evaluate it with the same meaning. The rule is removed." ), 1 );
426 const wxString& aTargetName,
const wxString& aDestDir, wxString& aError,
427 const wxString& aVariant,
bool aDropInsteadOfApproximate )
433 aError = wxString::Format(
_(
"Unknown target version '%s'. Valid targets: %s" ), aTargetName,
438 std::vector<std::pair<wxString, wxString>> tables;
444 std::filesystem::path srcPath = std::filesystem::weakly_canonical( aProjectDir.ToStdWstring(), ec );
445 std::filesystem::path destPath = std::filesystem::weakly_canonical( aDestDir.ToStdWstring(), ec );
450 for( std::filesystem::path p = destPath; !p.empty() && p != p.parent_path(); p = p.parent_path() )
452 std::error_code cmpEc;
454 if( p == srcPath || ( std::filesystem::exists( p, cmpEc ) && std::filesystem::equivalent( p, srcPath, cmpEc ) ) )
456 aError =
_(
"Choose a destination outside the current project." );
461 bool created = std::filesystem::create_directories( destPath, ec );
465 aError = wxString::Format(
_(
"Could not create the folder '%s'." ), aDestDir );
469 if( !created && !std::filesystem::is_empty( destPath, ec ) )
471 aError = wxString::Format(
_(
"The folder '%s' is not empty. Choose a new or empty folder." ), aDestDir );
478 auto failClosed = [&](
const wxString& aMessage ) ->
bool
480 std::error_code cleanupEc;
484 std::filesystem::remove_all( destPath, cleanupEc );
488 std::vector<std::filesystem::path> entries;
490 for(
const auto& entry : std::filesystem::directory_iterator( destPath, cleanupEc ) )
491 entries.push_back( entry.path() );
493 for(
const std::filesystem::path& entry : entries )
494 std::filesystem::remove_all( entry, cleanupEc );
502 << wxString::Format(
_(
"Some files could not be removed from '%s'." ), aDestDir );
511 wxDir::GetAllFiles( aProjectDir, &files );
513 for(
const wxString& file : files )
518 wxFileName rel( file );
519 rel.MakeRelativeTo( aProjectDir );
521 wxFileName out( aDestDir + wxFileName::GetPathSeparator() + rel.GetFullPath() );
523 if( !out.DirExists() && !wxFileName::Mkdir( out.GetPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
524 return failClosed(
_(
"Could not copy the project." ) );
526 if( !wxCopyFile( file, out.GetFullPath() ) )
527 return failClosed(
_(
"Could not copy the project." ) );
536 wxFileName destBoard( aDestDir, boardFn.GetFullName() );
540 job.
m_filename = boardFn.FileExists() ? destBoard.GetFullPath() : wxString();
545 job.
m_variant = boardFn.FileExists() ? aVariant : wxString();
551 return failClosed(
reporter.Explain(
_(
"Export failed: the board could not be converted. Nothing "
552 "was written." ) ) );
557 wxFileName destSch( aDestDir, schFn.GetFullName() );
561 job.
m_filename = schFn.FileExists() ? destSch.GetFullPath() : wxString();
565 job.
m_variant = schFn.FileExists() ? aVariant : wxString();
571 return failClosed(
reporter.Explain(
_(
"Export failed: the schematic could not be converted. Nothing "
572 "was written." ) ) );
578 if( destPro.FileExists() )
583 return failClosed(
_(
"Export failed: could not migrate the project file." ) );
589 if( destDru.FileExists() )
594 wxFFile file( destDru.GetFullPath(), wxT(
"rb" ) );
596 if( !file.IsOpened() || !file.ReadAll( &rulesText ) )
597 return failClosed(
_(
"Export failed: could not read the design rules file." ) );
602 if( !filtered.
m_error.IsEmpty() )
603 return failClosed( wxString::Format(
_(
"Export failed: %s" ), filtered.
m_error ) );
605 wxFFile out( destDru.GetFullPath(), wxT(
"wb" ) );
607 if( !out.IsOpened() || !out.Write( filtered.
m_text ) )
608 return failClosed(
_(
"Export failed: could not write the design rules file." ) );
614 wxFileName relative(
source );
615 relative.MakeRelativeTo( aProjectDir );
616 wxFileName destination( aDestDir + wxFileName::GetPathSeparator() + relative.GetFullPath() );
617 wxFFile out( destination.GetFullPath(), wxT(
"wb" ) );
619 if( !out.IsOpened() || !out.Write(
text ) )
620 return failClosed(
_(
"Export failed: could not write the library table." ) );
626 if( !tooNew.IsEmpty() )
629 wxFileName relative( tooNew );
630 relative.MakeRelativeTo( aDestDir );
631 wxString
source = wxFileName( aProjectDir, wxEmptyString ).GetPathWithSep() + relative.GetFullPath();
633 return failClosed( wxString::Format(
_(
"Export failed: '%s' is not handled by the export and %s cannot "
634 "open it. Remove or update the file and try again. Nothing was "
What an export to a given target will keep, approximate, drop, or block.
void Add(DOWNGRADE_BUCKET aBucket, const wxString &aFeature, const wxString &aDetail=wxEmptyString, int aCount=1)
void Merge(const COMPATIBILITY_REPORT &aOther)
bool IsBlocked() const
True if some feature makes the target impossible. The user cannot export anyway.
wxString Explain(const wxString &aMessage) const
The collected error text appended to aMessage, or aMessage alone when there is none.
REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED) override
Report a string with a given severity.
bool HasMessage() const override
Returns true if any messages were reported.
wxString m_outputFile
Where to write the downgraded copy.
bool m_dropInsteadOfApproximate
Omit features that would otherwise be approximated.
wxString m_filename
Input board file (may be empty to downgrade only libraries)
wxString m_libraryDir
If set, downgrade every .kicad_mod footprint under this folder too.
COMPATIBILITY_REPORT m_report
Filled by the handler so the caller can show it.
wxString m_variant
Flatten this variant into the export. Empty means the base design.
bool m_inPlace
Internal project export operating only on its prepared copy.
wxString m_target
Target release name, e.g. "KiCad 10.0". Empty means the newest.
bool m_dryRun
Only classify and fill m_report, do not write anything.
bool m_force
Export even when features must be dropped or approximated.
std::vector< wxString > m_foundVariants
Variant names found in the schematic when the target drops variants.
wxString m_variant
Flatten this variant into the export. Empty means the base design.
wxString m_libraryDir
If set, downgrade every .kicad_sym symbol library under this folder too.
bool m_inPlace
Internal only: convert the coordinator's already-copied export files.
bool m_dryRun
Only classify and fill m_report, do not write anything.
wxString m_filename
Root schematic file (may be empty to downgrade only libraries)
COMPATIBILITY_REPORT m_report
Filled by the handler so the caller can show it.
wxString m_target
Target release name or id, e.g. "KiCad 10.0" or "10.0". Empty means the newest.
bool m_dropInsteadOfApproximate
Omit features that would otherwise be approximated.
bool m_force
Export even when features must be dropped or approximated.
A minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the same KiCad...
int ProcessJob(KIWAY::FACE_T aFace, JOB *aJob, REPORTER *aReporter=nullptr, PROGRESS_REPORTER *aProgressReporter=nullptr)
tl::expected< LIBRARY_TABLE_IR, LIBRARY_PARSE_ERROR > ParseBuffer(const std::string &aBuffer)
@ BLOCK
Cannot be represented at all, export to this target is not possible.
@ DROP
No equivalent, left out.
bool IsDowngradeWorkingArtifact(const wxString &aPath)
Editor working state (autosaves, local history, backups) holds current-format copies nothing downgrad...
bool IsDowngradeDesignFile(const wxString &aPath)
Files the project export carries over.
std::string StripSexprStrings(const std::string &aText)
Drop quoted strings so a token inside a text value cannot cause a false refusal.
const DOWNGRADE_TARGET * FindDowngradeTarget(const wxString &aNameOrId)
Match a user-supplied target string against name or stable id, case-insensitively.
wxString DowngradeTargetNames()
The valid target choices as one user-facing list, for error messages.
DRC_RULES_FILTER_RESULT FilterDrcRulesForTarget(const wxString &aRulesText, const DOWNGRADE_TARGET &aTarget)
Filter a .kicad_dru file for the target.
static const std::string ProjectFileExtension
static const std::string KiCadSchematicFileExtension
static const std::string DesignRulesFileExtension
static const std::string KiCadPcbFileExtension
static int readSexprVersion(const wxString &aPath)
bool ExportProjectForDowngrade(KIWAY &aKiway, const wxString &aProjectDir, const wxString &aProjectName, const wxString &aTargetName, const wxString &aDestDir, wxString &aError, const wxString &aVariant, bool aDropInsteadOfApproximate)
Export a downgraded copy of the project into aDestDir.
static bool downgradeProjectFileOnDisk(const wxString &aPath, const DOWNGRADE_TARGET &aTarget, COMPATIBILITY_REPORT &aReport)
static wxString firstTooNewFile(const wxString &aDir, const DOWNGRADE_TARGET &aTarget)
bool ClassifyProjectForDowngrade(KIWAY &aKiway, const wxString &aProjectDir, const wxString &aProjectName, const wxString &aTargetName, COMPATIBILITY_REPORT &aReport, wxString &aError, std::vector< wxString > *aFoundVariants, bool aDropInsteadOfApproximate, const wxString &aVariant)
Run the board and schematic classification for a whole project without writing anything.
static bool quoteLegacyLibraryTableValue(const std::string &aValue, bool aPreTen, std::string &aQuoted)
static bool legacyLibraryTables(const wxString &aDir, const DOWNGRADE_TARGET &aTarget, std::vector< std::pair< wxString, wxString > > &aTables, wxString &aError)
void DowngradeProjectFileJson(nlohmann::json &aDoc, const DOWNGRADE_TARGET &aTarget, COMPATIBILITY_REPORT &aReport)
Migrate a .kicad_pro document down to what the target release expects.
An older KiCad release we can export a project back to.
int m_schVersion
SEXPR_SCHEMATIC_FILE_VERSION for the target.
int m_boardVersion
SEXPR_BOARD_FILE_VERSION for the target.
int m_symLibVersion
SEXPR_SYMBOL_LIB_FILE_VERSION for the target.
wxString m_name
User-facing name, e.g. "KiCad 10.0".
wxString m_text
The filtered rules file text.
wxString m_error
Invalid source syntax; do not write m_text.
std::vector< wxString > m_dropped
Names of the rules the target cannot parse.
IbisParser parser & reporter
Definition of file extensions used in Kicad.