KiCad PCB EDA Suite
Loading...
Searching...
No Matches
command_project_downgrade.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
21#include "cli/exit_codes.h"
23
24#include <json_common.h>
25#include <filesystem>
26#include <kiplatform/io.h>
27#include <downgrade_scan.h>
28
29#include <wx/crt.h>
30#include <wx/file.h>
31#include <wx/ffile.h>
32#include <wx/filename.h>
33
34#include <downgrade_target.h>
35
36#define ARG_TARGET "--target"
37#define ARG_FORCE "--force"
38#define ARG_DRY_RUN "--dry-run"
39#define ARG_REPORT_JSON "--report-json"
40#define ARG_VARIANT "--variant"
41#define ARG_DROP_APPROXIMATIONS "--drop-approximations"
42
44 COMMAND( "downgrade" )
45{
47 m_argParser.add_description( UTF8STDSTR( _( "Export a copy of the whole project readable by an older "
48 "KiCad version" ) ) );
49
50 m_argParser.add_argument( ARG_TARGET )
51 .default_value( std::string() )
52 .help( UTF8STDSTR(
53 _( "Target version name or id, e.g. \"KiCad 10.0\" or \"10.0\". Defaults to the newest." ) ) );
54
55 m_argParser.add_argument( ARG_FORCE )
56 .help( UTF8STDSTR( _( "Export even if some features must be dropped or approximated" ) ) )
57 .flag();
58
59 m_argParser.add_argument( ARG_DRY_RUN )
60 .help( UTF8STDSTR( _( "Print the compatibility report without writing anything" ) ) )
61 .flag();
62
64 .help( UTF8STDSTR(
65 _( "Omit features instead of approximating them; may remove drawing or electrical data" ) ) )
66 .flag();
67
68 m_argParser.add_argument( ARG_REPORT_JSON )
69 .default_value( std::string() )
70 .help( UTF8STDSTR( _( "Also write the compatibility report as JSON to this file" ) ) );
71
72 m_argParser.add_argument( ARG_VARIANT )
73 .default_value( std::string() )
74 .help( UTF8STDSTR( _( "Flatten this variant into the export. Default is the base design." ) ) );
75}
76
77
78static const char* bucketName( DOWNGRADE_BUCKET aBucket )
79{
80 switch( aBucket )
81 {
82 case DOWNGRADE_BUCKET::KEEP: return "keep";
83 case DOWNGRADE_BUCKET::LOWER: return "lower";
84 case DOWNGRADE_BUCKET::DROP: return "drop";
85 case DOWNGRADE_BUCKET::BLOCK: return "block";
86 }
87
88 return "unknown";
89}
90
91
93{
94 if( !wxFile::Exists( m_argInput ) )
95 {
96 wxFprintf( stderr, _( "Project file does not exist or is not accessible\n" ) );
98 }
99
100 wxFileName proFn( m_argInput );
101 proFn.MakeAbsolute();
102
103 wxString projectDir = proFn.GetPath();
104 wxString projectName = proFn.GetName();
105
106 wxString targetName = wxString::FromUTF8( m_argParser.get<std::string>( ARG_TARGET ) );
107
108 if( targetName.IsEmpty() )
109 targetName = GetDowngradeTargets().front().m_name;
110
111 const DOWNGRADE_TARGET* target = FindDowngradeTarget( targetName );
112
113 if( !target )
114 {
115 wxFprintf( stderr, _( "Unknown target version '%s'. Valid targets: %s\n" ), targetName,
118 }
119
120 // Argument problems surface before the classification runs, not one rerun at a time.
121 if( !m_argParser.get<bool>( ARG_DRY_RUN ) && m_argOutput.IsEmpty() )
122 {
123 wxFprintf( stderr, _( "An output folder is required. Use --output.\n" ) );
125 }
126
127 wxString jsonPath = wxString::FromUTF8( m_argParser.get<std::string>( ARG_REPORT_JSON ) );
128
129 if( !jsonPath.IsEmpty() )
130 {
131 wxFileName reportFile( jsonPath );
132 reportFile.MakeAbsolute();
133 jsonPath = reportFile.GetFullPath();
134 wxArrayString sources;
135 wxDir::GetAllFiles( projectDir, &sources );
136
137 for( const wxString& source : sources )
138 {
140 continue;
141
142 std::error_code error;
143 bool sameFile = std::filesystem::equivalent( jsonPath.ToStdWstring(), source.ToStdWstring(), error );
144
145 if( sameFile || reportFile.SameAs( wxFileName( source ) ) )
146 {
147 wxFprintf( stderr, _( "The report file must differ from the source project files.\n" ) );
149 }
150 }
151 }
152
154 wxString error;
155 bool dropInsteadOfApproximate = m_argParser.get<bool>( ARG_DROP_APPROXIMATIONS );
156 wxString variant = wxString::FromUTF8( m_argParser.get<std::string>( ARG_VARIANT ) );
157
158 if( !ClassifyProjectForDowngrade( aKiway, projectDir, projectName, targetName, report, error, nullptr,
159 dropInsteadOfApproximate, variant ) )
160 {
161 wxFprintf( stderr, wxT( "%s\n" ), error );
163 }
164
165 // The same printer the pcb and sch downgrade commands use, so the vocabulary matches.
166 report.Print( &CLI_REPORTER::GetInstance() );
167
168 if( !jsonPath.IsEmpty() )
169 {
170 nlohmann::json js;
171 js["target"] = std::string( target->m_name.ToUTF8() );
172 js["blocked"] = report.IsBlocked();
173 js["lossy"] = report.IsLossy();
174 js["drop_approximations"] = dropInsteadOfApproximate;
175 js["entries"] = nlohmann::json::array();
176
177 for( const COMPAT_ENTRY& entry : report.Entries() )
178 {
179 js["entries"].push_back( { { "category", bucketName( entry.m_bucket ) },
180 { "feature", std::string( entry.m_feature.ToUTF8() ) },
181 { "detail", std::string( entry.m_detail.ToUTF8() ) },
182 { "count", entry.m_count } } );
183 }
184
185 std::string text = js.dump( 2 ) + "\n";
186
187 if( !KIPLATFORM::IO::AtomicWriteFile( jsonPath, text.data(), text.size(), &error ) )
188 {
189 wxFprintf( stderr, _( "Could not write the JSON report to '%s'\n" ), jsonPath );
191 }
192 }
193
194 if( m_argParser.get<bool>( ARG_DRY_RUN ) )
195 return EXIT_CODES::OK;
196
197 if( report.IsBlocked() )
198 {
199 wxFprintf( stderr, _( "Cannot export: the project uses features the target cannot represent.\n" ) );
201 }
202
203 if( report.IsLossy() && !m_argParser.get<bool>( ARG_FORCE ) )
204 {
205 wxFprintf( stderr, _( "Export would lose data. Re-run with --force to export anyway.\n" ) );
207 }
208
209 wxFileName outDir( m_argOutput, wxEmptyString );
210 outDir.MakeAbsolute();
211
212 if( !ExportProjectForDowngrade( aKiway, projectDir, projectName, targetName, outDir.GetPath(), error, variant,
213 dropInsteadOfApproximate ) )
214 {
215 wxFprintf( stderr, wxT( "%s\n" ), error );
217 }
218
219 wxPrintf( _( "Exported to '%s'\n" ), outDir.GetPath() );
220 return EXIT_CODES::OK;
221}
argparse::ArgumentParser m_argParser
Definition command.h:113
COMMAND(const std::string &aName)
Define a new COMMAND instance.
Definition command.cpp:30
void addCommonArgs(bool aInput, bool aOutput, IO_TYPE aInputType, IO_TYPE aOutputType)
Set up the most common of args used across cli.
Definition command.cpp:134
wxString m_argOutput
Value of the output arg if configured.
Definition command.h:143
wxString m_argInput
Value of the common input arg if configured.
Definition command.h:138
static CLI_REPORTER & GetInstance()
Definition reporter.cpp:261
What an export to a given target will keep, approximate, drop, or block.
void Print(REPORTER *aReporter) const
bool IsLossy() const
True if anything is approximated or dropped. This is what triggers the lossy warning.
bool IsBlocked() const
True if some feature makes the target impossible. The user cannot export anyway.
const std::vector< COMPAT_ENTRY > & Entries() const
A minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the same KiCad...
Definition kiway.h:348
#define ARG_VARIANT
Definition command.h:38
#define UTF8STDSTR(s)
Definition command.h:27
#define ARG_FORCE
#define ARG_DRY_RUN
#define ARG_TARGET
#define ARG_DROP_APPROXIMATIONS
#define ARG_REPORT_JSON
static const char * bucketName(DOWNGRADE_BUCKET aBucket)
DOWNGRADE_BUCKET
What happens to a feature when we export to an older KiCad.
@ BLOCK
Cannot be represented at all, export to this target is not possible.
@ KEEP
Exists in the target, written unchanged.
@ LOWER
Approximated down to a simpler equivalent.
@ DROP
No equivalent, left out.
bool IsDowngradeDesignFile(const wxString &aPath)
Files the project export carries over.
const std::vector< DOWNGRADE_TARGET > & GetDowngradeTargets()
Supported downgrade targets, newest first.
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.
#define _(s)
std::string source
static const int ERR_ARGS
Definition exit_codes.h:31
static const int OK
Definition exit_codes.h:30
static const int ERR_INVALID_INPUT_FILE
Definition exit_codes.h:33
static const int ERR_UNKNOWN
Definition exit_codes.h:32
bool AtomicWriteFile(const wxString &aTargetPath, const void *aData, size_t aSize, wxString *aError=nullptr)
Writes aData to aTargetPath via a sibling temp file, fsyncs the data and directory,...
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.
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.
int doPerform(KIWAY &aKiway) override
The internal handler that should be overloaded to implement command specific processing and work.
One line in the compatibility report.
An older KiCad release we can export a project back to.
wxString m_name
User-facing name, e.g. "KiCad 10.0".