mirror of
https://github.com/reactos/reactos.git
synced 2025-08-06 09:13:00 +00:00
Verbose mode
svn path=/branches/xmlbuildsystem/; revision=14471
This commit is contained in:
parent
2c65178fde
commit
c4387e8d7d
10 changed files with 362 additions and 303 deletions
|
@ -28,6 +28,9 @@
|
||||||
# without source code) or no (to not build any map files). The variable
|
# without source code) or no (to not build any map files). The variable
|
||||||
# defaults to no.
|
# defaults to no.
|
||||||
#
|
#
|
||||||
|
# ROS_RBUILDFLAGS
|
||||||
|
# Pass parameters to rbuild.
|
||||||
|
#
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
|
@ -196,7 +199,7 @@ PREAUTO := \
|
||||||
|
|
||||||
makefile.auto: $(RBUILD_TARGET) $(PREAUTO) $(XMLBUILDFILES)
|
makefile.auto: $(RBUILD_TARGET) $(PREAUTO) $(XMLBUILDFILES)
|
||||||
$(ECHO_RBUILD)
|
$(ECHO_RBUILD)
|
||||||
$(Q)$(RBUILD_TARGET) mingw
|
$(Q)$(RBUILD_TARGET) $(ROS_RBUILDFLAGS) mingw
|
||||||
|
|
||||||
|
|
||||||
$(BUGCODES_H) $(BUGCODES_RC): $(WMC_TARGET) $(NTOSKRNL_MC)
|
$(BUGCODES_H) $(BUGCODES_RC): $(WMC_TARGET) $(NTOSKRNL_MC)
|
||||||
|
|
|
@ -65,9 +65,6 @@ SourceFile::Open ()
|
||||||
throw AccessDeniedException ( filename );
|
throw AccessDeniedException ( filename );
|
||||||
}
|
}
|
||||||
lastWriteTime = statbuf.st_mtime;
|
lastWriteTime = statbuf.st_mtime;
|
||||||
/* printf ( "lastWriteTime of %s is %s\n",
|
|
||||||
filename.c_str (),
|
|
||||||
ctime ( &lastWriteTime ) ); */
|
|
||||||
|
|
||||||
unsigned long len = (unsigned long) filelen ( f );
|
unsigned long len = (unsigned long) filelen ( f );
|
||||||
if ( len > MAX_BYTES_TO_READ )
|
if ( len > MAX_BYTES_TO_READ )
|
||||||
|
@ -219,7 +216,6 @@ SourceFile::Parse ()
|
||||||
while ( p < end )
|
while ( p < end )
|
||||||
{
|
{
|
||||||
string includedFilename ( "" );
|
string includedFilename ( "" );
|
||||||
//printf ( "Parsing '%s'\n", filename.c_str () );
|
|
||||||
|
|
||||||
bool includeNext;
|
bool includeNext;
|
||||||
while ( ReadInclude ( includedFilename,
|
while ( ReadInclude ( includedFilename,
|
||||||
|
@ -387,13 +383,12 @@ AutomaticDependency::RetrieveFromCache ( const string& filename )
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AutomaticDependency::CheckAutomaticDependencies ()
|
AutomaticDependency::CheckAutomaticDependencies ( bool verbose )
|
||||||
{
|
{
|
||||||
struct utimbuf timebuf;
|
struct utimbuf timebuf;
|
||||||
for ( size_t mi = 0; mi < project.modules.size (); mi++ )
|
for ( size_t mi = 0; mi < project.modules.size (); mi++ )
|
||||||
{
|
{
|
||||||
const vector<File*>& files = project.modules[mi]->non_if_data.files;
|
const vector<File*>& files = project.modules[mi]->non_if_data.files;
|
||||||
//Module& module = *project.modules[mi];
|
|
||||||
for ( size_t fi = 0; fi < files.size (); fi++ )
|
for ( size_t fi = 0; fi < files.size (); fi++ )
|
||||||
{
|
{
|
||||||
File& file = *files[fi];
|
File& file = *files[fi];
|
||||||
|
@ -406,20 +401,16 @@ AutomaticDependency::CheckAutomaticDependencies ()
|
||||||
assert ( sourceFile->youngestLastWriteTime != 0 );
|
assert ( sourceFile->youngestLastWriteTime != 0 );
|
||||||
if ( sourceFile->youngestLastWriteTime > sourceFile->lastWriteTime )
|
if ( sourceFile->youngestLastWriteTime > sourceFile->lastWriteTime )
|
||||||
{
|
{
|
||||||
printf ( "Marking %s for rebuild due to younger file %s\n",
|
if ( verbose )
|
||||||
sourceFile->filename.c_str (),
|
{
|
||||||
sourceFile->youngestFile->filename.c_str () );
|
printf ( "Marking %s for rebuild due to younger file %s\n",
|
||||||
|
sourceFile->filename.c_str (),
|
||||||
|
sourceFile->youngestFile->filename.c_str () );
|
||||||
|
}
|
||||||
timebuf.actime = sourceFile->youngestLastWriteTime;
|
timebuf.actime = sourceFile->youngestLastWriteTime;
|
||||||
timebuf.modtime = sourceFile->youngestLastWriteTime;
|
timebuf.modtime = sourceFile->youngestLastWriteTime;
|
||||||
utime ( sourceFile->filename.c_str (),
|
utime ( sourceFile->filename.c_str (),
|
||||||
&timebuf );
|
&timebuf );
|
||||||
|
|
||||||
/*printf ( "lastWriteTime of %s is %s\n",
|
|
||||||
sourceFile->filename.c_str (),
|
|
||||||
ctime ( &sourceFile->lastWriteTime ) );
|
|
||||||
printf ( "youngestLastWriteTime is %s with %s\n",
|
|
||||||
sourceFile->youngestFile->filename.c_str (),
|
|
||||||
ctime ( &sourceFile->youngestLastWriteTime ) );*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ Backend::Factory::Factory ( const std::string& name_ )
|
||||||
(*factories)[name] = this;
|
(*factories)[name] = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Backend::Factory::~Factory()
|
Backend::Factory::~Factory ()
|
||||||
{
|
{
|
||||||
if ( !--ref )
|
if ( !--ref )
|
||||||
{
|
{
|
||||||
|
@ -33,22 +33,26 @@ Backend::Factory::~Factory()
|
||||||
|
|
||||||
/*static*/ Backend*
|
/*static*/ Backend*
|
||||||
Backend::Factory::Create ( const string& name,
|
Backend::Factory::Create ( const string& name,
|
||||||
Project& project )
|
Project& project,
|
||||||
|
bool verbose )
|
||||||
{
|
{
|
||||||
string sname ( name );
|
string sname ( name );
|
||||||
strlwr ( &sname[0] );
|
strlwr ( &sname[0] );
|
||||||
if ( !factories || !factories->size() )
|
if ( !factories || !factories->size () )
|
||||||
throw Exception ( "internal tool error: no registered factories" );
|
throw InvalidOperationException ( __FILE__,
|
||||||
|
__LINE__,
|
||||||
|
"No registered factories" );
|
||||||
Backend::Factory* f = (*factories)[sname];
|
Backend::Factory* f = (*factories)[sname];
|
||||||
if ( !f )
|
if ( !f )
|
||||||
{
|
{
|
||||||
throw UnknownBackendException ( sname );
|
throw UnknownBackendException ( sname );
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return (*f) ( project );
|
return (*f) ( project, verbose );
|
||||||
}
|
}
|
||||||
|
|
||||||
Backend::Backend ( Project& project )
|
Backend::Backend ( Project& project, bool verbose )
|
||||||
: ProjectNode ( project )
|
: ProjectNode ( project ),
|
||||||
|
verbose ( verbose )
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
|
|
||||||
class Backend;
|
class Backend;
|
||||||
|
|
||||||
typedef Backend* BackendFactory ( Project& project );
|
typedef Backend* BackendFactory ( Project& project,
|
||||||
|
bool verbose );
|
||||||
|
|
||||||
class Backend
|
class Backend
|
||||||
{
|
{
|
||||||
|
@ -20,23 +21,23 @@ public:
|
||||||
Factory ( const std::string& name_ );
|
Factory ( const std::string& name_ );
|
||||||
virtual ~Factory();
|
virtual ~Factory();
|
||||||
|
|
||||||
virtual Backend* operator() ( Project& ) = 0;
|
virtual Backend* operator() ( Project&, bool verbose ) = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static Backend* Create ( const std::string& name,
|
static Backend* Create ( const std::string& name,
|
||||||
Project& project );
|
Project& project,
|
||||||
|
bool verbose );
|
||||||
private:
|
|
||||||
};
|
};
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Backend ( Project& project );
|
Backend ( Project& project, bool verbose );
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual void Process () = 0;
|
virtual void Process () = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Project& ProjectNode;
|
Project& ProjectNode;
|
||||||
|
bool verbose;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* __BACKEND_H */
|
#endif /* __BACKEND_H */
|
||||||
|
|
|
@ -34,15 +34,15 @@ static class DevCppFactory : public Backend::Factory
|
||||||
public:
|
public:
|
||||||
|
|
||||||
DevCppFactory() : Factory("devcpp") {}
|
DevCppFactory() : Factory("devcpp") {}
|
||||||
Backend *operator() (Project &project)
|
Backend *operator() (Project &project, bool verbose)
|
||||||
{
|
{
|
||||||
return new DevCppBackend(project);
|
return new DevCppBackend(project, verbose);
|
||||||
}
|
}
|
||||||
|
|
||||||
} factory;
|
} factory;
|
||||||
|
|
||||||
|
|
||||||
DevCppBackend::DevCppBackend(Project &project) : Backend(project)
|
DevCppBackend::DevCppBackend(Project &project, bool verbose) : Backend(project, verbose)
|
||||||
{
|
{
|
||||||
m_unitCount = 0;
|
m_unitCount = 0;
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,7 @@ void DevCppBackend::Process()
|
||||||
|
|
||||||
cout << "Creating Makefile: " << ProjectNode.makefile << endl;
|
cout << "Creating Makefile: " << ProjectNode.makefile << endl;
|
||||||
|
|
||||||
Backend *backend = Backend::Factory::Create("mingw", ProjectNode);
|
Backend *backend = Backend::Factory::Create("mingw", ProjectNode, verbose );
|
||||||
backend->Process();
|
backend->Process();
|
||||||
delete backend;
|
delete backend;
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ class DevCppBackend : public Backend
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
DevCppBackend(Project &project);
|
DevCppBackend(Project &project, bool verbose);
|
||||||
virtual ~DevCppBackend() {}
|
virtual ~DevCppBackend() {}
|
||||||
|
|
||||||
virtual void Process();
|
virtual void Process();
|
||||||
|
|
|
@ -27,7 +27,8 @@ public:
|
||||||
directory_map subdirs;
|
directory_map subdirs;
|
||||||
Directory ( const string& name );
|
Directory ( const string& name );
|
||||||
void Add ( const char* subdir );
|
void Add ( const char* subdir );
|
||||||
void GenerateTree ( const string& parent );
|
void GenerateTree ( const string& parent,
|
||||||
|
bool verbose );
|
||||||
private:
|
private:
|
||||||
bool mkdir_p ( const char* path );
|
bool mkdir_p ( const char* path );
|
||||||
string ReplaceVariable ( string name,
|
string ReplaceVariable ( string name,
|
||||||
|
@ -139,7 +140,8 @@ Directory::ResolveVariablesInPath ( char* buf,
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Directory::GenerateTree ( const string& parent )
|
Directory::GenerateTree ( const string& parent,
|
||||||
|
bool verbose )
|
||||||
{
|
{
|
||||||
string path;
|
string path;
|
||||||
|
|
||||||
|
@ -149,7 +151,7 @@ Directory::GenerateTree ( const string& parent )
|
||||||
|
|
||||||
path = parent + SSEP + name;
|
path = parent + SSEP + name;
|
||||||
ResolveVariablesInPath ( buf, path );
|
ResolveVariablesInPath ( buf, path );
|
||||||
if ( CreateDirectory ( buf ) )
|
if ( CreateDirectory ( buf ) && verbose )
|
||||||
printf ( "Created %s\n", buf );
|
printf ( "Created %s\n", buf );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -159,7 +161,7 @@ Directory::GenerateTree ( const string& parent )
|
||||||
i != subdirs.end();
|
i != subdirs.end();
|
||||||
++i )
|
++i )
|
||||||
{
|
{
|
||||||
i->second->GenerateTree ( path );
|
i->second->GenerateTree ( path, verbose );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,15 +169,15 @@ static class MingwFactory : public Backend::Factory
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MingwFactory() : Factory ( "mingw" ) {}
|
MingwFactory() : Factory ( "mingw" ) {}
|
||||||
Backend* operator() ( Project& project )
|
Backend* operator() ( Project& project, bool verbose )
|
||||||
{
|
{
|
||||||
return new MingwBackend ( project );
|
return new MingwBackend ( project, verbose );
|
||||||
}
|
}
|
||||||
} factory;
|
} factory;
|
||||||
|
|
||||||
|
|
||||||
MingwBackend::MingwBackend ( Project& project )
|
MingwBackend::MingwBackend ( Project& project, bool verbose )
|
||||||
: Backend ( project ),
|
: Backend ( project, verbose ),
|
||||||
int_directories ( new Directory("$(INTERMEDIATE)") ),
|
int_directories ( new Directory("$(INTERMEDIATE)") ),
|
||||||
out_directories ( new Directory("$(OUTPUT)") )
|
out_directories ( new Directory("$(OUTPUT)") )
|
||||||
{
|
{
|
||||||
|
@ -202,20 +204,12 @@ MingwBackend::AddDirectoryTarget ( const string& directory, bool out )
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MingwBackend::Process ()
|
MingwBackend::ProcessModules ()
|
||||||
{
|
{
|
||||||
size_t i;
|
printf ( "Processing modules..." );
|
||||||
|
|
||||||
DetectPipeSupport ();
|
|
||||||
DetectPCHSupport ();
|
|
||||||
|
|
||||||
CreateMakefile ();
|
|
||||||
GenerateHeader ();
|
|
||||||
GenerateGlobalVariables ();
|
|
||||||
GenerateXmlBuildFilesMacro();
|
|
||||||
|
|
||||||
vector<MingwModuleHandler*> v;
|
vector<MingwModuleHandler*> v;
|
||||||
|
size_t i;
|
||||||
for ( i = 0; i < ProjectNode.modules.size (); i++ )
|
for ( i = 0; i < ProjectNode.modules.size (); i++ )
|
||||||
{
|
{
|
||||||
Module& module = *ProjectNode.modules[i];
|
Module& module = *ProjectNode.modules[i];
|
||||||
|
@ -255,6 +249,19 @@ MingwBackend::Process ()
|
||||||
delete v[i];
|
delete v[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printf ( "done\n" );
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
MingwBackend::Process ()
|
||||||
|
{
|
||||||
|
DetectPipeSupport ();
|
||||||
|
DetectPCHSupport ();
|
||||||
|
CreateMakefile ();
|
||||||
|
GenerateHeader ();
|
||||||
|
GenerateGlobalVariables ();
|
||||||
|
GenerateXmlBuildFilesMacro ();
|
||||||
|
ProcessModules ();
|
||||||
GenerateDirectories ();
|
GenerateDirectories ();
|
||||||
CheckAutomaticDependencies ();
|
CheckAutomaticDependencies ();
|
||||||
CloseMakefile ();
|
CloseMakefile ();
|
||||||
|
@ -488,9 +495,11 @@ MingwBackend::GenerateXmlBuildFilesMacro() const
|
||||||
void
|
void
|
||||||
MingwBackend::CheckAutomaticDependencies ()
|
MingwBackend::CheckAutomaticDependencies ()
|
||||||
{
|
{
|
||||||
|
printf ( "Checking automatic dependencies..." );
|
||||||
AutomaticDependency automaticDependency ( ProjectNode );
|
AutomaticDependency automaticDependency ( ProjectNode );
|
||||||
automaticDependency.Process ();
|
automaticDependency.Process ();
|
||||||
automaticDependency.CheckAutomaticDependencies ();
|
automaticDependency.CheckAutomaticDependencies ( verbose );
|
||||||
|
printf ( "done\n" );
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
@ -505,8 +514,10 @@ MingwBackend::IncludeDirectoryTarget ( const string& directory ) const
|
||||||
void
|
void
|
||||||
MingwBackend::GenerateDirectories ()
|
MingwBackend::GenerateDirectories ()
|
||||||
{
|
{
|
||||||
int_directories->GenerateTree ( "" );
|
printf ( "Creating directories..." );
|
||||||
out_directories->GenerateTree ( "" );
|
int_directories->GenerateTree ( "", verbose );
|
||||||
|
out_directories->GenerateTree ( "", verbose );
|
||||||
|
printf ( "done\n" );
|
||||||
}
|
}
|
||||||
|
|
||||||
string
|
string
|
||||||
|
|
|
@ -15,7 +15,7 @@ class MingwModuleHandler;
|
||||||
class MingwBackend : public Backend
|
class MingwBackend : public Backend
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MingwBackend ( Project& project );
|
MingwBackend ( Project& project, bool verbose );
|
||||||
virtual ~MingwBackend ();
|
virtual ~MingwBackend ();
|
||||||
virtual void Process ();
|
virtual void Process ();
|
||||||
std::string AddDirectoryTarget ( const std::string& directory, bool out );
|
std::string AddDirectoryTarget ( const std::string& directory, bool out );
|
||||||
|
@ -40,6 +40,7 @@ private:
|
||||||
bool IncludeDirectoryTarget ( const std::string& directory ) const;
|
bool IncludeDirectoryTarget ( const std::string& directory ) const;
|
||||||
void DetectPipeSupport ();
|
void DetectPipeSupport ();
|
||||||
void DetectPCHSupport ();
|
void DetectPCHSupport ();
|
||||||
|
void ProcessModules ();
|
||||||
FILE* fMakefile;
|
FILE* fMakefile;
|
||||||
bool use_pch;
|
bool use_pch;
|
||||||
Directory *int_directories, *out_directories;
|
Directory *int_directories, *out_directories;
|
||||||
|
|
|
@ -16,24 +16,72 @@
|
||||||
using std::string;
|
using std::string;
|
||||||
using std::vector;
|
using std::vector;
|
||||||
|
|
||||||
|
static string BuildSystem;
|
||||||
|
static bool Verbose = false;
|
||||||
|
|
||||||
|
bool
|
||||||
|
ParseSwitch ( int argc, char** argv, int index )
|
||||||
|
{
|
||||||
|
char switchChar = argv[index][1];
|
||||||
|
switch ( switchChar )
|
||||||
|
{
|
||||||
|
case 'v':
|
||||||
|
Verbose = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printf ( "Unknown switch -%c",
|
||||||
|
switchChar );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
ParseArguments ( int argc, char** argv )
|
||||||
|
{
|
||||||
|
if ( argc < 2 )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for ( int i = 1; i < argc; i++ )
|
||||||
|
{
|
||||||
|
if ( argv[i][0] == '-' )
|
||||||
|
{
|
||||||
|
if ( !ParseSwitch ( argc, argv, i ) )
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
BuildSystem = argv[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
main ( int argc, char** argv )
|
main ( int argc, char** argv )
|
||||||
{
|
{
|
||||||
if ( argc != 2 )
|
if ( !ParseArguments ( argc, argv ) )
|
||||||
{
|
{
|
||||||
printf ( "syntax: rbuild {buildtarget}\n" );
|
printf ( "Generates project files for buildsystems\n\n" );
|
||||||
|
printf ( " rbuild [-v] buildsystem\n\n" );
|
||||||
|
printf ( "Switches:\n" );
|
||||||
|
printf ( " -v Be verbose\n" );
|
||||||
|
printf ( "\n" );
|
||||||
|
printf ( " buildsystem Target build system. Can be one of:\n" );
|
||||||
|
printf ( " mingw MinGW\n" );
|
||||||
|
printf ( " devcpp DevC++\n" );
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
string buildtarget ( argv[1] );
|
|
||||||
strlwr ( &buildtarget[0] );
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string projectFilename ( "ReactOS.xml" );
|
string projectFilename ( "ReactOS.xml" );
|
||||||
|
printf ( "Reading build files..." );
|
||||||
Project project ( projectFilename );
|
Project project ( projectFilename );
|
||||||
|
printf ( "done\n" );
|
||||||
project.WriteConfigurationFile ();
|
project.WriteConfigurationFile ();
|
||||||
project.ExecuteInvocations ();
|
project.ExecuteInvocations ();
|
||||||
Backend* backend = Backend::Factory::Create ( buildtarget,
|
Backend* backend = Backend::Factory::Create ( BuildSystem,
|
||||||
project );
|
project,
|
||||||
|
Verbose );
|
||||||
backend->Process ();
|
backend->Process ();
|
||||||
delete backend;
|
delete backend;
|
||||||
|
|
||||||
|
@ -41,8 +89,8 @@ main ( int argc, char** argv )
|
||||||
}
|
}
|
||||||
catch (Exception& ex)
|
catch (Exception& ex)
|
||||||
{
|
{
|
||||||
printf ( "%s: %s\n",
|
printf ( "%s\n",
|
||||||
typeid(ex).name(), ex.Message.c_str() );
|
ex.Message.c_str () );
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -469,7 +469,7 @@ public:
|
||||||
const std::string& filename,
|
const std::string& filename,
|
||||||
SourceFile* parentSourceFile );
|
SourceFile* parentSourceFile );
|
||||||
SourceFile* RetrieveFromCache ( const std::string& filename );
|
SourceFile* RetrieveFromCache ( const std::string& filename );
|
||||||
void CheckAutomaticDependencies ();
|
void CheckAutomaticDependencies ( bool verbose );
|
||||||
void CheckAutomaticDependenciesForFile ( SourceFile* sourceFile );
|
void CheckAutomaticDependenciesForFile ( SourceFile* sourceFile );
|
||||||
private:
|
private:
|
||||||
void ProcessModule ( Module& module );
|
void ProcessModule ( Module& module );
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue