Commit 48263c9a by Emmanuel Briot Committed by Arnaud Charlet

2009-10-30 Emmanuel Briot <briot@adacore.com>

	* make.adb, adaint.c, adaint.h, osint.adb, osint.ads, bcheck.adb
	(*_attr): new subprograms.
	(File_Length, File_Time_Stamp, Is_Writable_File): new subprograms
	(Read_Library_Info_From_Full, Full_Library_Info_Name,
	Full_Source_Name): Now benefit from a previous cache of the file
	attributes, to further save on system calls.
	(Smart_Find_File): now also cache the file attributes. This makes the
	package File_Stamp_Hash_Table useless, and it was removed.
	(Compile_Sources): create subprograms for the various steps of the main
	loop, for readibility and to avoid sharing variables between the
	various steps.

From-SVN: r153747
parent b11cb5fd
2009-10-30 Emmanuel Briot <briot@adacore.com>
* make.adb, adaint.c, adaint.h, osint.adb, osint.ads, bcheck.adb
(*_attr): new subprograms.
(File_Length, File_Time_Stamp, Is_Writable_File): new subprograms
(Read_Library_Info_From_Full, Full_Library_Info_Name,
Full_Source_Name): Now benefit from a previous cache of the file
attributes, to further save on system calls.
(Smart_Find_File): now also cache the file attributes. This makes the
package File_Stamp_Hash_Table useless, and it was removed.
(Compile_Sources): create subprograms for the various steps of the main
loop, for readibility and to avoid sharing variables between the
various steps.
2009-10-30 Emmanuel Briot <briot@adacore.com>
* make.adb, osint.adb, osint.ads (Library_File_Stamp): Removed, since
unused.
(Read_Library_Info_From_Full): New subprogram.
......
......@@ -324,6 +324,12 @@ const int __gnat_vmsp = 0;
#endif
/* Used for Ada bindings */
const int size_of_file_attributes = sizeof (struct file_attributes);
/* Reset the file attributes as if no system call had been performed */
void __gnat_stat_to_attr (int fd, char* name, struct file_attributes* attr);
/* The __gnat_max_path_len variable is used to export the maximum
length of a path name to Ada code. max_path_len is also provided
for compatibility with older GNAT versions, please do not use
......@@ -371,6 +377,24 @@ to_ptr32 (char **ptr64)
#define MAYBE_TO_PTR32(argv) argv
#endif
void
reset_attributes
(struct file_attributes* attr)
{
attr->exists = -1;
attr->writable = -1;
attr->readable = -1;
attr->executable = -1;
attr->regular = -1;
attr->symbolic_link = -1;
attr->directory = -1;
attr->timestamp = (OS_Time)-2;
attr->file_length = -1;
}
OS_Time
__gnat_current_time
(void)
......@@ -1036,42 +1060,89 @@ __gnat_open_new_temp (char *path, int fmode)
return fd < 0 ? -1 : fd;
}
/* Return the number of bytes in the specified file. */
/****************************************************************
** Perform a call to GNAT_STAT or GNAT_FSTAT, and extract as much information
** as possible from it, storing the result in a cache for later reuse
****************************************************************/
long
__gnat_file_length (int fd)
void
__gnat_stat_to_attr (int fd, char* name, struct file_attributes* attr)
{
int ret;
GNAT_STRUCT_STAT statbuf;
int ret;
if (fd != -1)
ret = GNAT_FSTAT (fd, &statbuf);
if (ret || !S_ISREG (statbuf.st_mode))
return 0;
else
ret = __gnat_stat (name, &statbuf);
attr->regular = (!ret && S_ISREG (statbuf.st_mode));
attr->directory = (!ret && S_ISDIR (statbuf.st_mode));
if (!attr->regular)
attr->file_length = 0;
else
/* st_size may be 32 bits, or 64 bits which is converted to long. We
don't return a useful value for files larger than 2 gigabytes in
either case. */
attr->file_length = statbuf.st_size; /* all systems */
#ifndef __MINGW32__
/* on Windows requires extra system call, see comment in __gnat_file_exists_attr */
attr->exists = !ret;
#endif
#if !defined (_WIN32) || defined (RTX)
/* on Windows requires extra system call, see __gnat_is_readable_file_attr */
attr->readable = (!ret && (statbuf.st_mode & S_IRUSR));
attr->writable = (!ret && (statbuf.st_mode & S_IWUSR));
attr->executable = (!ret && (statbuf.st_mode & S_IXUSR));
#endif
#if !defined (__EMX__) && !defined (MSDOS) && (!defined (_WIN32) || defined (RTX))
/* on Windows requires extra system call, see __gnat_file_time_name_attr */
if (ret != 0) {
attr->timestamp = (OS_Time)-1;
} else {
#ifdef VMS
/* VMS has file versioning. */
attr->timestamp = (OS_Time)statbuf.st_ctime;
#else
attr->timestamp = (OS_Time)statbuf.st_mtime;
#endif
}
#endif
return (statbuf.st_size);
}
/* Return the number of bytes in the specified named file. */
/****************************************************************
** Return the number of bytes in the specified file
****************************************************************/
long
__gnat_named_file_length (char *name)
__gnat_file_length_attr (int fd, char* name, struct file_attributes* attr)
{
int ret;
GNAT_STRUCT_STAT statbuf;
if (attr->file_length == -1) {
__gnat_stat_to_attr (fd, name, attr);
}
ret = __gnat_stat (name, &statbuf);
if (ret || !S_ISREG (statbuf.st_mode))
return 0;
return attr->file_length;
}
/* st_size may be 32 bits, or 64 bits which is converted to long. We
don't return a useful value for files larger than 2 gigabytes in
either case. */
long
__gnat_file_length (int fd)
{
struct file_attributes attr;
reset_attributes (&attr);
return __gnat_file_length_attr (fd, NULL, &attr);
}
return (statbuf.st_size);
long
__gnat_named_file_length (char *name)
{
struct file_attributes attr;
reset_attributes (&attr);
return __gnat_file_length_attr (-1, name, &attr);
}
/* Create a temporary filename and put it in string pointed to by
......@@ -1266,51 +1337,50 @@ win32_filetime (HANDLE h)
/* Return a GNAT time stamp given a file name. */
OS_Time
__gnat_file_time_name (char *name)
__gnat_file_time_name_attr (char* name, struct file_attributes* attr)
{
if (attr->timestamp == (OS_Time)-2) {
#if defined (__EMX__) || defined (MSDOS)
int fd = open (name, O_RDONLY | O_BINARY);
time_t ret = __gnat_file_time_fd (fd);
close (fd);
return (OS_Time)ret;
attr->timestamp = (OS_Time)ret;
#elif defined (_WIN32) && !defined (RTX)
time_t ret = -1;
TCHAR wname[GNAT_MAX_PATH_LEN];
S2WSC (wname, name, GNAT_MAX_PATH_LEN);
HANDLE h = CreateFile
(wname, GENERIC_READ, FILE_SHARE_READ, 0,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
if (h != INVALID_HANDLE_VALUE)
{
if (h != INVALID_HANDLE_VALUE) {
ret = win32_filetime (h);
CloseHandle (h);
}
return (OS_Time) ret;
#else
GNAT_STRUCT_STAT statbuf;
if (__gnat_stat (name, &statbuf) != 0) {
return (OS_Time)-1;
} else {
#ifdef VMS
/* VMS has file versioning. */
return (OS_Time)statbuf.st_ctime;
attr->timestamp = (OS_Time) ret;
#else
return (OS_Time)statbuf.st_mtime;
__gnat_stat_to_attr (-1, name, attr);
#endif
}
#endif
return attr->timestamp;
}
OS_Time
__gnat_file_time_name (char *name)
{
struct file_attributes attr;
reset_attributes (&attr);
return __gnat_file_time_name_attr (name, &attr);
}
/* Return a GNAT time stamp given a file descriptor. */
OS_Time
__gnat_file_time_fd (int fd)
__gnat_file_time_fd_attr (int fd, struct file_attributes* attr)
{
if (attr->timestamp == (OS_Time)-2) {
/* The following workaround code is due to the fact that under EMX and
DJGPP fstat attempts to convert time values to GMT rather than keep the
actual OS timestamp of the file. By using the OS2/DOS functions directly
......@@ -1376,27 +1446,27 @@ __gnat_file_time_fd (int fd)
tot_secs += file_hour * 3600;
tot_secs += file_min * 60;
tot_secs += file_tsec * 2;
return (OS_Time) tot_secs;
attr->timestamp = (OS_Time) tot_secs;
#elif defined (_WIN32) && !defined (RTX)
HANDLE h = (HANDLE) _get_osfhandle (fd);
time_t ret = win32_filetime (h);
return (OS_Time) ret;
attr->timestamp = (OS_Time) ret;
#else
GNAT_STRUCT_STAT statbuf;
if (GNAT_FSTAT (fd, &statbuf) != 0) {
return (OS_Time) -1;
} else {
#ifdef VMS
/* VMS has file versioning. */
return (OS_Time) statbuf.st_ctime;
#else
return (OS_Time) statbuf.st_mtime;
__gnat_stat_to_attr (fd, NULL, attr);
#endif
}
#endif
return attr->timestamp;
}
OS_Time
__gnat_file_time_fd (int fd)
{
struct file_attributes attr;
reset_attributes (&attr);
return __gnat_file_time_fd_attr (fd, &attr);
}
/* Set the file time stamp. */
......@@ -1722,24 +1792,41 @@ __gnat_stat (char *name, GNAT_STRUCT_STAT *statbuf)
#endif
}
/*************************************************************************
** Check whether a file exists
*************************************************************************/
int
__gnat_file_exists (char *name)
__gnat_file_exists_attr (char* name, struct file_attributes* attr)
{
if (attr->exists == -1) {
#ifdef __MINGW32__
/* On Windows do not use __gnat_stat() because a bug in Microsoft
/* On Windows do not use __gnat_stat() because of a bug in Microsoft
_stat() routine. When the system time-zone is set with a negative
offset the _stat() routine fails on specific files like CON: */
TCHAR wname [GNAT_MAX_PATH_LEN + 2];
S2WSC (wname, name, GNAT_MAX_PATH_LEN + 2);
return GetFileAttributes (wname) != INVALID_FILE_ATTRIBUTES;
attr->exists = GetFileAttributes (wname) != INVALID_FILE_ATTRIBUTES;
#else
GNAT_STRUCT_STAT statbuf;
return !__gnat_stat (name, &statbuf);
__gnat_stat_to_attr (-1, name, attr);
#endif
}
return attr->exists;
}
int
__gnat_file_exists (char *name)
{
struct file_attributes attr;
reset_attributes (&attr);
return __gnat_file_exists_attr (name, &attr);
}
/**********************************************************************
** Whether name is an absolute path
**********************************************************************/
int
__gnat_is_absolute_path (char *name, int length)
{
......@@ -1776,23 +1863,39 @@ __gnat_is_absolute_path (char *name, int length)
}
int
__gnat_is_regular_file_attr (char* name, struct file_attributes* attr)
{
if (attr->regular == -1) {
__gnat_stat_to_attr (-1, name, attr);
}
return attr->regular;
}
int
__gnat_is_regular_file (char *name)
{
int ret;
GNAT_STRUCT_STAT statbuf;
struct file_attributes attr;
reset_attributes (&attr);
return __gnat_is_regular_file_attr (name, &attr);
}
ret = __gnat_stat (name, &statbuf);
return (!ret && S_ISREG (statbuf.st_mode));
int
__gnat_is_directory_attr (char* name, struct file_attributes* attr)
{
if (attr->directory == -1) {
__gnat_stat_to_attr (-1, name, attr);
}
return attr->directory;
}
int
__gnat_is_directory (char *name)
{
int ret;
GNAT_STRUCT_STAT statbuf;
ret = __gnat_stat (name, &statbuf);
return (!ret && S_ISDIR (statbuf.st_mode));
struct file_attributes attr;
reset_attributes (&attr);
return __gnat_is_directory_attr (name, &attr);
}
#if defined (_WIN32) && !defined (RTX)
......@@ -1986,8 +2089,9 @@ __gnat_can_use_acl (TCHAR *wname)
#endif /* defined (_WIN32) && !defined (RTX) */
int
__gnat_is_readable_file (char *name)
__gnat_is_readable_file_attr (char* name, struct file_attributes* attr)
{
if (attr->readable == -1) {
#if defined (_WIN32) && !defined (RTX)
TCHAR wname [GNAT_MAX_PATH_LEN + 2];
GENERIC_MAPPING GenericMapping;
......@@ -1998,26 +2102,30 @@ __gnat_is_readable_file (char *name)
{
ZeroMemory (&GenericMapping, sizeof (GENERIC_MAPPING));
GenericMapping.GenericRead = GENERIC_READ;
return __gnat_check_OWNER_ACL (wname, FILE_READ_DATA, GenericMapping);
attr->readable = __gnat_check_OWNER_ACL (wname, FILE_READ_DATA, GenericMapping);
}
else
return GetFileAttributes (wname) != INVALID_FILE_ATTRIBUTES;
attr->readable = GetFileAttributes (wname) != INVALID_FILE_ATTRIBUTES;
#else
int ret;
int mode;
GNAT_STRUCT_STAT statbuf;
ret = GNAT_STAT (name, &statbuf);
mode = statbuf.st_mode & S_IRUSR;
return (!ret && mode);
__gnat_stat_to_attr (-1, name, attr);
#endif
}
return attr->readable;
}
int
__gnat_is_writable_file (char *name)
__gnat_is_readable_file (char *name)
{
struct file_attributes attr;
reset_attributes (&attr);
return __gnat_is_readable_file_attr (name, &attr);
}
int
__gnat_is_writable_file_attr (char* name, struct file_attributes* attr)
{
if (attr->writable == -1) {
#if defined (_WIN32) && !defined (RTX)
TCHAR wname [GNAT_MAX_PATH_LEN + 2];
GENERIC_MAPPING GenericMapping;
......@@ -2029,27 +2137,33 @@ __gnat_is_writable_file (char *name)
ZeroMemory (&GenericMapping, sizeof (GENERIC_MAPPING));
GenericMapping.GenericWrite = GENERIC_WRITE;
return __gnat_check_OWNER_ACL
attr->writable = __gnat_check_OWNER_ACL
(wname, FILE_WRITE_DATA | FILE_APPEND_DATA, GenericMapping)
&& !(GetFileAttributes (wname) & FILE_ATTRIBUTE_READONLY);
}
else
return !(GetFileAttributes (wname) & FILE_ATTRIBUTE_READONLY);
attr->writable = !(GetFileAttributes (wname) & FILE_ATTRIBUTE_READONLY);
#else
int ret;
int mode;
GNAT_STRUCT_STAT statbuf;
ret = GNAT_STAT (name, &statbuf);
mode = statbuf.st_mode & S_IWUSR;
return (!ret && mode);
__gnat_stat_to_attr (-1, name, attr);
#endif
}
return attr->writable;
}
int
__gnat_is_executable_file (char *name)
__gnat_is_writable_file (char *name)
{
struct file_attributes attr;
reset_attributes (&attr);
return __gnat_is_writable_file_attr (name, &attr);
}
int
__gnat_is_executable_file_attr (char* name, struct file_attributes* attr)
{
if (attr->executable == -1) {
#if defined (_WIN32) && !defined (RTX)
TCHAR wname [GNAT_MAX_PATH_LEN + 2];
GENERIC_MAPPING GenericMapping;
......@@ -2061,20 +2175,25 @@ __gnat_is_executable_file (char *name)
ZeroMemory (&GenericMapping, sizeof (GENERIC_MAPPING));
GenericMapping.GenericExecute = GENERIC_EXECUTE;
return __gnat_check_OWNER_ACL (wname, FILE_EXECUTE, GenericMapping);
attr->executable = __gnat_check_OWNER_ACL (wname, FILE_EXECUTE, GenericMapping);
}
else
return GetFileAttributes (wname) != INVALID_FILE_ATTRIBUTES
attr->executable = GetFileAttributes (wname) != INVALID_FILE_ATTRIBUTES
&& _tcsstr (wname, _T(".exe")) - wname == (int) (_tcslen (wname) - 4);
#else
int ret;
int mode;
GNAT_STRUCT_STAT statbuf;
ret = GNAT_STAT (name, &statbuf);
mode = statbuf.st_mode & S_IXUSR;
return (!ret && mode);
__gnat_stat_to_attr (-1, name, attr);
#endif
}
return attr->executable;
}
int
__gnat_is_executable_file (char *name)
{
struct file_attributes attr;
reset_attributes (&attr);
return __gnat_is_executable_file_attr (name, &attr);
}
void
......@@ -2193,21 +2312,31 @@ __gnat_set_non_readable (char *name)
}
int
__gnat_is_symbolic_link (char *name ATTRIBUTE_UNUSED)
__gnat_is_symbolic_link_attr (char* name, struct file_attributes* attr)
{
if (attr->symbolic_link == -1) {
#if defined (__vxworks) || defined (__nucleus__)
return 0;
attr->symbolic_link = 0;
#elif defined (_AIX) || defined (__APPLE__) || defined (__unix__)
int ret;
GNAT_STRUCT_STAT statbuf;
ret = GNAT_LSTAT (name, &statbuf);
return (!ret && S_ISLNK (statbuf.st_mode));
attr->symbolic_link = (!ret && S_ISLNK (statbuf.st_mode));
#else
return 0;
attr->symbolic_link = 0;
#endif
}
return attr->symbolic_link;
}
int
__gnat_is_symbolic_link (char *name ATTRIBUTE_UNUSED)
{
struct file_attributes attr;
reset_attributes (&attr);
return __gnat_is_symbolic_link_attr (name, &attr);
}
#if defined (sun) && defined (__SVR4)
......
......@@ -68,6 +68,30 @@ typedef long long OS_Time;
typedef long OS_Time;
#endif
/* A lazy cache for the attributes of a file. On some systems, a single call to
stat() will give all this information, so it is better than doing a system
call every time. On other systems this require several system calls.
*/
struct file_attributes {
short exists;
short writable;
short readable;
short executable;
short symbolic_link;
short regular;
short directory;
OS_Time timestamp;
long file_length;
};
/* WARNING: changing the size here might require changing the constant
* File_Attributes_Size in osint.ads (which should be big enough to
* fit the above struct on any system)
*/
extern int __gnat_max_path_len;
extern OS_Time __gnat_current_time (void);
extern void __gnat_current_time_string (char *);
......@@ -130,6 +154,19 @@ extern int __gnat_is_directory (char *);
extern int __gnat_is_writable_file (char *);
extern int __gnat_is_readable_file (char *name);
extern int __gnat_is_executable_file (char *name);
extern void reset_attributes (struct file_attributes* attr);
extern long __gnat_file_length_attr (int, char *, struct file_attributes *);
extern OS_Time __gnat_file_time_name_attr (char *, struct file_attributes *);
extern OS_Time __gnat_file_time_fd_attr (int, struct file_attributes *);
extern int __gnat_file_exists_attr (char *, struct file_attributes *);
extern int __gnat_is_regular_file_attr (char *, struct file_attributes *);
extern int __gnat_is_directory_attr (char *, struct file_attributes *);
extern int __gnat_is_readable_file_attr (char *, struct file_attributes *);
extern int __gnat_is_writable_file_attr (char *, struct file_attributes *);
extern int __gnat_is_executable_file_attr (char *, struct file_attributes *);
extern int __gnat_is_symbolic_link_attr (char *, struct file_attributes *);
extern void __gnat_set_non_writable (char *name);
extern void __gnat_set_writable (char *name);
extern void __gnat_set_executable (char *name);
......
......@@ -190,7 +190,7 @@ package body Bcheck is
else
ALI_Path_Id :=
Osint.Find_File ((ALIs.Table (A).Afile), Osint.Library);
Osint.Full_Lib_File_Name (ALIs.Table (A).Afile);
if Osint.Is_Readonly_Library (ALI_Path_Id) then
if Tolerate_Consistency_Errors then
Error_Msg ("?{ should be recompiled");
......
......@@ -106,13 +106,17 @@ package body Make is
Full_Source_File : File_Name_Type;
Lib_File : File_Name_Type;
Source_Unit : Unit_Name_Type;
Full_Lib_File : File_Name_Type;
Lib_File_Attr : aliased File_Attributes;
Mapping_File : Natural := No_Mapping_File;
Project : Project_Id := No_Project;
Syntax_Only : Boolean := False;
Output_Is_Object : Boolean := True;
end record;
-- Data recorded for each compilation process spawned
No_Compilation_Data : constant Compilation_Data :=
(Invalid_Pid, No_File, No_File, No_Unit_Name, No_File, Unknown_Attributes,
No_Mapping_File, No_Project);
type Comp_Data_Arr is array (Positive range <>) of Compilation_Data;
type Comp_Data_Ptr is access Comp_Data_Arr;
Running_Compile : Comp_Data_Ptr;
......@@ -741,6 +745,7 @@ package body Make is
The_Args : Argument_List;
Lib_File : File_Name_Type;
Full_Lib_File : File_Name_Type;
Lib_File_Attr : access File_Attributes;
Read_Only : Boolean;
ALI : out ALI_Id;
O_File : out File_Name_Type;
......@@ -752,7 +757,9 @@ package body Make is
-- up-to-date, then the corresponding source file needs to be recompiled.
-- In this case ALI = No_ALI_Id.
-- Full_Lib_File must be the result of calling Osint.Full_Lib_File_Name on
-- Lib_File. Precomputing it saves system calls.
-- Lib_File. Precomputing it saves system calls. Lib_File_Attr is the
-- initialized attributes of that file, which is also used to save on
-- system calls (it can safely be initialized to Unknown_Attributes).
procedure Check_Linker_Options
(E_Stamp : Time_Stamp_Type;
......@@ -1418,6 +1425,7 @@ package body Make is
The_Args : Argument_List;
Lib_File : File_Name_Type;
Full_Lib_File : File_Name_Type;
Lib_File_Attr : access File_Attributes;
Read_Only : Boolean;
ALI : out ALI_Id;
O_File : out File_Name_Type;
......@@ -1577,12 +1585,12 @@ package body Make is
Check_Object_Consistency;
begin
Check_Object_Consistency := False;
Text := Read_Library_Info_From_Full (Full_Lib_File);
Text := Read_Library_Info_From_Full (Full_Lib_File, Lib_File_Attr);
Check_Object_Consistency := Saved_Check_Object_Consistency;
end;
else
Text := Read_Library_Info_From_Full (Full_Lib_File);
Text := Read_Library_Info_From_Full (Full_Lib_File, Lib_File_Attr);
end if;
Full_Obj_File := Full_Object_File_Name;
......@@ -2418,61 +2426,21 @@ package body Make is
Initialize_ALI_Data : Boolean := True;
Max_Process : Positive := 1)
is
Source_Unit : Unit_Name_Type;
-- Current source unit
Source_File : File_Name_Type;
-- Current source file
Full_Source_File : File_Name_Type;
-- Full name of the current source file
Lib_File : File_Name_Type;
-- Current library file
Full_Lib_File : File_Name_Type;
-- Full name of the current library file
Obj_File : File_Name_Type;
-- Full name of the object file corresponding to Lib_File
Obj_Stamp : Time_Stamp_Type;
-- Time stamp of the current object file
Sfile : File_Name_Type;
-- Contains the source file of the units withed by Source_File
Uname : Unit_Name_Type;
-- Contains the unit name of the units withed by Source_File
ALI : ALI_Id;
-- ALI Id of the current ALI file
-- Comment following declarations ???
Read_Only : Boolean := False;
Compilation_OK : Boolean;
Need_To_Compile : Boolean;
Pid : Process_Id;
Text : Text_Buffer_Ptr;
Mfile : Natural := No_Mapping_File;
Mapping_File_Arg : String_Access;
-- Info on the mapping file
Need_To_Check_Standard_Library : Boolean :=
Check_Readonly_Files
and not Unique_Compile;
Mapping_File_Arg : String_Access;
Process_Created : Boolean := False;
procedure Add_Process
(Pid : Process_Id;
Sfile : File_Name_Type;
Afile : File_Name_Type;
Uname : Unit_Name_Type;
Full_Lib_File : File_Name_Type;
Lib_File_Attr : File_Attributes;
Mfile : Natural := No_Mapping_File);
-- Adds process Pid to the current list of outstanding compilation
-- processes and record the full name of the source file Sfile that
......@@ -2482,18 +2450,16 @@ package body Make is
-- array The_Mapping_File_Names.
procedure Await_Compile
(Sfile : out File_Name_Type;
Afile : out File_Name_Type;
Uname : out Unit_Name_Type;
(Data : out Compilation_Data;
OK : out Boolean);
-- Awaits that an outstanding compilation process terminates. When
-- it does set Sfile to the name of the source file that was compiled
-- Afile to the name of its library file and Uname to the name of its
-- unit. Note that this time stamp can be used to check whether the
-- it does set Data to the information registered for the corresponding
-- call to Add_Process.
-- Note that this time stamp can be used to check whether the
-- compilation did generate an object file. OK is set to True if the
-- compilation succeeded. Note that Sfile, Afile and Uname could be
-- resp. No_File, No_File and No_Name if there were no compilations
-- to wait for.
-- compilation succeeded.
-- Data could be No_Compilation_Data if there was no compilation to wait
-- for.
function Bad_Compilation_Count return Natural;
-- Returns the number of compilation failures
......@@ -2501,8 +2467,15 @@ package body Make is
procedure Check_Standard_Library;
-- Check if s-stalib.adb needs to be compiled
procedure Collect_Arguments_And_Compile (Source_Index : Int);
-- Collect arguments from project file (if any) and compile
procedure Collect_Arguments_And_Compile
(Full_Source_File : File_Name_Type;
Lib_File : File_Name_Type;
Source_Index : Int;
Pid : out Process_Id;
Process_Created : out Boolean);
-- Collect arguments from project file (if any) and compile.
-- If no compilation was attempted, Processed_Created is set to False,
-- and the value of Pid is unknown.
function Compile
(Project : Project_Id;
......@@ -2545,6 +2518,29 @@ package body Make is
procedure Record_Good_ALI (A : ALI_Id);
-- Records in the previous set the Id of an ALI file
function Must_Exit_Because_Of_Error return Boolean;
-- Return True if there were errors and the user decided to exit in such
-- a case. This waits for any outstanding compilation.
function Start_Compile_If_Possible (Args : Argument_List) return Boolean;
-- Check if there is more work that we can do (i.e. the Queue is non
-- empty). If there is, do it only if we have not yet used up all the
-- available processes.
-- Returns True if we should exit the main loop
procedure Wait_For_Available_Slot;
-- Check if we should wait for a compilation to finish. This is the case
-- if all the available processes are busy compiling sources or there is
-- nothing else to do (that is the Q is empty and there are no good ALIs
-- to process).
procedure Fill_Queue_From_ALI_Files;
-- Check if we recorded good ALI files. If yes process them now in the
-- order in which they have been recorded. There are two occasions in
-- which we record good ali files. The first is in phase 1 when, after
-- scanning an existing ALI file we realize it is up-to-date, the second
-- instance is after a successful compilation.
-----------------
-- Add_Process --
-----------------
......@@ -2554,6 +2550,8 @@ package body Make is
Sfile : File_Name_Type;
Afile : File_Name_Type;
Uname : Unit_Name_Type;
Full_Lib_File : File_Name_Type;
Lib_File_Attr : File_Attributes;
Mfile : Natural := No_Mapping_File)
is
OC1 : constant Positive := Outstanding_Compiles + 1;
......@@ -2562,14 +2560,15 @@ package body Make is
pragma Assert (OC1 <= Max_Process);
pragma Assert (Pid /= Invalid_Pid);
Running_Compile (OC1).Pid := Pid;
Running_Compile (OC1).Full_Source_File := Sfile;
Running_Compile (OC1).Lib_File := Afile;
Running_Compile (OC1).Source_Unit := Uname;
Running_Compile (OC1).Mapping_File := Mfile;
Running_Compile (OC1).Project := Arguments_Project;
Running_Compile (OC1).Syntax_Only := Syntax_Only;
Running_Compile (OC1).Output_Is_Object := Output_Is_Object;
Running_Compile (OC1) :=
(Pid => Pid,
Full_Source_File => Sfile,
Lib_File => Afile,
Full_Lib_File => Full_Lib_File,
Lib_File_Attr => Lib_File_Attr,
Source_Unit => Uname,
Mapping_File => Mfile,
Project => Arguments_Project);
Outstanding_Compiles := OC1;
end Add_Process;
......@@ -2579,21 +2578,17 @@ package body Make is
-------------------
procedure Await_Compile
(Sfile : out File_Name_Type;
Afile : out File_Name_Type;
Uname : out Unit_Name_Type;
(Data : out Compilation_Data;
OK : out Boolean)
is
Pid : Process_Id;
Project : Project_Id;
Data : Project_Compilation_Access;
Comp_Data : Project_Compilation_Access;
begin
pragma Assert (Outstanding_Compiles > 0);
Sfile := No_File;
Afile := No_File;
Uname := No_Unit_Name;
Data := No_Compilation_Data;
OK := False;
-- The loop here is a work-around for a problem on VMS; in some
......@@ -2611,21 +2606,19 @@ package body Make is
for J in Running_Compile'First .. Outstanding_Compiles loop
if Pid = Running_Compile (J).Pid then
Sfile := Running_Compile (J).Full_Source_File;
Afile := Running_Compile (J).Lib_File;
Uname := Running_Compile (J).Source_Unit;
Syntax_Only := Running_Compile (J).Syntax_Only;
Output_Is_Object := Running_Compile (J).Output_Is_Object;
Data := Running_Compile (J);
Project := Running_Compile (J).Project;
-- If a mapping file was used by this compilation,
-- get its file name for reuse by a subsequent compilation
if Running_Compile (J).Mapping_File /= No_Mapping_File then
Data := Project_Compilation_Htable.Get
Comp_Data := Project_Compilation_Htable.Get
(Project_Compilation, Project);
Data.Last_Free_Indices := Data.Last_Free_Indices + 1;
Data.Free_Mapping_File_Indices (Data.Last_Free_Indices) :=
Comp_Data.Last_Free_Indices :=
Comp_Data.Last_Free_Indices + 1;
Comp_Data.Free_Mapping_File_Indices
(Comp_Data.Last_Free_Indices) :=
Running_Compile (J).Mapping_File;
end if;
......@@ -2707,11 +2700,13 @@ package body Make is
-- Collect_Arguments_And_Compile --
-----------------------------------
procedure Collect_Arguments_And_Compile (Source_Index : Int) is
procedure Collect_Arguments_And_Compile
(Full_Source_File : File_Name_Type;
Lib_File : File_Name_Type;
Source_Index : Int;
Pid : out Process_Id;
Process_Created : out Boolean) is
begin
-- Process_Created will be set True if an attempt is made to compile
-- the source, that is if it is not in an externally built project.
Process_Created := False;
-- If we use mapping file (-P or -C switches), then get one
......@@ -2759,11 +2754,11 @@ package body Make is
Pid :=
Compile
(Arguments_Project,
File_Name_Type (Arguments_Path_Name),
Lib_File,
Source_Index,
Arguments (1 .. Last_Argument));
(Project => Arguments_Project,
S => File_Name_Type (Arguments_Path_Name),
L => Lib_File,
Source_Index => Source_Index,
Args => Arguments (1 .. Last_Argument));
Process_Created := True;
end if;
......@@ -2773,11 +2768,11 @@ package body Make is
Pid :=
Compile
(Main_Project,
Full_Source_File,
Lib_File,
Source_Index,
Arguments (1 .. Last_Argument));
(Project => Main_Project,
S => Full_Source_File,
L => Lib_File,
Source_Index => Source_Index,
Args => Arguments (1 .. Last_Argument));
Process_Created := True;
end if;
end Collect_Arguments_And_Compile;
......@@ -2994,6 +2989,119 @@ package body Make is
(Gcc_Path.all, Comp_Args (Args'First .. Comp_Last));
end Compile;
-------------------------------
-- Fill_Queue_From_ALI_Files --
-------------------------------
procedure Fill_Queue_From_ALI_Files is
ALI : ALI_Id;
Source_Index : Int;
Sfile : File_Name_Type;
Uname : Unit_Name_Type;
Unit_Name : Name_Id;
Uid : Prj.Unit_Index;
begin
while Good_ALI_Present loop
ALI := Get_Next_Good_ALI;
Source_Index := Unit_Index_Of (ALIs.Table (ALI).Afile);
-- If we are processing the library file corresponding to the
-- main source file check if this source can be a main unit.
if ALIs.Table (ALI).Sfile = Main_Source
and then Source_Index = Main_Index
then
Main_Unit := ALIs.Table (ALI).Main_Program /= None;
end if;
-- The following adds the standard library (s-stalib) to the
-- list of files to be handled by gnatmake: this file and any
-- files it depends on are always included in every bind,
-- even if they are not in the explicit dependency list.
-- Of course, it is not added if Suppress_Standard_Library
-- is True.
-- However, to avoid annoying output about s-stalib.ali being
-- read only, when "-v" is used, we add the standard library
-- only when "-a" is used.
if Need_To_Check_Standard_Library then
Check_Standard_Library;
end if;
-- Now insert in the Q the unmarked source files (i.e. those
-- which have never been inserted in the Q and hence never
-- considered). Only do that if Unique_Compile is False.
if not Unique_Compile then
for J in
ALIs.Table (ALI).First_Unit .. ALIs.Table (ALI).Last_Unit
loop
for K in
Units.Table (J).First_With .. Units.Table (J).Last_With
loop
Sfile := Withs.Table (K).Sfile;
Uname := Withs.Table (K).Uname;
-- If project files are used, find the proper source
-- to compile, in case Sfile is the spec, but there
-- is a body.
if Main_Project /= No_Project then
Get_Name_String (Uname);
Name_Len := Name_Len - 2;
Unit_Name := Name_Find;
Uid :=
Units_Htable.Get (Project_Tree.Units_HT, Unit_Name);
if Uid /= Prj.No_Unit_Index then
if Uid.File_Names (Impl) /= null
and then not Uid.File_Names (Impl).Locally_Removed
then
Sfile := Uid.File_Names (Impl).File;
Source_Index := Uid.File_Names (Impl).Index;
elsif Uid.File_Names (Spec) /= null
and then not Uid.File_Names (Spec).Locally_Removed
then
Sfile := Uid.File_Names (Spec).File;
Source_Index := Uid.File_Names (Spec).Index;
end if;
end if;
end if;
Dependencies.Append ((ALIs.Table (ALI).Sfile, Sfile));
if Is_In_Obsoleted (Sfile) then
Executable_Obsolete := True;
end if;
if Sfile = No_File then
Debug_Msg ("Skipping generic:", Withs.Table (K).Uname);
else
Source_Index := Unit_Index_Of (Withs.Table (K).Afile);
if Is_Marked (Sfile, Source_Index) then
Debug_Msg ("Skipping marked file:", Sfile);
elsif not Check_Readonly_Files
and then Is_Internal_File_Name (Sfile, False)
then
Debug_Msg ("Skipping internal file:", Sfile);
else
Insert_Q
(Sfile, Withs.Table (K).Uname, Source_Index);
Mark (Sfile, Source_Index);
end if;
end if;
end loop;
end loop;
end if;
end loop;
end Fill_Queue_From_ALI_Files;
----------------------
-- Get_Mapping_File --
----------------------
......@@ -3049,6 +3157,29 @@ package body Make is
return Good_ALI.First <= Good_ALI.Last;
end Good_ALI_Present;
--------------------------------
-- Must_Exit_Because_Of_Error --
--------------------------------
function Must_Exit_Because_Of_Error return Boolean is
Data : Compilation_Data;
Success : Boolean;
begin
if Bad_Compilation_Count > 0 and then not Keep_Going then
while Outstanding_Compiles > 0 loop
Await_Compile (Data, Success);
if not Success then
Record_Failure (Data.Full_Source_File, Data.Source_Unit);
end if;
end loop;
return True;
end if;
return False;
end Must_Exit_Because_Of_Error;
--------------------
-- Record_Failure --
--------------------
......@@ -3073,94 +3204,52 @@ package body Make is
Good_ALI.Table (Good_ALI.Last) := A;
end Record_Good_ALI;
-- Start of processing for Compile_Sources
begin
pragma Assert (Args'First = 1);
Outstanding_Compiles := 0;
Running_Compile := new Comp_Data_Arr (1 .. Max_Process);
-- Package and Queue initializations
Good_ALI.Init;
if First_Q_Initialization then
Init_Q;
end if;
if Initialize_ALI_Data then
Initialize_ALI;
Initialize_ALI_Source;
end if;
-- The following two flags affect the behavior of ALI.Set_Source_Table.
-- We set Check_Source_Files to True to ensure that source file
-- time stamps are checked, and we set All_Sources to False to
-- avoid checking the presence of the source files listed in the
-- source dependency section of an ali file (which would be a mistake
-- since the ali file may be obsolete).
Check_Source_Files := True;
All_Sources := False;
-- Only insert in the Q if it is not already done, to avoid simultaneous
-- compilations if -jnnn is used.
if not Is_Marked (Main_Source, Main_Index) then
Insert_Q (Main_Source, Index => Main_Index);
Mark (Main_Source, Main_Index);
end if;
First_Compiled_File := No_File;
Most_Recent_Obj_File := No_File;
Most_Recent_Obj_Stamp := Empty_Time_Stamp;
Main_Unit := False;
-- Keep looping until there is no more work to do (the Q is empty)
-- and all the outstanding compilations have terminated
Make_Loop : while not Empty_Q or else Outstanding_Compiles > 0 loop
-- If the user does not want to keep going in case of errors then
-- wait for the remaining outstanding compiles and then exit.
if Bad_Compilation_Count > 0 and then not Keep_Going then
while Outstanding_Compiles > 0 loop
Await_Compile
(Full_Source_File, Lib_File, Source_Unit, Compilation_OK);
if not Compilation_OK then
Record_Failure (Full_Source_File, Source_Unit);
end if;
end loop;
exit Make_Loop;
end if;
-- PHASE 1: Check if there is more work that we can do (i.e. the Q
-- is non empty). If there is, do it only if we have not yet used
-- up all the available processes.
-------------------------------
-- Start_Compile_If_Possible --
-------------------------------
if not Empty_Q and then Outstanding_Compiles < Max_Process then
declare
function Start_Compile_If_Possible
(Args : Argument_List) return Boolean
is
In_Lib_Dir : Boolean;
Need_To_Compile : Boolean;
Pid : Process_Id;
Process_Created : Boolean;
Source_File : File_Name_Type;
Full_Source_File : File_Name_Type;
Source_File_Attr : aliased File_Attributes;
-- The full name of the source file, and its attributes (size,...)
Source_Unit : Unit_Name_Type;
Source_Index : Int;
-- Index of the current unit in the current source file
Lib_File : File_Name_Type;
Full_Lib_File : File_Name_Type;
Lib_File_Attr : aliased File_Attributes;
Read_Only : Boolean := False;
ALI : ALI_Id;
-- The ALI file and its attributes (size, stamp,...)
Obj_File : File_Name_Type;
Obj_Stamp : Time_Stamp_Type;
-- The object file
begin
if not Empty_Q and then Outstanding_Compiles < Max_Process then
Extract_From_Q (Source_File, Source_Unit, Source_Index);
Full_Source_File := Osint.Full_Source_Name (Source_File);
Lib_File := Osint.Lib_File_Name
(Source_File, Source_Index);
-- Compute the location of Lib_File (involves system calls)
-- ??? Can we compute at the same time if the file is
-- writable, which would save a system call on some systems
-- (when calling Is_Readonly_Library below)
Osint.Full_Source_Name
(Source_File,
Full_File => Full_Source_File,
Attr => Source_File_Attr'Access);
Full_Lib_File := Osint.Full_Lib_File_Name (Lib_File);
Lib_File := Osint.Lib_File_Name (Source_File, Source_Index);
Osint.Full_Lib_File_Name
(Lib_File,
Lib_File => Full_Lib_File,
Attr => Lib_File_Attr);
-- If this source has already been compiled, the executable is
-- obsolete.
......@@ -3176,10 +3265,16 @@ package body Make is
-- when needed
if not In_Lib_Dir then
Read_Only :=
Full_Lib_File /= No_File
if Full_Lib_File /= No_File
and then not Check_Readonly_Files
and then Is_Readonly_Library (Full_Lib_File);
then
Get_Name_String (Full_Lib_File);
Name_Buffer (Name_Len + 1) := ASCII.NUL;
Read_Only := not Is_Writable_File
(Name_Buffer'Address, Lib_File_Attr'Access);
else
Read_Only := False;
end if;
end if;
-- If the library file is an Ada library skip it
......@@ -3255,6 +3350,7 @@ package body Make is
The_Args => Args,
Lib_File => Lib_File,
Full_Lib_File => Full_Lib_File,
Lib_File_Attr => Lib_File_Attr'Access,
Read_Only => Read_Only,
ALI => ALI,
O_File => Obj_File,
......@@ -3300,31 +3396,47 @@ package body Make is
Most_Recent_Obj_File := No_File;
if Do_Not_Execute then
exit Make_Loop;
-- Exit the main loop
return True;
end if;
end if;
if In_Place_Mode then
if Full_Lib_File = No_File then
-- If the library file was not found, then save
-- the library file near the source file.
if Full_Lib_File = No_File then
Lib_File := Osint.Lib_File_Name
(Full_Source_File, Source_Index);
Full_Lib_File := Lib_File;
else
-- If the library file was found, then save the
-- library file in the same place.
else
Lib_File := Full_Lib_File;
end if;
Lib_File_Attr := Unknown_Attributes;
else
-- We will recompile, so we'll have to guess the
-- location of the object file based on the command
-- line switches and object_dir
Full_Lib_File := No_File;
Lib_File_Attr := Unknown_Attributes;
end if;
-- Start the compilation and record it. We can do
-- this because there is at least one free process.
Collect_Arguments_And_Compile (Source_Index);
Collect_Arguments_And_Compile
(Full_Source_File => Full_Source_File,
Lib_File => Lib_File,
Source_Index => Source_Index,
Pid => Pid,
Process_Created => Process_Created);
-- Make sure we could successfully start
-- the Compilation.
......@@ -3334,34 +3446,42 @@ package body Make is
Record_Failure (Full_Source_File, Source_Unit);
else
Add_Process
(Pid,
Full_Source_File,
Lib_File,
Source_Unit,
Mfile);
(Pid => Pid,
Sfile => Full_Source_File,
Afile => Lib_File,
Uname => Source_Unit,
Mfile => Mfile,
Full_Lib_File => Full_Lib_File,
Lib_File_Attr => Lib_File_Attr);
end if;
end if;
end if;
end if;
end if;
end;
end if;
return False;
end Start_Compile_If_Possible;
-- PHASE 2: Now check if we should wait for a compilation to
-- finish. This is the case if all the available processes are
-- busy compiling sources or there is nothing else to do
-- (that is the Q is empty and there are no good ALIs to process).
-----------------------------
-- Wait_For_Available_Slot --
-----------------------------
procedure Wait_For_Available_Slot is
Compilation_OK : Boolean;
Text : Text_Buffer_Ptr;
ALI : ALI_Id;
Data : Compilation_Data;
begin
if Outstanding_Compiles = Max_Process
or else (Empty_Q
and then not Good_ALI_Present
and then Outstanding_Compiles > 0)
then
Await_Compile
(Full_Source_File, Lib_File, Source_Unit, Compilation_OK);
Await_Compile (Data, Compilation_OK);
if not Compilation_OK then
Record_Failure (Full_Source_File, Source_Unit);
Record_Failure (Data.Full_Source_File, Data.Source_Unit);
end if;
if Compilation_OK or else Keep_Going then
......@@ -3382,7 +3502,7 @@ package body Make is
and Compilation_OK
and (Output_Is_Object or Do_Bind_Step);
if Full_Lib_File = No_File then
if Data.Full_Lib_File = No_File then
-- Compute the expected location of the ALI file. This
-- can be from several places:
-- -i => in place mode. In such a case, Full_Lib_File
......@@ -3396,14 +3516,21 @@ package body Make is
if Object_Directory_Path /= null then
Name_Len := 0;
Add_Str_To_Name_Buffer (Object_Directory_Path.all);
Add_Str_To_Name_Buffer (Get_Name_String (Lib_File));
Full_Lib_File := Name_Find;
Add_Str_To_Name_Buffer
(Get_Name_String (Data.Lib_File));
Data.Full_Lib_File := Name_Find;
else
Full_Lib_File := Lib_File;
Data.Full_Lib_File := Data.Lib_File;
end if;
-- Invalidate the cache for the attributes, since the
-- file was just created
Data.Lib_File_Attr := Unknown_Attributes;
end if;
Text := Read_Library_Info_From_Full (Full_Lib_File);
Text := Read_Library_Info_From_Full
(Data.Full_Lib_File, Data.Lib_File_Attr'Access);
-- Restore Check_Object_Consistency to its initial value
......@@ -3417,8 +3544,8 @@ package body Make is
-- the unit just compiled.
if Text /= null then
ALI :=
Scan_ALI (Lib_File, Text, Ignore_ED => False, Err => True);
ALI := Scan_ALI
(Data.Lib_File, Text, Ignore_ED => False, Err => True);
if ALI = No_ALI_Id then
......@@ -3426,15 +3553,18 @@ package body Make is
if Compilation_OK then
Inform
(Lib_File,
(Data.Lib_File,
"incompatible ALI file, please recompile");
Record_Failure (Full_Source_File, Source_Unit);
Record_Failure
(Data.Full_Source_File, Data.Source_Unit);
end if;
else
Free (Text);
Record_Good_ALI (ALI);
end if;
Free (Text);
-- If we could not read the ALI file that was just generated
-- then there could be a problem reading either the ALI or the
-- corresponding object file (if Check_Object_Consistency is
......@@ -3445,137 +3575,71 @@ package body Make is
else
if Compilation_OK and not Syntax_Only then
Inform
(Lib_File,
(Data.Lib_File,
"WARNING: ALI or object file not found after compile");
Record_Failure (Full_Source_File, Source_Unit);
Record_Failure (Data.Full_Source_File, Data.Source_Unit);
end if;
end if;
end if;
end if;
end Wait_For_Available_Slot;
-- PHASE 3: Check if we recorded good ALI files. If yes process
-- them now in the order in which they have been recorded. There
-- are two occasions in which we record good ali files. The first is
-- in phase 1 when, after scanning an existing ALI file we realize
-- it is up-to-date, the second instance is after a successful
-- compilation.
while Good_ALI_Present loop
ALI := Get_Next_Good_ALI;
declare
Source_Index : Int := Unit_Index_Of (ALIs.Table (ALI).Afile);
-- Start of processing for Compile_Sources
begin
-- If we are processing the library file corresponding to the
-- main source file check if this source can be a main unit.
pragma Assert (Args'First = 1);
if ALIs.Table (ALI).Sfile = Main_Source and then
Source_Index = Main_Index
then
Main_Unit := ALIs.Table (ALI).Main_Program /= None;
end if;
Outstanding_Compiles := 0;
Running_Compile := new Comp_Data_Arr (1 .. Max_Process);
-- The following adds the standard library (s-stalib) to the
-- list of files to be handled by gnatmake: this file and any
-- files it depends on are always included in every bind,
-- even if they are not in the explicit dependency list.
-- Of course, it is not added if Suppress_Standard_Library
-- is True.
-- Package and Queue initializations
-- However, to avoid annoying output about s-stalib.ali being
-- read only, when "-v" is used, we add the standard library
-- only when "-a" is used.
Good_ALI.Init;
if Need_To_Check_Standard_Library then
Check_Standard_Library;
if First_Q_Initialization then
Init_Q;
end if;
-- Now insert in the Q the unmarked source files (i.e. those
-- which have never been inserted in the Q and hence never
-- considered). Only do that if Unique_Compile is False.
if not Unique_Compile then
for J in
ALIs.Table (ALI).First_Unit .. ALIs.Table (ALI).Last_Unit
loop
for K in
Units.Table (J).First_With .. Units.Table (J).Last_With
loop
Sfile := Withs.Table (K).Sfile;
Uname := Withs.Table (K).Uname;
-- If project files are used, find the proper source
-- to compile, in case Sfile is the spec, but there
-- is a body.
if Main_Project /= No_Project then
declare
Unit_Name : Name_Id;
Uid : Prj.Unit_Index;
begin
Get_Name_String (Uname);
Name_Len := Name_Len - 2;
Unit_Name := Name_Find;
Uid :=
Units_Htable.Get
(Project_Tree.Units_HT, Unit_Name);
if Initialize_ALI_Data then
Initialize_ALI;
Initialize_ALI_Source;
end if;
if Uid /= Prj.No_Unit_Index then
if Uid.File_Names (Impl) /= null
and then
not Uid.File_Names (Impl).Locally_Removed
then
Sfile := Uid.File_Names (Impl).File;
Source_Index :=
Uid.File_Names (Impl).Index;
-- The following two flags affect the behavior of ALI.Set_Source_Table.
-- We set Check_Source_Files to True to ensure that source file
-- time stamps are checked, and we set All_Sources to False to
-- avoid checking the presence of the source files listed in the
-- source dependency section of an ali file (which would be a mistake
-- since the ali file may be obsolete).
elsif Uid.File_Names (Spec) /= null
and then
not Uid.File_Names (Spec).Locally_Removed
then
Sfile := Uid.File_Names (Spec).File;
Source_Index :=
Uid.File_Names (Spec).Index;
end if;
end if;
end;
end if;
Check_Source_Files := True;
All_Sources := False;
Dependencies.Append ((ALIs.Table (ALI).Sfile, Sfile));
-- Only insert in the Q if it is not already done, to avoid simultaneous
-- compilations if -jnnn is used.
if Is_In_Obsoleted (Sfile) then
Executable_Obsolete := True;
if not Is_Marked (Main_Source, Main_Index) then
Insert_Q (Main_Source, Index => Main_Index);
Mark (Main_Source, Main_Index);
end if;
if Sfile = No_File then
Debug_Msg
("Skipping generic:", Withs.Table (K).Uname);
First_Compiled_File := No_File;
Most_Recent_Obj_File := No_File;
Most_Recent_Obj_Stamp := Empty_Time_Stamp;
Main_Unit := False;
else
Source_Index :=
Unit_Index_Of (Withs.Table (K).Afile);
-- Keep looping until there is no more work to do (the Q is empty)
-- and all the outstanding compilations have terminated
if Is_Marked (Sfile, Source_Index) then
Debug_Msg ("Skipping marked file:", Sfile);
Make_Loop : while not Empty_Q or else Outstanding_Compiles > 0 loop
exit Make_Loop when Must_Exit_Because_Of_Error;
exit Make_Loop when Start_Compile_If_Possible (Args);
elsif not Check_Readonly_Files
and then Is_Internal_File_Name (Sfile, False)
then
Debug_Msg ("Skipping internal file:", Sfile);
Wait_For_Available_Slot;
else
Insert_Q
(Sfile, Withs.Table (K).Uname, Source_Index);
Mark (Sfile, Source_Index);
end if;
end if;
end loop;
end loop;
end if;
end;
end loop;
-- ??? Should be done as soon as we add a Good_ALI, wouldn't it avoid
-- the need for a list of good ALI ?
Fill_Queue_From_ALI_Files;
if Display_Compilation_Progress then
Write_Str ("completed ");
......
......@@ -94,16 +94,39 @@ package body Osint is
-- Update the specified path to replace the prefix with the location
-- where GNAT is installed. See the file prefix.c in GCC for details.
function Locate_File
procedure Locate_File
(N : File_Name_Type;
T : File_Type;
Dir : Natural;
Name : String) return File_Name_Type;
Name : String;
Found : out File_Name_Type;
Attr : access File_Attributes);
-- See if the file N whose name is Name exists in directory Dir. Dir is an
-- index into the Lib_Search_Directories table if T = Library. Otherwise
-- if T = Source, Dir is an index into the Src_Search_Directories table.
-- Returns the File_Name_Type of the full file name if file found, or
-- No_File if not found.
-- On exit, Found is set to the file that was found, and Attr to a cache of
-- its attributes (at least those that have been computed so far). Reusing
-- the cache will save some system calls.
-- Attr is always reset in this call to Unknown_Attributes, even in case of
-- failure
procedure Find_File
(N : File_Name_Type;
T : File_Type;
Found : out File_Name_Type;
Attr : access File_Attributes);
-- A version of Find_File that also returns a cache of the file attributes
-- for later reuse
procedure Smart_Find_File
(N : File_Name_Type;
T : File_Type;
Found : out File_Name_Type;
Attr : out File_Attributes);
-- A version of Smart_Find_File that also returns a cache of the file
-- attributes for later reuse
function C_String_Length (S : Address) return Integer;
-- Returns length of a C string (zero for a null address)
......@@ -212,18 +235,17 @@ package body Osint is
function File_Hash (F : File_Name_Type) return File_Hash_Num;
-- Compute hash index for use by Simple_HTable
package File_Name_Hash_Table is new GNAT.HTable.Simple_HTable (
Header_Num => File_Hash_Num,
Element => File_Name_Type,
No_Element => No_File,
Key => File_Name_Type,
Hash => File_Hash,
Equal => "=");
type File_Info_Cache is record
File : File_Name_Type;
Attr : aliased File_Attributes;
end record;
No_File_Info_Cache : constant File_Info_Cache :=
(No_File, Unknown_Attributes);
package File_Stamp_Hash_Table is new GNAT.HTable.Simple_HTable (
package File_Name_Hash_Table is new GNAT.HTable.Simple_HTable (
Header_Num => File_Hash_Num,
Element => Time_Stamp_Type,
No_Element => Empty_Time_Stamp,
Element => File_Info_Cache,
No_Element => No_File_Info_Cache,
Key => File_Name_Type,
Hash => File_Hash,
Equal => "=");
......@@ -959,6 +981,33 @@ package body Osint is
return File_Hash_Num (Int (F) rem File_Hash_Num'Range_Length);
end File_Hash;
-----------------
-- File_Length --
-----------------
function File_Length
(Name : C_File_Name; Attr : access File_Attributes) return Long_Integer
is
function Internal
(F : Integer; N : C_File_Name; A : System.Address) return Long_Integer;
pragma Import (C, Internal, "__gnat_file_length_attr");
begin
return Internal (-1, Name, Attr.all'Address);
end File_Length;
---------------------
-- File_Time_Stamp --
---------------------
function File_Time_Stamp
(Name : C_File_Name; Attr : access File_Attributes) return OS_Time
is
function Internal (N : C_File_Name; A : System.Address) return OS_Time;
pragma Import (C, Internal, "__gnat_file_time_name_attr");
begin
return Internal (Name, Attr.all'Address);
end File_Time_Stamp;
----------------
-- File_Stamp --
----------------
......@@ -993,6 +1042,22 @@ package body Osint is
(N : File_Name_Type;
T : File_Type) return File_Name_Type
is
Attr : aliased File_Attributes;
Found : File_Name_Type;
begin
Find_File (N, T, Found, Attr'Access);
return Found;
end Find_File;
---------------
-- Find_File --
---------------
procedure Find_File
(N : File_Name_Type;
T : File_Type;
Found : out File_Name_Type;
Attr : access File_Attributes) is
begin
Get_Name_String (N);
......@@ -1016,7 +1081,9 @@ package body Osint is
(Hostparm.OpenVMS and then
Name_Buffer (Name_Len - 2 .. Name_Len) = "_dg")))
then
return N;
Found := N;
Attr.all := Unknown_Attributes;
return;
-- If we are trying to find the current main file just look in the
-- directory where the user said it was.
......@@ -1024,7 +1091,8 @@ package body Osint is
elsif Look_In_Primary_Directory_For_Current_Main
and then Current_Main = N
then
return Locate_File (N, T, Primary_Directory, File_Name);
Locate_File (N, T, Primary_Directory, File_Name, Found, Attr);
return;
-- Otherwise do standard search for source file
......@@ -1042,21 +1110,23 @@ package body Osint is
-- return No_File, indicating the file is not a source.
if File = Error_File_Name then
return No_File;
Found := No_File;
else
return File;
Found := File;
end if;
Attr.all := Unknown_Attributes;
return;
end if;
-- First place to look is in the primary directory (i.e. the same
-- directory as the source) unless this has been disabled with -I-
if Opt.Look_In_Primary_Dir then
File := Locate_File (N, T, Primary_Directory, File_Name);
Locate_File (N, T, Primary_Directory, File_Name, Found, Attr);
if File /= No_File then
return File;
if Found /= No_File then
return;
end if;
end if;
......@@ -1069,14 +1139,15 @@ package body Osint is
end if;
for D in Primary_Directory + 1 .. Last_Dir loop
File := Locate_File (N, T, D, File_Name);
Locate_File (N, T, D, File_Name, Found, Attr);
if File /= No_File then
return File;
if Found /= No_File then
return;
end if;
end loop;
return No_File;
Attr.all := Unknown_Attributes;
Found := No_File;
end if;
end;
end Find_File;
......@@ -1148,9 +1219,28 @@ package body Osint is
-- Full_Lib_File_Name --
------------------------
procedure Full_Lib_File_Name
(N : File_Name_Type;
Lib_File : out File_Name_Type;
Attr : out File_Attributes)
is
A : aliased File_Attributes;
begin
-- ??? seems we could use Smart_Find_File here
Find_File (N, Library, Lib_File, A'Access);
Attr := A;
end Full_Lib_File_Name;
------------------------
-- Full_Lib_File_Name --
------------------------
function Full_Lib_File_Name (N : File_Name_Type) return File_Name_Type is
Attr : File_Attributes;
File : File_Name_Type;
begin
return Find_File (N, Library);
Full_Lib_File_Name (N, File, Attr);
return File;
end Full_Lib_File_Name;
----------------------------
......@@ -1189,6 +1279,18 @@ package body Osint is
return Smart_Find_File (N, Source);
end Full_Source_Name;
----------------------
-- Full_Source_Name --
----------------------
procedure Full_Source_Name
(N : File_Name_Type;
Full_File : out File_Name_Type;
Attr : access File_Attributes) is
begin
Smart_Find_File (N, Source, Full_File, Attr.all);
end Full_Source_Name;
-------------------
-- Get_Directory --
-------------------
......@@ -1470,6 +1572,19 @@ package body Osint is
Lib_Search_Directories.Table (Primary_Directory) := new String'("");
end Initialize;
------------------
-- Is_Directory --
------------------
function Is_Directory
(Name : C_File_Name; Attr : access File_Attributes) return Boolean
is
function Internal (N : C_File_Name; A : System.Address) return Integer;
pragma Import (C, Internal, "__gnat_is_directory_attr");
begin
return Internal (Name, Attr.all'Address) /= 0;
end Is_Directory;
----------------------------
-- Is_Directory_Separator --
----------------------------
......@@ -1501,6 +1616,71 @@ package body Osint is
return not Is_Writable_File (Name_Buffer (1 .. Name_Len));
end Is_Readonly_Library;
------------------------
-- Is_Executable_File --
------------------------
function Is_Executable_File
(Name : C_File_Name; Attr : access File_Attributes) return Boolean
is
function Internal (N : C_File_Name; A : System.Address) return Integer;
pragma Import (C, Internal, "__gnat_is_executable_file_attr");
begin
return Internal (Name, Attr.all'Address) /= 0;
end Is_Executable_File;
----------------------
-- Is_Readable_File --
----------------------
function Is_Readable_File
(Name : C_File_Name; Attr : access File_Attributes) return Boolean
is
function Internal (N : C_File_Name; A : System.Address) return Integer;
pragma Import (C, Internal, "__gnat_is_readable_file_attr");
begin
return Internal (Name, Attr.all'Address) /= 0;
end Is_Readable_File;
---------------------
-- Is_Regular_File --
---------------------
function Is_Regular_File
(Name : C_File_Name; Attr : access File_Attributes) return Boolean
is
function Internal (N : C_File_Name; A : System.Address) return Integer;
pragma Import (C, Internal, "__gnat_is_regular_file_attr");
begin
return Internal (Name, Attr.all'Address) /= 0;
end Is_Regular_File;
----------------------
-- Is_Symbolic_Link --
----------------------
function Is_Symbolic_Link
(Name : C_File_Name; Attr : access File_Attributes) return Boolean
is
function Internal (N : C_File_Name; A : System.Address) return Integer;
pragma Import (C, Internal, "__gnat_is_symbolic_link_attr");
begin
return Internal (Name, Attr.all'Address) /= 0;
end Is_Symbolic_Link;
----------------------
-- Is_Writable_File --
----------------------
function Is_Writable_File
(Name : C_File_Name; Attr : access File_Attributes) return Boolean
is
function Internal (N : C_File_Name; A : System.Address) return Integer;
pragma Import (C, Internal, "__gnat_is_writable_file_attr");
begin
return Internal (Name, Attr.all'Address) /= 0;
end Is_Writable_File;
-------------------
-- Lib_File_Name --
-------------------
......@@ -1533,11 +1713,13 @@ package body Osint is
-- Locate_File --
-----------------
function Locate_File
procedure Locate_File
(N : File_Name_Type;
T : File_Type;
Dir : Natural;
Name : String) return File_Name_Type
Name : String;
Found : out File_Name_Type;
Attr : access File_Attributes)
is
Dir_Name : String_Ptr;
......@@ -1555,24 +1737,28 @@ package body Osint is
end if;
declare
Full_Name : String (1 .. Dir_Name'Length + Name'Length);
Full_Name : String (1 .. Dir_Name'Length + Name'Length + 1);
begin
Full_Name (1 .. Dir_Name'Length) := Dir_Name.all;
Full_Name (Dir_Name'Length + 1 .. Full_Name'Length) := Name;
Full_Name (Dir_Name'Length + 1 .. Full_Name'Last - 1) := Name;
Full_Name (Full_Name'Last) := ASCII.NUL;
if not Is_Regular_File (Full_Name) then
return No_File;
Attr.all := Unknown_Attributes;
if not Is_Regular_File (Full_Name'Address, Attr) then
Found := No_File;
else
-- If the file is in the current directory then return N itself
if Dir_Name'Length = 0 then
return N;
Found := N;
else
Name_Len := Full_Name'Length;
Name_Buffer (1 .. Name_Len) := Full_Name;
return Name_Enter;
Name_Len := Full_Name'Length - 1;
Name_Buffer (1 .. Name_Len) :=
Full_Name (1 .. Full_Name'Last - 1);
Found := Name_Find; -- ??? Was Name_Enter, no obvious reason
end if;
end if;
end;
......@@ -1592,11 +1778,13 @@ package body Osint is
declare
File_Name : constant String := Name_Buffer (1 .. Name_Len);
File : File_Name_Type := No_File;
Attr : aliased File_Attributes;
Last_Dir : Natural;
begin
if Opt.Look_In_Primary_Dir then
File := Locate_File (N, Source, Primary_Directory, File_Name);
Locate_File
(N, Source, Primary_Directory, File_Name, File, Attr'Access);
if File /= No_File and then T = File_Stamp (N) then
return File;
......@@ -1606,7 +1794,7 @@ package body Osint is
Last_Dir := Src_Search_Directories.Last;
for D in Primary_Directory + 1 .. Last_Dir loop
File := Locate_File (N, Source, D, File_Name);
Locate_File (N, Source, D, File_Name, File, Attr'Access);
if File /= No_File and then T = File_Stamp (File) then
return File;
......@@ -2110,10 +2298,15 @@ package body Osint is
function Read_Library_Info
(Lib_File : File_Name_Type;
Fatal_Err : Boolean := False) return Text_Buffer_Ptr is
Fatal_Err : Boolean := False) return Text_Buffer_Ptr
is
File : File_Name_Type;
Attr : aliased File_Attributes;
begin
Find_File (Lib_File, Library, File, Attr'Access);
return Read_Library_Info_From_Full
(Full_Lib_File => Find_File (Lib_File, Library),
(Full_Lib_File => File,
Lib_File_Attr => Attr'Access,
Fatal_Err => Fatal_Err);
end Read_Library_Info;
......@@ -2123,12 +2316,17 @@ package body Osint is
function Read_Library_Info_From_Full
(Full_Lib_File : File_Name_Type;
Lib_File_Attr : access File_Attributes;
Fatal_Err : Boolean := False) return Text_Buffer_Ptr
is
Lib_FD : File_Descriptor;
-- The file descriptor for the current library file. A negative value
-- indicates failure to open the specified source file.
Len : Integer;
-- Length of source file text (ALI). If it doesn't fit in an integer
-- we're probably stuck anyway (>2 gigs of source seems a lot!)
Text : Text_Buffer_Ptr;
-- Allocated text buffer
......@@ -2168,17 +2366,32 @@ package body Osint is
end if;
end if;
-- Compute the length of the file (potentially also preparing other data
-- like the timestamp and whether the file is read-only, for future use)
Len := Integer (File_Length (Name_Buffer'Address, Lib_File_Attr));
-- Check for object file consistency if requested
if Opt.Check_Object_Consistency then
Current_Full_Lib_Stamp := File_Stamp (Current_Full_Lib_Name);
-- On most systems, this does not result in an extra system call
Current_Full_Lib_Stamp := OS_Time_To_GNAT_Time
(File_Time_Stamp (Name_Buffer'Address, Lib_File_Attr));
-- ??? One system call here
Current_Full_Obj_Stamp := File_Stamp (Current_Full_Obj_Name);
if Current_Full_Obj_Stamp (1) = ' ' then
-- When the library is readonly always assume object is consistent
-- The call to Is_Writable_File only results in a system call on
-- some systems, but in most cases it has already been computed as
-- part of the call to File_Length above.
if Is_Readonly_Library (Current_Full_Lib_Name) then
Get_Name_String (Current_Full_Lib_Name);
Name_Buffer (Name_Len + 1) := ASCII.NUL;
if not Is_Writable_File (Name_Buffer'Address, Lib_File_Attr) then
Current_Full_Obj_Stamp := Current_Full_Lib_Stamp;
elsif Fatal_Err then
......@@ -2203,10 +2416,6 @@ package body Osint is
-- Read data from the file
declare
Len : constant Integer := Integer (File_Length (Lib_FD));
-- Length of source file text. If it doesn't fit in an integer
-- we're probably stuck anyway (>2 gigs of source seems a lot!)
Actual_Len : Integer := 0;
Lo : constant Text_Ptr := 0;
......@@ -2482,21 +2691,23 @@ package body Osint is
(N : File_Name_Type;
T : File_Type) return Time_Stamp_Type
is
Time_Stamp : Time_Stamp_Type;
File : File_Name_Type;
Attr : aliased File_Attributes;
begin
if not File_Cache_Enabled then
return File_Stamp (Find_File (N, T));
Find_File (N, T, File, Attr'Access);
else
Smart_Find_File (N, T, File, Attr);
end if;
Time_Stamp := File_Stamp_Hash_Table.Get (N);
if Time_Stamp (1) = ' ' then
Time_Stamp := File_Stamp (Smart_Find_File (N, T));
File_Stamp_Hash_Table.Set (N, Time_Stamp);
if File = No_File then
return Empty_Time_Stamp;
else
Get_Name_String (File);
Name_Buffer (Name_Len + 1) := ASCII.NUL;
return OS_Time_To_GNAT_Time
(File_Time_Stamp (Name_Buffer'Address, Attr'Access));
end if;
return Time_Stamp;
end Smart_File_Stamp;
---------------------
......@@ -2507,21 +2718,38 @@ package body Osint is
(N : File_Name_Type;
T : File_Type) return File_Name_Type
is
Full_File_Name : File_Name_Type;
File : File_Name_Type;
Attr : File_Attributes;
begin
Smart_Find_File (N, T, File, Attr);
return File;
end Smart_Find_File;
---------------------
-- Smart_Find_File --
---------------------
procedure Smart_Find_File
(N : File_Name_Type;
T : File_Type;
Found : out File_Name_Type;
Attr : out File_Attributes)
is
Info : File_Info_Cache;
begin
if not File_Cache_Enabled then
return Find_File (N, T);
Find_File (N, T, Info.File, Info.Attr'Access);
else
Info := File_Name_Hash_Table.Get (N);
if Info.File = No_File then
Find_File (N, T, Info.File, Info.Attr'Access);
File_Name_Hash_Table.Set (N, Info);
end if;
Full_File_Name := File_Name_Hash_Table.Get (N);
if Full_File_Name = No_File then
Full_File_Name := Find_File (N, T);
File_Name_Hash_Table.Set (N, Full_File_Name);
end if;
return Full_File_Name;
Found := Info.File;
Attr := Info.Attr;
end Smart_Find_File;
----------------------
......@@ -2951,6 +3179,9 @@ package body Osint is
-- Package Initialization --
----------------------------
procedure Reset_File_Attributes (Attr : System.Address);
pragma Import (C, Reset_File_Attributes, "reset_attributes");
begin
Initialization : declare
......@@ -2966,7 +3197,15 @@ begin
"__gnat_get_maximum_file_name_length");
-- Function to get maximum file name length for system
Sizeof_File_Attributes : Integer;
pragma Import (C, Sizeof_File_Attributes,
"size_of_file_attributes");
begin
pragma Assert (Sizeof_File_Attributes <= File_Attributes_Size);
Reset_File_Attributes (Unknown_Attributes'Address);
Identifier_Character_Set := Get_Default_Identifier_Character_Set;
Maximum_File_Name_Length := Get_Maximum_File_Name_Length;
......
......@@ -29,6 +29,7 @@
with Namet; use Namet;
with Types; use Types;
with System.Storage_Elements;
with System.OS_Lib; use System.OS_Lib;
with System; use System;
......@@ -230,6 +231,47 @@ package Osint is
-- this routine called with Name set to "gnat" will return "-lgnat-5.02"
-- on UNIX and Windows and -lgnat_5_02 on VMS.
---------------------
-- File attributes --
---------------------
-- The following subprograms offer services similar to those found in
-- System.OS_Lib, but with the ability to extra multiple information from
-- a single system call, depending on the system. This can result in fewer
-- system calls when reused.
-- In all these subprograms, the requested value is either read from the
-- File_Attributes parameter (resulting in no system call), or computed
-- from the disk and then cached in the File_Attributes parameter (possibly
-- along with other values).
type File_Attributes is private;
Unknown_Attributes : constant File_Attributes;
-- A cache for various attributes for a file (length, accessibility,...)
-- This must be initialized to Unknown_Attributes prior to the first call.
function Is_Directory
(Name : C_File_Name; Attr : access File_Attributes) return Boolean;
function Is_Regular_File
(Name : C_File_Name; Attr : access File_Attributes) return Boolean;
function Is_Symbolic_Link
(Name : C_File_Name; Attr : access File_Attributes) return Boolean;
-- Return the type of the file,
function File_Length
(Name : C_File_Name; Attr : access File_Attributes) return Long_Integer;
-- Return the length (number of bytes) of the file
function File_Time_Stamp
(Name : C_File_Name; Attr : access File_Attributes) return OS_Time;
-- Return the time stamp of the file
function Is_Readable_File
(Name : C_File_Name; Attr : access File_Attributes) return Boolean;
function Is_Executable_File
(Name : C_File_Name; Attr : access File_Attributes) return Boolean;
function Is_Writable_File
(Name : C_File_Name; Attr : access File_Attributes) return Boolean;
-- Return the access rights for the file
-------------------------
-- Search Dir Routines --
-------------------------
......@@ -380,6 +422,10 @@ package Osint is
-- using Read_Source_File. Calling this routine entails no source file
-- directory lookup penalty.
procedure Full_Source_Name
(N : File_Name_Type;
Full_File : out File_Name_Type;
Attr : access File_Attributes);
function Full_Source_Name (N : File_Name_Type) return File_Name_Type;
function Source_File_Stamp (N : File_Name_Type) return Time_Stamp_Type;
-- Returns the full name/time stamp of the source file whose simple name
......@@ -390,6 +436,8 @@ package Osint is
-- The source file directory lookup penalty is incurred every single time
-- the routines are called unless you have previously called
-- Source_File_Data (Cache => True). See below.
-- The procedural version also returns some file attributes for the ALI
-- file (to save on system calls later on).
function Current_File_Index return Int;
-- Return the index in its source file of the current main unit
......@@ -488,10 +536,14 @@ package Osint is
function Read_Library_Info_From_Full
(Full_Lib_File : File_Name_Type;
Lib_File_Attr : access File_Attributes;
Fatal_Err : Boolean := False) return Text_Buffer_Ptr;
-- Same as Read_Library_Info, except Full_Lib_File must contains the full
-- path to the library file (instead of having Read_Library_Info recompute
-- it)
-- it).
-- Lib_File_Attr should be an initialized set of attributes for the
-- library file (it can be initialized to Unknown_Attributes, but in
-- general will have been initialized by a previous call to Find_File).
function Full_Library_Info_Name return File_Name_Type;
function Full_Object_File_Name return File_Name_Type;
......@@ -508,6 +560,10 @@ package Osint is
-- It is an error to call Current_Object_File_Stamp if
-- Opt.Check_Object_Consistency is set to False.
procedure Full_Lib_File_Name
(N : File_Name_Type;
Lib_File : out File_Name_Type;
Attr : out File_Attributes);
function Full_Lib_File_Name (N : File_Name_Type) return File_Name_Type;
-- Returns the full name of library file N. N should not include
-- path information. Note that if the file cannot be located No_File is
......@@ -515,6 +571,8 @@ package Osint is
-- for the second (this is not an error situation). The full name includes
-- the appropriate directory information. The library file directory lookup
-- penalty is incurred every single time this routine is called.
-- The procedural version also returns some file attributes for the ALI
-- file (to save on system calls later on).
function Lib_File_Name
(Source_File : File_Name_Type;
......@@ -660,4 +718,18 @@ private
-- detected, the file being written is deleted, and a fatal error is
-- signalled.
File_Attributes_Size : constant Integer := 50;
-- This should be big enough to fit a "struct file_attributes" on any
-- system. It doesn't matter if it is too big (which avoids the need for
-- either mapping the struct exactly or importing the sizeof from C, which
-- would result in dynamic code)
type File_Attributes is
array (1 .. File_Attributes_Size)
of System.Storage_Elements.Storage_Element;
Unknown_Attributes : constant File_Attributes := (others => 0);
-- Will be initialized properly at elaboration (for efficiency later on,
-- avoid function calls every time we want to reset the attributes).
end Osint;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment