diff --git a/all.sas b/all.sas
index eac75d8..aebb823 100644
--- a/all.sas
+++ b/all.sas
@@ -1642,24 +1642,47 @@ Usage:
recognise this and fetch the log of the parent session instead)
@li STP environments must finish cleanly to avoid the log being sent to
_webout. To assist with this, we also run stpsrvset('program error', 0)
- and set SYSCC=0. For 9.4m3 we take a unique approach - we open a macro
- but don't close it! This provides a graceful abort, EXCEPT when called
- called within a %include within a macro (and that macro contains additional
- logic). See mp_abort.test.nofix.sas for the example case.
- If you know of another way to gracefully abort a 9.4m3 STP session, we'd
- love to hear about it!
+ and set SYSCC=0. We take a unique "soft abort" approach - we open a macro
+ but don't close it! This works everywhere EXCEPT inside a \%include inside
+ a macro. For that, we recommend you use mp_include.sas to perform the
+ include, and then call \%mp_abort(mode=INCLUDE) from the source program (ie,
+ OUTSIDE of the top-parent macro).
@param mac= to contain the name of the calling macro
@param msg= message to be returned
@param iftrue= supply a condition under which the macro should be executed.
+ @param errds= (work.mp_abort_errds) There is no clean way to end a process
+ within a %include called within a macro. Furthermore, there is no way to
+ test if a macro is called within a %include. To handle this particular
+ scenario, the %include should be switched for the mp_include.sas macro.
+ This provides an indicator that we are running a macro within a \%include
+ (`_SYSINCLUDEFILEDEVICE`) and allows us to provide a dataset with the abort
+ values (msg, mac).
+ We can then run an abort cancel FILE to stop the include running, and pass
+ the dataset back to the calling program to run a regular \%mp_abort().
+ The dataset will contain the following fields:
+ @li iftrue (1=1)
+ @li msg (the message)
+ @li mac (the mac param)
- @version 9.4M3
+ @param mode= (REGULAR) If mode=INCLUDE then the &errds dataset is checked for
+ an abort status.
+ Valid values:
+ @li REGULAR (default)
+ @li INCLUDE
+
+
Related Macros
+ @li mp_include.sas
+
+ @version 9.4
@author Allan Bowe
@cond
**/
%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)
+ , errds=work.mp_abort_errds
+ , mode=REGULAR
)/*/STORE SOURCE*/;
%global sysprocessmode sysprocessname;
@@ -1670,9 +1693,38 @@ Usage:
%if %length(&mac)>0 %then %put NOTE- called by &mac;
%put NOTE - &msg;
+ %if %symexist(_SYSINCLUDEFILEDEVICE) %then %do;
+ %if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;
+ data &errds;
+ iftrue='1=1';
+ length mac $100 msg $5000;
+ mac=symget('mac');
+ msg=symget('msg');
+ run;
+ data _null_;
+ abort cancel FILE;
+ run;
+ %return;
+ %end;
+ %end;
+
/* Stored Process Server web app context */
- %if %symexist(_metaperson) or "&SYSPROCESSNAME "="Compute Server " %then %do;
+ %if %symexist(_metaperson)
+ or "&SYSPROCESSNAME "="Compute Server "
+ or &mode=INCLUDE
+ %then %do;
options obs=max replace nosyntaxcheck mprint;
+ %if &mode=INCLUDE %then %do;
+ data _null_;
+ set &errds;
+ call symputx('iftrue',iftrue,'l');
+ call symputx('mac',mac,'l');
+ call symputx('msg',msg,'l');
+ putlog (_all_)(=);
+ run;
+ %if (&iftrue)=0 %then %return;
+ %end;
+
/* extract log errs / warns, if exist */
%local logloc logline;
%global logmsg; /* capture global messages */
@@ -1759,7 +1811,9 @@ Usage:
put ',"_PROGRAM" : ' _PROGRAM ;
put ",""SYSCC"" : ""&syscc"" ";
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
+ put ",""SYSHOSTNAME"" : ""&syshostname"" ";
put ",""SYSJOBID"" : ""&sysjobid"" ";
+ put ",""SYSSITE"" : ""&syssite"" ";
sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong;
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" ";
@@ -1776,11 +1830,22 @@ Usage:
rc=stpsrvset('program error', 0);
call symputx("syscc",0,"g");
run;
- %if "%substr(&sysvlong.xxxxxxxxx,1,9)" ne "9.04.01M3" %then %do;
- %put NOTE: Ending SAS session due to:;
- %put NOTE- &msg;
- endsas;
- %end;
+ /**
+ * endsas kills 9.4m3 deployments by orphaning multibridges.
+ * Abort variants are ungraceful (non zero return code)
+ * This approach lets SAS run silently until the end :-)
+ * Caution - fails when called within a %include within a macro
+ * Use mp_include() to handle this.
+ */
+ filename skip temp;
+ data _null_;
+ file skip;
+ put '%macro skip();';
+ comment '%mend skip; -> fix lint ';
+ put '%macro skippy();';
+ comment '%mend skippy; -> fix lint ';
+ run;
+ %inc skip;
%end;
%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
/* endsas kills the session making it harder to fetch results */
@@ -1796,24 +1861,6 @@ Usage:
abort cancel nolist;
run;
%end;
- %else %if "%substr(&sysvlong.xxxxxxxxx,1,9)" = "9.04.01M3" %then %do;
- /**
- * endsas kills 9.4m3 deployments by orphaning multibridges.
- * Abort variants are ungraceful (non zero return code)
- * This approach lets SAS run silently until the end :-)
- * Caution - fails when called within a %include within a macro
- * See tests/mp_abort.test.1 for an example case.
- */
- filename skip temp;
- data _null_;
- file skip;
- put '%macro skip();';
- comment '%mend skip; -> fix lint ';
- put '%macro skippy();';
- comment '%mend skippy; -> fix lint ';
- run;
- %inc skip;
- %end;
%else %do;
%abort cancel;
%end;
@@ -5397,6 +5444,109 @@ create table &outds (rename=(
run;
%end;
%mend mp_hashdataset;/**
+ @file
+ @brief Performs a wrapped \%include
+ @details This macro wrapper is necessary if you need your included code to
+ know that it is being \%included.
+
+ If you are using %include in a regular program, you could make use of the
+ following macro variables:
+
+ @li SYSINCLUDEFILEDEVICE
+ @li SYSINCLUDEFILEDIR
+ @li SYSINCLUDEFILEFILEREF
+ @li SYSINCLUDEFILENAME
+
+ However these variables are NOT available inside a macro, as documented here:
+https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/mcrolref/n1j5tcc0n2xczyn1kg1o0606gsv9.htm
+
+ This macro can be used in place of the %include statement, and will insert
+ the following (equivalent) global variables:
+
+ @li _SYSINCLUDEFILEDEVICE
+ @li _SYSINCLUDEFILEDIR
+ @li _SYSINCLUDEFILEFILEREF
+ @li _SYSINCLUDEFILENAME
+
+ These can be used whenever testing _within a macro_. Outside of the macro,
+ the regular automatic variables will still be available (thanks to a
+ concatenated file list in the include statement).
+
+ Example usage:
+
+ filename example temp;
+ data _null_;
+ file example;
+ put '%macro test();';
+ put '%put &=_SYSINCLUDEFILEFILEREF;';
+ put '%put &=SYSINCLUDEFILEFILEREF;';
+ put '%mend; %test()';
+ put '%put &=SYSINCLUDEFILEFILEREF;';
+ run;
+ %mp_include(example)
+
+ @param [in] fileref The fileref of the file to be included. Must be provided.
+ @param [in] prefix= (_) The prefix to apply to the global variables.
+ @param [in] opts= (SOURCE2) The options to apply to the %inc statement
+ @param [in] errds= (work.mp_abort_errds) There is no clean way to end a
+ process within a %include called within a macro. Furthermore, there is no
+ way to test if a macro is called within a %include. To handle this
+ particular scenario, the %mp_abort() macro will test for the existence of
+ the `_SYSINCLUDEFILEDEVICE` variable and return the outputs (msg,mac) inside
+ this dataset.
+ It will then run an abort cancel FILE to stop the include running, and pass
+ the dataset back.
+ NOTE - it is NOT possible to read this dataset as part of _this_ macro -
+ when running abort cancel FILE, ALL macros are closed, so instead it is
+ necessary to invoke "%mp_abort(mode=INCLUDE)" OUTSIDE of any macro wrappers.
+
+
+ @version 9.4
+ @author Allan Bowe
+
+ SAS Macros
+ @li mf_getuniquefileref.sas
+ @li mp_abort.sas
+
+**/
+
+%macro mp_include(fileref
+ ,prefix=_
+ ,opts=SOURCE2
+ ,errds=work.mp_abort_errds
+)/*/STORE SOURCE*/;
+
+/* prepare precode */
+%local tempref;
+%let tempref=%mf_getuniquefileref();
+data _null_;
+ file &tempref;
+ set sashelp.vextfl(where=(fileref="%upcase(&fileref)"));
+ put '%let _SYSINCLUDEFILEDEVICE=' xengine ';';
+ name=scan(xpath,-1,'/\');
+ put '%let _SYSINCLUDEFILENAME=' name ';';
+ path=subpad(xpath,1,length(xpath)-length(name)-1);
+ put '%let _SYSINCLUDEFILEDIR=' path ';';
+ put '%let _SYSINCLUDEFILEFILEREF=' "&fileref;";
+run;
+
+/* prepare the errds */
+data &errds;
+ length msg mac $1000;
+ iftrue='1=0';
+run;
+
+/* include the include */
+%inc &tempref &fileref/&opts;
+
+%mp_abort(iftrue= (&syscc ne 0)
+ ,mac=%str(&_SYSINCLUDEFILEDIR/&_SYSINCLUDEFILENAME)
+ ,msg=%str(syscc=&syscc after executing &_SYSINCLUDEFILENAME)
+)
+
+filename &tempref clear;
+
+%mend mp_include;/**
@file mp_jsonout.sas
@brief Writes JSON in SASjs format to a fileref
@details PROC JSON is faster but will produce errs like the ones below if
diff --git a/base/mp_abort.sas b/base/mp_abort.sas
index b880aed..e4d9ae0 100644
--- a/base/mp_abort.sas
+++ b/base/mp_abort.sas
@@ -15,24 +15,47 @@
recognise this and fetch the log of the parent session instead)
@li STP environments must finish cleanly to avoid the log being sent to
_webout. To assist with this, we also run stpsrvset('program error', 0)
- and set SYSCC=0. For 9.4m3 we take a unique approach - we open a macro
- but don't close it! This provides a graceful abort, EXCEPT when called
- called within a %include within a macro (and that macro contains additional
- logic). See mp_abort.test.nofix.sas for the example case.
- If you know of another way to gracefully abort a 9.4m3 STP session, we'd
- love to hear about it!
+ and set SYSCC=0. We take a unique "soft abort" approach - we open a macro
+ but don't close it! This works everywhere EXCEPT inside a \%include inside
+ a macro. For that, we recommend you use mp_include.sas to perform the
+ include, and then call \%mp_abort(mode=INCLUDE) from the source program (ie,
+ OUTSIDE of the top-parent macro).
@param mac= to contain the name of the calling macro
@param msg= message to be returned
@param iftrue= supply a condition under which the macro should be executed.
+ @param errds= (work.mp_abort_errds) There is no clean way to end a process
+ within a %include called within a macro. Furthermore, there is no way to
+ test if a macro is called within a %include. To handle this particular
+ scenario, the %include should be switched for the mp_include.sas macro.
+ This provides an indicator that we are running a macro within a \%include
+ (`_SYSINCLUDEFILEDEVICE`) and allows us to provide a dataset with the abort
+ values (msg, mac).
+ We can then run an abort cancel FILE to stop the include running, and pass
+ the dataset back to the calling program to run a regular \%mp_abort().
+ The dataset will contain the following fields:
+ @li iftrue (1=1)
+ @li msg (the message)
+ @li mac (the mac param)
- @version 9.4M3
+ @param mode= (REGULAR) If mode=INCLUDE then the &errds dataset is checked for
+ an abort status.
+ Valid values:
+ @li REGULAR (default)
+ @li INCLUDE
+
+ Related Macros
+ @li mp_include.sas
+
+ @version 9.4
@author Allan Bowe
@cond
**/
%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)
+ , errds=work.mp_abort_errds
+ , mode=REGULAR
)/*/STORE SOURCE*/;
%global sysprocessmode sysprocessname;
@@ -43,9 +66,38 @@
%if %length(&mac)>0 %then %put NOTE- called by &mac;
%put NOTE - &msg;
+ %if %symexist(_SYSINCLUDEFILEDEVICE) %then %do;
+ %if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;
+ data &errds;
+ iftrue='1=1';
+ length mac $100 msg $5000;
+ mac=symget('mac');
+ msg=symget('msg');
+ run;
+ data _null_;
+ abort cancel FILE;
+ run;
+ %return;
+ %end;
+ %end;
+
/* Stored Process Server web app context */
- %if %symexist(_metaperson) or "&SYSPROCESSNAME "="Compute Server " %then %do;
+ %if %symexist(_metaperson)
+ or "&SYSPROCESSNAME "="Compute Server "
+ or &mode=INCLUDE
+ %then %do;
options obs=max replace nosyntaxcheck mprint;
+ %if &mode=INCLUDE %then %do;
+ data _null_;
+ set &errds;
+ call symputx('iftrue',iftrue,'l');
+ call symputx('mac',mac,'l');
+ call symputx('msg',msg,'l');
+ putlog (_all_)(=);
+ run;
+ %if (&iftrue)=0 %then %return;
+ %end;
+
/* extract log errs / warns, if exist */
%local logloc logline;
%global logmsg; /* capture global messages */
@@ -132,7 +184,9 @@
put ',"_PROGRAM" : ' _PROGRAM ;
put ",""SYSCC"" : ""&syscc"" ";
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
+ put ",""SYSHOSTNAME"" : ""&syshostname"" ";
put ",""SYSJOBID"" : ""&sysjobid"" ";
+ put ",""SYSSITE"" : ""&syssite"" ";
sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong;
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" ";
@@ -149,11 +203,22 @@
rc=stpsrvset('program error', 0);
call symputx("syscc",0,"g");
run;
- %if "%substr(&sysvlong.xxxxxxxxx,1,9)" ne "9.04.01M3" %then %do;
- %put NOTE: Ending SAS session due to:;
- %put NOTE- &msg;
- endsas;
- %end;
+ /**
+ * endsas kills 9.4m3 deployments by orphaning multibridges.
+ * Abort variants are ungraceful (non zero return code)
+ * This approach lets SAS run silently until the end :-)
+ * Caution - fails when called within a %include within a macro
+ * Use mp_include() to handle this.
+ */
+ filename skip temp;
+ data _null_;
+ file skip;
+ put '%macro skip();';
+ comment '%mend skip; -> fix lint ';
+ put '%macro skippy();';
+ comment '%mend skippy; -> fix lint ';
+ run;
+ %inc skip;
%end;
%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
/* endsas kills the session making it harder to fetch results */
@@ -169,24 +234,6 @@
abort cancel nolist;
run;
%end;
- %else %if "%substr(&sysvlong.xxxxxxxxx,1,9)" = "9.04.01M3" %then %do;
- /**
- * endsas kills 9.4m3 deployments by orphaning multibridges.
- * Abort variants are ungraceful (non zero return code)
- * This approach lets SAS run silently until the end :-)
- * Caution - fails when called within a %include within a macro
- * See tests/mp_abort.test.1 for an example case.
- */
- filename skip temp;
- data _null_;
- file skip;
- put '%macro skip();';
- comment '%mend skip; -> fix lint ';
- put '%macro skippy();';
- comment '%mend skippy; -> fix lint ';
- run;
- %inc skip;
- %end;
%else %do;
%abort cancel;
%end;
diff --git a/base/mp_include.sas b/base/mp_include.sas
new file mode 100644
index 0000000..678d9e8
--- /dev/null
+++ b/base/mp_include.sas
@@ -0,0 +1,104 @@
+/**
+ @file
+ @brief Performs a wrapped \%include
+ @details This macro wrapper is necessary if you need your included code to
+ know that it is being \%included.
+
+ If you are using %include in a regular program, you could make use of the
+ following macro variables:
+
+ @li SYSINCLUDEFILEDEVICE
+ @li SYSINCLUDEFILEDIR
+ @li SYSINCLUDEFILEFILEREF
+ @li SYSINCLUDEFILENAME
+
+ However these variables are NOT available inside a macro, as documented here:
+https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/mcrolref/n1j5tcc0n2xczyn1kg1o0606gsv9.htm
+
+ This macro can be used in place of the %include statement, and will insert
+ the following (equivalent) global variables:
+
+ @li _SYSINCLUDEFILEDEVICE
+ @li _SYSINCLUDEFILEDIR
+ @li _SYSINCLUDEFILEFILEREF
+ @li _SYSINCLUDEFILENAME
+
+ These can be used whenever testing _within a macro_. Outside of the macro,
+ the regular automatic variables will still be available (thanks to a
+ concatenated file list in the include statement).
+
+ Example usage:
+
+ filename example temp;
+ data _null_;
+ file example;
+ put '%macro test();';
+ put '%put &=_SYSINCLUDEFILEFILEREF;';
+ put '%put &=SYSINCLUDEFILEFILEREF;';
+ put '%mend; %test()';
+ put '%put &=SYSINCLUDEFILEFILEREF;';
+ run;
+ %mp_include(example)
+
+ @param [in] fileref The fileref of the file to be included. Must be provided.
+ @param [in] prefix= (_) The prefix to apply to the global variables.
+ @param [in] opts= (SOURCE2) The options to apply to the %inc statement
+ @param [in] errds= (work.mp_abort_errds) There is no clean way to end a
+ process within a %include called within a macro. Furthermore, there is no
+ way to test if a macro is called within a %include. To handle this
+ particular scenario, the %mp_abort() macro will test for the existence of
+ the `_SYSINCLUDEFILEDEVICE` variable and return the outputs (msg,mac) inside
+ this dataset.
+ It will then run an abort cancel FILE to stop the include running, and pass
+ the dataset back.
+ NOTE - it is NOT possible to read this dataset as part of _this_ macro -
+ when running abort cancel FILE, ALL macros are closed, so instead it is
+ necessary to invoke "%mp_abort(mode=INCLUDE)" OUTSIDE of any macro wrappers.
+
+
+ @version 9.4
+ @author Allan Bowe
+
+ SAS Macros
+ @li mf_getuniquefileref.sas
+ @li mp_abort.sas
+
+**/
+
+%macro mp_include(fileref
+ ,prefix=_
+ ,opts=SOURCE2
+ ,errds=work.mp_abort_errds
+)/*/STORE SOURCE*/;
+
+/* prepare precode */
+%local tempref;
+%let tempref=%mf_getuniquefileref();
+data _null_;
+ file &tempref;
+ set sashelp.vextfl(where=(fileref="%upcase(&fileref)"));
+ put '%let _SYSINCLUDEFILEDEVICE=' xengine ';';
+ name=scan(xpath,-1,'/\');
+ put '%let _SYSINCLUDEFILENAME=' name ';';
+ path=subpad(xpath,1,length(xpath)-length(name)-1);
+ put '%let _SYSINCLUDEFILEDIR=' path ';';
+ put '%let _SYSINCLUDEFILEFILEREF=' "&fileref;";
+run;
+
+/* prepare the errds */
+data &errds;
+ length msg mac $1000;
+ iftrue='1=0';
+run;
+
+/* include the include */
+%inc &tempref &fileref/&opts;
+
+%mp_abort(iftrue= (&syscc ne 0)
+ ,mac=%str(&_SYSINCLUDEFILEDIR/&_SYSINCLUDEFILENAME)
+ ,msg=%str(syscc=&syscc after executing &_SYSINCLUDEFILENAME)
+)
+
+filename &tempref clear;
+
+%mend mp_include;
\ No newline at end of file