From 6b18f6ad542c190b20f7968ae4a88a380f69702f Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Wed, 19 Aug 2026 22:28:42 +1000 Subject: [PATCH 1/8] rtapi: drop the unused hal.h include uspace_rtapi_main.cc includes and uses nothing declared in it. The one HAL entry point it reaches, hal_comp_invoke_make(), is resolved with dlsym() through a function pointer typedef written beside the call, so the declaration in hal.h is never consulted. That include is the only thing making rtapi depend on hal. Removing it splits the tree's single 17 directory include cycle into one of 10 and one of 5, and leaves rtapi and libposemath with no cycle at all. --- src/rtapi/uspace_rtapi_main.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 5e1d6ac7d01..a36159634b1 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -57,7 +57,6 @@ #include #include "rtapi.h" -#include #include "uspace_common.h" static RtapiApp &App(); From 5842686f5bea618d20a8eba12df74a44df236d06 Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Wed, 19 Aug 2026 22:28:53 +1000 Subject: [PATCH 2/8] hal: move setps_util beside the code that links it setps_util.c is named in hal/Submakefile for the python module and in hal/utils/Submakefile for halcmd, halsh and halrmt, so hal/ was reaching down into hal/utils/ for both a source file and a header. It is shared HAL support rather than a halcmd utility, so it moves up to hal/ and its three users in hal/utils/ name the new path. The same object is built into the same four targets. With the edge gone hal and hal/utils leave the include cycle, which is down to 8 directories. --- src/hal/Submakefile | 2 +- src/hal/halmodule.cc | 2 +- src/hal/{utils => }/setps_util.c | 0 src/hal/{utils => }/setps_util.h | 0 src/hal/utils/Submakefile | 6 +++--- src/hal/utils/halcmd_commands.cc | 2 +- src/hal/utils/halrmt.cc | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) rename src/hal/{utils => }/setps_util.c (100%) rename src/hal/{utils => }/setps_util.h (100%) diff --git a/src/hal/Submakefile b/src/hal/Submakefile index 851e2c88881..ea2bfe20500 100644 --- a/src/hal/Submakefile +++ b/src/hal/Submakefile @@ -20,7 +20,7 @@ $(HALLIB).0: $(call TOOBJS, $(HALLIBSRCS)) @rm -f $@ $(Q)$(CC) $(LDFLAGS) -Wl,-soname,$(notdir $@) -shared -o $@ $^ $(HALLIB_LIBS) $(ULAPI_LDFLAGS) -HALMODULESRCS := hal/halmodule.cc hal/utils/setps_util.c hal/halquery.cc +HALMODULESRCS := hal/halmodule.cc hal/setps_util.c hal/halquery.cc PYSRCS += $(HALMODULESRCS) HALMODULE := ../lib/python/_hal.so diff --git a/src/hal/halmodule.cc b/src/hal/halmodule.cc index 115dc28fa9a..716b7354e41 100644 --- a/src/hal/halmodule.cc +++ b/src/hal/halmodule.cc @@ -28,7 +28,7 @@ #include #include "halqrec.hh" -#include "utils/setps_util.h" +#include "setps_util.h" #define EXCEPTION_IF_NOT_LIVE(retval) do { \ if(self->hal_id <= 0) { \ diff --git a/src/hal/utils/setps_util.c b/src/hal/setps_util.c similarity index 100% rename from src/hal/utils/setps_util.c rename to src/hal/setps_util.c diff --git a/src/hal/utils/setps_util.h b/src/hal/setps_util.h similarity index 100% rename from src/hal/utils/setps_util.h rename to src/hal/setps_util.h diff --git a/src/hal/utils/Submakefile b/src/hal/utils/Submakefile index 89f22bbb2fb..856b8df389f 100644 --- a/src/hal/utils/Submakefile +++ b/src/hal/utils/Submakefile @@ -2,13 +2,13 @@ HALCMDSRCS := \ hal/utils/halcmd.c \ hal/utils/halcmd_commands.cc \ hal/utils/halcmd_main.c \ - hal/utils/setps_util.c + hal/setps_util.c HALSHSRCS := \ hal/utils/halcmd.c \ hal/utils/halcmd_commands.cc \ hal/utils/halsh.c \ - hal/utils/setps_util.c + hal/setps_util.c ifneq ($(READLINE_LIBS),) HALCMDSRCS += hal/utils/halcmd_completion.c @@ -33,7 +33,7 @@ endif $(Q)$(CXX) $(LDFLAGS) -o $@ $^ $(READLINE_LIBS) -lfmt TARGETS += ../bin/halcmd -HALRMTSRCS := hal/utils/halrmt.cc hal/utils/setps_util.c +HALRMTSRCS := hal/utils/halrmt.cc hal/setps_util.c USERSRCS += $(HALRMTSRCS) ../bin/halrmt: $(call TOOBJS, $(HALRMTSRCS)) ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcncini.so.1 diff --git a/src/hal/utils/halcmd_commands.cc b/src/hal/utils/halcmd_commands.cc index 1f37bacd9f5..569293f2f4e 100644 --- a/src/hal/utils/halcmd_commands.cc +++ b/src/hal/utils/halcmd_commands.cc @@ -59,7 +59,7 @@ #include #include -#include "setps_util.h" +#include "hal/setps_util.h" static int unloadrt_comp(const char *mod_name); static void print_comp_info(const char **patterns); diff --git a/src/hal/utils/halrmt.cc b/src/hal/utils/halrmt.cc index bed6f70103e..c7094b0222b 100644 --- a/src/hal/utils/halrmt.cc +++ b/src/hal/utils/halrmt.cc @@ -56,7 +56,7 @@ #include #include -#include "setps_util.h" +#include "hal/setps_util.h" using namespace linuxcnc; From 14237de641f7aea7fe1a04d91b07b3c206b7b97b Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Thu, 20 Aug 2026 13:29:05 +1000 Subject: [PATCH 3/8] emc: move the interpreter and motion interface types into nml_intf Four includes in emc/nml_intf reached up into emc/rs274ngc, and one in emc/rs274ngc reached into emc/motion, for types that neither of those directories owns. state_tag_t is filled in by the interpreter and carried in motion messages and in TC_STRUCT; its own header says the info "isn't actually used by motion directly". StateTag is its C++ form, embedded in canon.hh, emc.hh and emc_nml.hh, and emc/nml_intf/Submakefile already compiled modal_state.cc into libemc from across the tree. Both move to emc/nml_intf, which is where the rest of the shared interface already lives. emc_nml.hh included rs274ngc.hh, and so the whole interpreter class hierarchy, for three array sizes. ACTIVE_G_CODES, ACTIVE_M_CODES and ACTIVE_SETTINGS move into emc/nml_intf/interp_codes.h, included by interp_base.hh where they were and by emc_nml.hh where they are used. Two includes go with that. modal_state.cc included interp_base.hh and used nothing from it. emcmodule.cc was reaching debugflags.h through the rs274ngc.hh chain and now names it. emc/nml_intf no longer depends on emc/rs274ngc and emc/rs274ngc no longer depends on emc/motion, which takes emc/rs274ngc, emc/ini and emc/pythonplugin out of the cycle. Twelve libc includes go in with it. emc_nml.hh no longer pulls in the interpreter header chain, and eight files under emc/ were reaching stdio.h, stdlib.h and unistd.h through it rather than naming them. On Debian sid that chain does not lead there and getpid() in shcom.cc and emcsvr.cc does not compile; on bookworm and trixie it still does, which is why one package job caught it and the rest did not. --- src/emc/canterp/canterp.cc | 2 +- src/emc/motion/control.c | 2 +- src/emc/motion/motion.h | 2 +- src/emc/nml_intf/Submakefile | 2 +- src/emc/nml_intf/canon.hh | 2 +- src/emc/nml_intf/emc.hh | 2 +- src/emc/nml_intf/emc_nml.hh | 4 ++-- src/emc/nml_intf/emcops.cc | 1 + src/emc/nml_intf/interp_codes.h | 23 +++++++++++++++++++ src/emc/{rs274ngc => nml_intf}/modal_state.cc | 1 - src/emc/{rs274ngc => nml_intf}/modal_state.hh | 2 +- src/emc/{motion => nml_intf}/state_tag.h | 0 src/emc/rs274ngc/Submakefile | 2 +- src/emc/rs274ngc/interp_base.hh | 8 ++----- src/emc/sai/dummyemcstat.cc | 1 + src/emc/task/emccanon.cc | 2 +- src/emc/task/emcsvr.cc | 3 +++ src/emc/task/emctask.cc | 1 + src/emc/task/taskintf.cc | 1 + src/emc/tooldata/tool_watch.cc | 1 + src/emc/tp/tc_types.h | 2 +- src/emc/usr_intf/axis/extensions/emcmodule.cc | 3 +++ src/emc/usr_intf/shcom.cc | 2 ++ 23 files changed, 50 insertions(+), 19 deletions(-) create mode 100644 src/emc/nml_intf/interp_codes.h rename src/emc/{rs274ngc => nml_intf}/modal_state.cc (98%) rename src/emc/{rs274ngc => nml_intf}/modal_state.hh (98%) rename src/emc/{motion => nml_intf}/state_tag.h (100%) diff --git a/src/emc/canterp/canterp.cc b/src/emc/canterp/canterp.cc index 0f5e54f7579..51e67f6832d 100644 --- a/src/emc/canterp/canterp.cc +++ b/src/emc/canterp/canterp.cc @@ -59,7 +59,7 @@ #include "nml_intf/interp_return.hh" #include "nml_intf/canon.hh" #include "rs274ngc/interp_base.hh" -#include "rs274ngc/modal_state.hh" +#include "nml_intf/modal_state.hh" static char the_command[LINELEN] = { 0 }; // our current command static char the_command_name[LINELEN] = { 0 }; // just the name part diff --git a/src/emc/motion/control.c b/src/emc/motion/control.c index 593c337e071..cdc87901a55 100644 --- a/src/emc/motion/control.c +++ b/src/emc/motion/control.c @@ -34,7 +34,7 @@ #include "config.h" #include "homing.h" #include "axis.h" -#include "state_tag.h" +#include "../nml_intf/state_tag.h" // Mark strings for translation, but defer translation to userspace #define _(s) (s) diff --git a/src/emc/motion/motion.h b/src/emc/motion/motion.h index aad1d345730..1312b5e45dd 100644 --- a/src/emc/motion/motion.h +++ b/src/emc/motion/motion.h @@ -72,7 +72,7 @@ to another. #include #include "simple_tp.h" -#include "state_tag.h" +#include "../nml_intf/state_tag.h" #include "../tp/tp_types.h" // define a special value to denote an invalid motion ID diff --git a/src/emc/nml_intf/Submakefile b/src/emc/nml_intf/Submakefile index 2d2577ab7a4..8edb2e4d573 100644 --- a/src/emc/nml_intf/Submakefile +++ b/src/emc/nml_intf/Submakefile @@ -1,7 +1,7 @@ LIBEMCSRCS := \ emc/nml_intf/emcglb.c \ - emc/rs274ngc/modal_state.cc \ + emc/nml_intf/modal_state.cc \ emc/nml_intf/emc.cc \ emc/nml_intf/emcpose.c \ emc/nml_intf/emcargs.cc \ diff --git a/src/emc/nml_intf/canon.hh b/src/emc/nml_intf/canon.hh index 31cbb3cb3c8..916b3e92971 100644 --- a/src/emc/nml_intf/canon.hh +++ b/src/emc/nml_intf/canon.hh @@ -20,7 +20,7 @@ #include "emctool.h" #include "canon_position.hh" #include // Just for EMCMOT_NUM_SPINDLES -#include "rs274ngc/modal_state.hh" +#include "modal_state.hh" /* This is the header file that all applications that use the diff --git a/src/emc/nml_intf/emc.hh b/src/emc/nml_intf/emc.hh index b70fc4504a7..2738b34144b 100644 --- a/src/emc/nml_intf/emc.hh +++ b/src/emc/nml_intf/emc.hh @@ -19,7 +19,7 @@ #include "libnml/nml/nml_type.hh" #include "motion_types.h" #include -#include "rs274ngc/modal_state.hh" +#include "modal_state.hh" // Forward class declarations class EMC_JOINT_STAT; diff --git a/src/emc/nml_intf/emc_nml.hh b/src/emc/nml_intf/emc_nml.hh index 717de3ce619..d502bca759a 100644 --- a/src/emc/nml_intf/emc_nml.hh +++ b/src/emc/nml_intf/emc_nml.hh @@ -20,9 +20,9 @@ #include "libnml/rcs/rcs.hh" #include "libnml/nml/cmd_msg.hh" #include "libnml/nml/stat_msg.hh" -#include "rs274ngc/modal_state.hh" +#include "modal_state.hh" #include "canon.hh" // CANON_TOOL_TABLE, CANON_UNITS -#include "rs274ngc/rs274ngc.hh" // ACTIVE_G_CODES, etc +#include "interp_codes.h" // ACTIVE_G_CODES, etc // ------------------ // CLASS DECLARATIONS diff --git a/src/emc/nml_intf/emcops.cc b/src/emc/nml_intf/emcops.cc index d58c87c637a..c7eae4d7a6b 100644 --- a/src/emc/nml_intf/emcops.cc +++ b/src/emc/nml_intf/emcops.cc @@ -15,6 +15,7 @@ * Last change: ********************************************************************/ +#include #include "emc.hh" #include "emc_nml.hh" #include "tooldata/tooldata.hh" diff --git a/src/emc/nml_intf/interp_codes.h b/src/emc/nml_intf/interp_codes.h new file mode 100644 index 00000000000..a4fe6403fa7 --- /dev/null +++ b/src/emc/nml_intf/interp_codes.h @@ -0,0 +1,23 @@ +#ifndef __LINUXCNC_INTERP_CODES_H +#define __LINUXCNC_INTERP_CODES_H +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +/* Sizes of the arrays of active codes that the interpreter reports and + the status message carries. */ +#define ACTIVE_G_CODES 17 +#define ACTIVE_M_CODES 10 +#define ACTIVE_SETTINGS 5 + +#endif diff --git a/src/emc/rs274ngc/modal_state.cc b/src/emc/nml_intf/modal_state.cc similarity index 98% rename from src/emc/rs274ngc/modal_state.cc rename to src/emc/nml_intf/modal_state.cc index b735f5e01d7..ce724a2f38d 100644 --- a/src/emc/rs274ngc/modal_state.cc +++ b/src/emc/nml_intf/modal_state.cc @@ -19,7 +19,6 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ********************************************************************/ -#include "interp_base.hh" #include "modal_state.hh" #include diff --git a/src/emc/rs274ngc/modal_state.hh b/src/emc/nml_intf/modal_state.hh similarity index 98% rename from src/emc/rs274ngc/modal_state.hh rename to src/emc/nml_intf/modal_state.hh index e86c431e4f9..8b2faf468ae 100644 --- a/src/emc/rs274ngc/modal_state.hh +++ b/src/emc/nml_intf/modal_state.hh @@ -27,7 +27,7 @@ // Bring in C struct for a state tag from motion extern "C" { -#include "motion/state_tag.h" +#include "state_tag.h" } /** diff --git a/src/emc/motion/state_tag.h b/src/emc/nml_intf/state_tag.h similarity index 100% rename from src/emc/motion/state_tag.h rename to src/emc/nml_intf/state_tag.h diff --git a/src/emc/rs274ngc/Submakefile b/src/emc/rs274ngc/Submakefile index 861abbbc4df..28e974a36c5 100644 --- a/src/emc/rs274ngc/Submakefile +++ b/src/emc/rs274ngc/Submakefile @@ -17,7 +17,6 @@ LIBRS274SRCS := $(addprefix emc/rs274ngc/, \ interp_write.cc \ interp_o_word.cc \ interp_g7x.cc \ - modal_state.cc \ nurbs_additional_functions.cc \ interp_namedparams.cc \ interp_python.cc \ @@ -32,6 +31,7 @@ LIBRS274SRCS := $(addprefix emc/rs274ngc/, \ interpmodule.cc \ rs274ngc_pre.cc \ interp_inspection.cc) +LIBRS274SRCS += emc/nml_intf/modal_state.cc USERSRCS += $(LIBRS274SRCS) $(call TOOBJSDEPS, $(LIBRS274SRCS)) : EXTRAFLAGS+=-fPIC $(BOOST_DEBUG_FLAGS) diff --git a/src/emc/rs274ngc/interp_base.hh b/src/emc/rs274ngc/interp_base.hh index 21ddd71e293..5ac25b3f446 100644 --- a/src/emc/rs274ngc/interp_base.hh +++ b/src/emc/rs274ngc/interp_base.hh @@ -23,12 +23,8 @@ #include #include #include -#include "modal_state.hh" - -/* Size of certain arrays */ -#define ACTIVE_G_CODES 17 -#define ACTIVE_M_CODES 10 -#define ACTIVE_SETTINGS 5 +#include "../nml_intf/modal_state.hh" +#include "../nml_intf/interp_codes.h" class InterpBase : boost::noncopyable { public: diff --git a/src/emc/sai/dummyemcstat.cc b/src/emc/sai/dummyemcstat.cc index 314b3dbb7f4..13016c97ac9 100644 --- a/src/emc/sai/dummyemcstat.cc +++ b/src/emc/sai/dummyemcstat.cc @@ -17,6 +17,7 @@ */ // keep linker happy so TaskMod can be resolved +#include #include "libnml/rcs/rcs.hh" // NML classes, nmlErrorFormat() #include "nml_intf/emc.hh" // EMC NML #include "nml_intf/emc_nml.hh" diff --git a/src/emc/task/emccanon.cc b/src/emc/task/emccanon.cc index 21815adad51..b4685a1d26f 100644 --- a/src/emc/task/emccanon.cc +++ b/src/emc/task/emccanon.cc @@ -61,7 +61,7 @@ #include "nml_intf/interpl.hh" // interp_list #include "nml_intf/emcglb.h" // TRAJ_MAX_VELOCITY #include -#include "rs274ngc/modal_state.hh" +#include "nml_intf/modal_state.hh" #include "tooldata/tooldata.hh" #include diff --git a/src/emc/task/emcsvr.cc b/src/emc/task/emcsvr.cc index 43903d739df..a0accf8de4c 100644 --- a/src/emc/task/emcsvr.cc +++ b/src/emc/task/emcsvr.cc @@ -13,6 +13,9 @@ * Last change: ********************************************************************/ +#include +#include +#include #include #include "libnml/rcs/rcs.hh" // EMC NML diff --git a/src/emc/task/emctask.cc b/src/emc/task/emctask.cc index b598bd12b52..67465d507dd 100644 --- a/src/emc/task/emctask.cc +++ b/src/emc/task/emctask.cc @@ -13,6 +13,7 @@ * Last change: ********************************************************************/ +#include #include #include // rtapi_strlcpy() #include // struct stat diff --git a/src/emc/task/taskintf.cc b/src/emc/task/taskintf.cc index 3d0b7bc05fd..482d8bf8afe 100644 --- a/src/emc/task/taskintf.cc +++ b/src/emc/task/taskintf.cc @@ -12,6 +12,7 @@ * ********************************************************************/ +#include #include #include // DBL_MAX #include // memcpy() strncpy() diff --git a/src/emc/tooldata/tool_watch.cc b/src/emc/tooldata/tool_watch.cc index 10e01a6f7a1..4cea7e21412 100644 --- a/src/emc/tooldata/tool_watch.cc +++ b/src/emc/tooldata/tool_watch.cc @@ -19,6 +19,7 @@ ** along with this program; if not, write to the Free Software ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include #include "nml_intf/emc.hh" #include "nml_intf/emc_nml.hh" #include diff --git a/src/emc/tp/tc_types.h b/src/emc/tp/tc_types.h index c1767d56132..0621a6c14f2 100644 --- a/src/emc/tp/tc_types.h +++ b/src/emc/tp/tc_types.h @@ -18,7 +18,7 @@ #include #include "spherical_arc.h" -#include "../motion/state_tag.h" +#include "../nml_intf/state_tag.h" #define BLEND_DIST_FRACTION 0.5 /* values for endFlag */ diff --git a/src/emc/usr_intf/axis/extensions/emcmodule.cc b/src/emc/usr_intf/axis/extensions/emcmodule.cc index 9d062a509c3..0c5a5742986 100644 --- a/src/emc/usr_intf/axis/extensions/emcmodule.cc +++ b/src/emc/usr_intf/axis/extensions/emcmodule.cc @@ -18,6 +18,8 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. #define PY_SSIZE_T_CLEAN +#include +#include #include #include #include @@ -26,6 +28,7 @@ #include "libnml/rcs/rcs.hh" #include "nml_intf/emc.hh" #include "nml_intf/emc_nml.hh" +#include "nml_intf/debugflags.h" #include #include "config.h" #include diff --git a/src/emc/usr_intf/shcom.cc b/src/emc/usr_intf/shcom.cc index 6295c99a598..e1236a44536 100644 --- a/src/emc/usr_intf/shcom.cc +++ b/src/emc/usr_intf/shcom.cc @@ -14,6 +14,8 @@ * Last change: ********************************************************************/ +#include +#include #include #include From 8e9d79dbd50700105d578fca6b39e5c816e5098d Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Wed, 19 Aug 2026 22:29:20 +1000 Subject: [PATCH 4/8] emc: move emcmotcfg.h to the interface directory emcmotcfg.h holds compile-time sizes and defaults and includes nothing. It is an exported header and every user takes it as out of include/, so nothing about how it is reached changes and no source needs touching. What changes is which directory owns it: emc/kinematics (9 includes), emc/nml_intf (3) and emc/ini (1) depended on emc/motion for this header and for nothing else. emc/motion/Submakefile existed only to copy it into include/, so it goes, and emc/motion comes off SUBDIRS with it. What is left of the cycle is three small ones: libnml's five directories among themselves, emc/nml_intf with emc/tooldata, and emc/motion with emc/tp. --- src/Makefile | 4 ++-- src/emc/motion/Submakefile | 6 ------ src/emc/nml_intf/Submakefile | 1 + src/emc/{motion => nml_intf}/emcmotcfg.h | 0 4 files changed, 3 insertions(+), 8 deletions(-) delete mode 100644 src/emc/motion/Submakefile rename src/emc/{motion => nml_intf}/emcmotcfg.h (100%) diff --git a/src/Makefile b/src/Makefile index 03d5d76cd2f..a0214c850f6 100644 --- a/src/Makefile +++ b/src/Makefile @@ -194,7 +194,7 @@ SUBDIRS := \ \ $(GUI_SUBDIRS) \ emc/usr_intf/axis emc/usr_intf emc/nml_intf emc/task emc/kinematics emc/tp emc/canterp \ - emc/motion emc/ini emc/rs274ngc emc/sai emc/pythonplugin \ + emc/ini emc/rs274ngc emc/sai emc/pythonplugin \ emc/motion-logger \ emc/tooldata \ emc \ @@ -403,7 +403,7 @@ SRCHEADERS := \ hal/drivers/mesa-hostmot2/hostmot2-serial.h \ emc/linuxcnc.h \ emc/kinematics/kinematics.h \ - emc/motion/emcmotcfg.h \ + emc/nml_intf/emcmotcfg.h \ emc/ini/inifile.hh \ emc/ini/inifile.h \ emc/nml_intf/emcpos.h \ diff --git a/src/emc/motion/Submakefile b/src/emc/motion/Submakefile deleted file mode 100644 index 692801c06f1..00000000000 --- a/src/emc/motion/Submakefile +++ /dev/null @@ -1,6 +0,0 @@ - -EMCMOTIONINCS = \ - ./emc/motion/emcmotcfg.h - -$(patsubst ./emc/motion/%,../include/%,$(EMCMOTIONINCS)): ../include/%.h: ./emc/motion/%.h - cp $^ $@ diff --git a/src/emc/nml_intf/Submakefile b/src/emc/nml_intf/Submakefile index 8edb2e4d573..9c250e242dc 100644 --- a/src/emc/nml_intf/Submakefile +++ b/src/emc/nml_intf/Submakefile @@ -26,6 +26,7 @@ TARGETS += ../lib/liblinuxcnc.a @$(AR) $(ARFLAGS) $@ $^ EMCNMLINTFINCS = \ + ./emc/nml_intf/emcmotcfg.h \ ./emc/nml_intf/emcpos.h \ ./emc/nml_intf/emcpose.h \ ./emc/nml_intf/motion_types.h diff --git a/src/emc/motion/emcmotcfg.h b/src/emc/nml_intf/emcmotcfg.h similarity index 100% rename from src/emc/motion/emcmotcfg.h rename to src/emc/nml_intf/emcmotcfg.h From 3df82210d2d5e64f791249534325e2b1c1815251 Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Wed, 19 Aug 2026 22:36:06 +1000 Subject: [PATCH 5/8] scripts: add a directory dependency report for src/ Resolves every #include in the tree the way the compiler that sees it would, buckets each file into a directory at the granularity of SUBDIRS, and reports the edges between those buckets as markdown. The two compiles do not get the same -I. Userspace gets INCLUDE from src/Makefile, "." plus the "emc" added by src/emc/Submakefile. Realtime gets only what EXTRA_CFLAGS carries, $(BASEPWD) and the exported include/, so a quoted emc/... form that compiles in userspace does not compile in a realtime module. Each file is resolved under the rules of the compile it goes through, taken from the -objs lists, and .comp sources are scanned below their ;; line and resolved the way halcompile does, with the component's own directory first. The exported header set is read out of SRCHEADERS, so it follows what the build installs rather than a list of its own. What it prints: the dependency cycles, the cycle edges that are a single include, any include that would not resolve in a realtime compile, the headers reached from both compiles, and the exported headers with no user outside their own directory. That last-but-one list is the one the graph cannot give. An edge records that a header was included; it cannot say whether the header was needed to build the code or to interface to it. A header pulled in by both compiles has to serve both, so it is where that question has to be asked. This is what the four commits before it were measured with. On the tree as it stands it reports three cycles over nine directories, all inside libnml or between a pair; before them it was one cycle over seventeen. --- scripts/include-dep-report.py | 366 ++++++++++++++++++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100755 scripts/include-dep-report.py diff --git a/scripts/include-dep-report.py b/scripts/include-dep-report.py new file mode 100755 index 00000000000..2dff665f0f9 --- /dev/null +++ b/scripts/include-dep-report.py @@ -0,0 +1,366 @@ +#!/usr/bin/env python3 +"""Directory-level include dependency report for the LinuxCNC src tree. + +Resolves every #include the way the compiler does, aggregates the result to the +directory granularity of SUBDIRS, finds the strongly connected components, and +writes a markdown report. The exported-header list is read out of SRCHEADERS in +src/Makefile, so the report follows what the build actually installs. + + ./scripts/include-dep-report.py [src] > report.md +""" + +import os +import re +import sys +from collections import defaultdict + +SRC = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else "src") +SKIP_DIRS = {"objects", "autom4te.cache", "m4", "depends"} +EXTS = (".c", ".cc", ".cpp", ".h", ".hh", ".hpp", ".comp", ".icomp") +INC_RE = re.compile(r'^\s*#\s*include\s*([<"])([^>"]+)[>"]') +OBJS_RE = re.compile(r'^\s*[A-Za-z0-9_.-]+-objs\s*[:+]?=(.*)$') + +# The -I list differs between the two compiles. Userspace gets INCLUDE from +# src/Makefile, which is "." plus the "emc" added by src/emc/Submakefile. +# Realtime gets only what EXTRA_CFLAGS carries, "$(BASEPWD)" and the exported +# include/ directory, so an -Iemc form that compiles in userspace does not +# compile in a realtime module. halcompile adds the .comp's own directory. +SEARCH = {"user": ["", "emc"], "rt": [""], "comp": [""]} + + +def read_srcheaders(): + out, collecting = [], False + for line in open(os.path.join(SRC, "Makefile"), encoding="utf-8", errors="replace"): + if not collecting: + if re.match(r'^\s*SRCHEADERS\s*:?=', line): + collecting = True + line = line.split("=", 1)[1] + else: + continue + cont = line.rstrip("\n").endswith("\\") + out += [t for t in line.replace("\\", " ").split() if t.endswith((".h", ".hh"))] + if not cont: + break + return out + + +def walk_sources(): + for dirpath, dirnames, filenames in os.walk(SRC): + dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS] + for fn in filenames: + if fn.endswith(EXTS): + yield os.path.join(dirpath, fn) + + +def read_rt_sources(): + """Sources the realtime rule compiles, from the -objs lists in src/Makefile + plus the .comp files that halcompile turns into realtime modules.""" + out = set() + for line in open(os.path.join(SRC, "Makefile"), encoding="utf-8", errors="replace"): + m = OBJS_RE.match(line) + if not m: + continue + for tok in m.group(1).split(): + if tok.endswith(".o") and "$(" not in tok: + out.add(tok[:-2]) + return out + + +def rt_source_files(stems, relfiles): + """Map the -objs stems onto the source files they are built from, and add the + .comp files halcompile turns into realtime modules.""" + out = set() + for stem in stems: + for ext in (".c", ".cc", ".cpp", ".comp"): + if stem + ext in relfiles: + out.add(stem + ext) + break + for rel in relfiles: + if rel.endswith(".comp") and os.path.dirname(rel) in ( + "hal/components", "hal/drivers") and os.path.basename(rel) != "tpcomp.comp": + out.add(rel) + return out + + +def include_lines(path): + """#include lines of a source file. A .comp only becomes C after the ;; line, + so the declaration section above it is skipped.""" + body = False if path.endswith((".comp", ".icomp")) else True + for lineno, line in enumerate(open(path, encoding="utf-8", errors="replace"), 1): + if not body: + if line.rstrip("\n") == ";;": + body = True + continue + m = INC_RE.match(line) + if m: + yield lineno, m.group(1), m.group(2) + + +def module_of(rel): + parts = rel.split("/") + if len(parts) == 1: + return "src" + if parts[0] in ("emc", "hal", "libnml", "rtapi") and len(parts) > 2: + return "/".join(parts[:2]) + return parts[0] + + +def sccs(adj, nodes): + index, low, on, st, out, c = {}, {}, {}, [], [], [0] + for root in sorted(nodes): + if root in index: + continue + work = [(root, iter(sorted(adj[root])))] + index[root] = low[root] = c[0]; c[0] += 1 + st.append(root); on[root] = True + while work: + n, it = work[-1]; adv = False + for w in it: + if w not in index: + index[w] = low[w] = c[0]; c[0] += 1 + st.append(w); on[w] = True + work.append((w, iter(sorted(adj[w])))); adv = True; break + if on.get(w): + low[n] = min(low[n], index[w]) + if adv: + continue + work.pop() + if work: + low[work[-1][0]] = min(low[work[-1][0]], low[n]) + if low[n] == index[n]: + comp = [] + while True: + w = st.pop(); on[w] = False; comp.append(w) + if w == n: + break + if len(comp) > 1: + out.append(sorted(comp)) + return out + + +def analyse(): + exported = {os.path.basename(h): h for h in read_srcheaders()} + relfiles = {os.path.relpath(f, SRC): f for f in walk_sources()} + by_name = defaultdict(list) + for rel in relfiles: + by_name[os.path.basename(rel)].append(rel) + rt_sources = rt_source_files(read_rt_sources(), relfiles) + + def resolve(name, style, incdir, mode): + """Return (target, via) the way the compiler for `mode` would find it.""" + if style == '"': + cand = os.path.normpath(os.path.join(incdir, name)) if incdir else name + if cand in relfiles: + return cand, "own directory" + for base in SEARCH[mode]: + cand = os.path.normpath(os.path.join(base, name)) if base else name + if cand in relfiles: + return cand, ("-I." if not base else "-I" + base) + b = os.path.basename(name) + if b in exported: + return exported[b], "include/" + if style == '"' and len(by_name.get(b, [])) == 1: + return by_name[b][0], "unique basename" + return None, None + + edges = defaultdict(set) + exported_users = defaultdict(set) + rt_only_via_emc = [] # would not compile in a realtime module + includers = defaultdict(set) # header -> set of includers + + for rel, full in sorted(relfiles.items()): + d, src_mod = os.path.dirname(rel), module_of(rel) + if rel.endswith((".comp", ".icomp")): + modes = ["comp"] + elif rel in rt_sources: + modes = ["rt", "user"] if not rel.endswith((".h", ".hh")) else ["rt"] + else: + modes = ["user"] + for lineno, style, name in include_lines(full): + seen = {} + for mode in modes: + seen[mode] = resolve(name, style, d, mode) + target, via = seen[modes[0]] + if "rt" in seen and seen["rt"][0] is None and seen.get("user", (None,))[0]: + rt_only_via_emc.append((rel, lineno, name, seen["user"][1])) + target, via = seen["user"] + if target is None: + for mode in modes: + if seen[mode][0]: + target, via = seen[mode] + break + if target is None: + continue + includers[target].add(rel) + if os.path.basename(target) in exported: + exported_users[exported[os.path.basename(target)]].add(rel) + dst_mod = module_of(target) + if dst_mod != src_mod: + edges[(src_mod, dst_mod)].add((rel, lineno, target, style)) + + # A file is realtime-reachable if a realtime compile pulls it in, directly or + # through another header; likewise for userspace. A header in both sets is + # one whose contents have to serve both, which is where "needed to build" and + # "needed to interface" stop being the same question. + def reach(roots): + out, work = set(roots), list(roots) + while work: + cur = work.pop() + for h, users in includers.items(): + if h in out: + continue + if cur in users: + out.add(h) + work.append(h) + return out + + rt_reach = reach(rt_sources) + user_roots = {r for r in relfiles + if r not in rt_sources and not r.endswith((".h", ".hh", ".hpp"))} + user_reach = reach(user_roots) + return { + "edges": edges, "exported": exported, "exported_users": exported_users, + "rt_only_via_emc": rt_only_via_emc, "rt_sources": rt_sources, + "rt_reach": rt_reach, "user_reach": user_reach, "relfiles": relfiles, + } + + +def main(): + data = analyse() + edges = data["edges"] + exported = data["exported"] + exported_users = data["exported_users"] + nodes = {m for e in edges for m in e} + adj = defaultdict(set) + for (a, b) in edges: + adj[a].add(b) + comps = sorted(sccs(adj, nodes), key=len, reverse=True) + incycle = {m for c in comps for m in c} + intra = {k: v for k, v in edges.items() + if any(k[0] in c and k[1] in c for c in comps)} + + w = sys.stdout.write + total_sites = sum(len(v) for v in edges.values()) + intra_sites = sum(len(v) for v in intra.values()) + thin = {k: v for k, v in intra.items() if len(v) == 1} + + w("# src/ directory dependency report\n\n") + w("Generated by `scripts/include-dep-report.py`, which resolves every `#include` " + "in the tree the way the compiler that sees it would, buckets each file into a " + "directory at the granularity of `SUBDIRS`, and reports the edges between those " + "buckets.\n\n") + w("The two compiles do not get the same `-I`. Userspace gets `INCLUDE` from " + "`src/Makefile`, which is `.` plus the `emc` added by `src/emc/Submakefile`. " + "Realtime gets only what `EXTRA_CFLAGS` carries, `$(BASEPWD)` and the exported " + "`include/`, so a quoted `emc/...` form that compiles in userspace does not " + "compile in a realtime module. Each file is resolved under the rules of the " + "compile it actually goes through, taken from the `-objs` lists in `src/Makefile`; " + "`.comp` sources are scanned below their `;;` line and resolved the way " + "halcompile does, with the component's own directory ahead of the rest. The " + "exported-header set is read out of `SRCHEADERS`, so it follows what the build " + "installs rather than a list of its own. Includes that resolve outside the tree " + "are dropped.\n\n") + + w("## Summary\n\n") + w(f"- {len(nodes)} directories, {len(edges)} directory-level edges, " + f"{total_sites} include sites between directories.\n") + if comps: + w(f"- {len(comps)} dependency cycle{'s' if len(comps) != 1 else ''} covering " + f"{len(incycle)} directories, {len(intra)} edges, {intra_sites} include sites:\n") + for c in comps: + w(f" - {', '.join('`%s`' % m for m in c)}\n") + w(f"- {len(thin)} of those {len(intra)} cycle edges are **a single `#include`**.\n") + else: + w("- No dependency cycles.\n") + w(f"- {len(exported)} exported headers.\n\n") + + if comps: + w("## The cycles\n\n```mermaid\ngraph LR\n") + ids = {n: "n%d" % i for i, n in enumerate(sorted(incycle))} + for n in sorted(incycle): + w(f' {ids[n]}["{n}"]\n') + for (a, b), v in sorted(intra.items()): + w(f" {ids[a]} -->|{len(v)}| {ids[b]}\n") + w("```\n\n") + + if thin: + w("## Cycle edges that are one include\n\n") + w("Each of these is a single line. Cutting it removes a directory-level " + "dependency outright.\n\n") + w("| edge | site | include |\n|---|---|---|\n") + for (a, b), v in sorted(thin.items()): + rel, ln, tgt, style = sorted(v)[0] + close = '"' if style == '"' else '>' + w(f"| `{a}` -> `{b}` | `src/{rel}:{ln}` | " + f"`{style}{os.path.basename(tgt)}{close}` = `src/{tgt}` |\n") + w("\n") + + heavy = [(k, v) for k, v in intra.items() if len(v) > 1] + if heavy: + w("## The heavier knots\n\n
Cycle edges carrying two or more " + "includes\n\n") + w("| edge | sites | headers |\n|---|---|---|\n") + for (a, b), v in sorted(heavy, key=lambda kv: -len(kv[1])): + hdrs = defaultdict(int) + for s_ in v: + hdrs[os.path.basename(s_[2])] += 1 + txt = ", ".join(f"`{h}`" + (f" x{c}" if c > 1 else "") + for h, c in sorted(hdrs.items(), key=lambda x: (-x[1], x[0]))) + w(f"| `{a}` -> `{b}` | {len(v)} | {txt} |\n") + w("\n
\n\n") + + rtbad = data["rt_only_via_emc"] + w("## Includes that would not resolve in a realtime compile\n\n") + if not rtbad: + w("None. No realtime-compiled file reaches a header by a form that only the " + "userspace `-I` list provides.\n\n") + else: + w("| file | line | include | resolved in userspace by |\n|---|---|---|---|\n") + for rel, ln, name, via in sorted(rtbad): + w(f"| `src/{rel}` | {ln} | `{name}` | {via} |\n") + w("\n") + + both = sorted(h for h in data["rt_reach"] & data["user_reach"] + if h.endswith((".h", ".hh", ".hpp"))) + allh = [f for f in data["relfiles"] if f.endswith((".h", ".hh", ".hpp"))] + w("## Headers reached from both compiles\n\n") + w(f"{len(both)} of {len(allh)} headers are pulled in by a realtime compile and by a " + "userspace one, following includes transitively from the sources each compile " + "starts at. A header in both sets has to serve both, which is where \"what this " + "code needs to build\" and \"what other code needs to interface\" stop being the " + "same question. The graph above records the include either way and cannot tell " + "the two apart, so this is the list to read beside it.\n\n") + bydir = defaultdict(list) + for h in both: + bydir[module_of(h)].append(h) + w("| directory | headers | |\n|---|---|---|\n") + for d in sorted(bydir, key=lambda x: (-len(bydir[x]), x)): + names = ", ".join("`%s`" % os.path.basename(h) for h in sorted(bydir[d])) + w(f"| `{d}` | {len(bydir[d])} | {names} |\n") + w("\n") + + w("## Exported headers with no in-tree user outside their own directory\n\n") + w("A header reached only from its own directory is a candidate for coming off " + "`SRCHEADERS`, but not automatically: a header included by its umbrella beside " + "it, or by code in a subdirectory that falls in the same bucket, shows up here " + "too.\n\n") + w("| header | users elsewhere |\n|---|---|\n") + for h in sorted(exported.values()): + own = module_of(h) + outside = sorted({u for u in exported_users.get(h, set()) if module_of(u) != own}) + if len(outside) > 2: + continue + cell = ", ".join(f"`src/{u}`" for u in outside) or "none" + w(f"| `{h}` | {cell} |\n") + w("\n") + + w("
Full directory edge list\n\n") + w("| from | to | sites |\n|---|---|---|\n") + for (a, b), v in sorted(edges.items(), key=lambda kv: (kv[0][0], -len(kv[1]))): + mark = " (in cycle)" if (a, b) in intra else "" + w(f"| `{a}` | `{b}`{mark} | {len(v)} |\n") + w("\n
\n") + + +if __name__ == "__main__": + main() From 2fb782361cd0162b9f1e55fe722e5060cc2622a9 Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Thu, 20 Aug 2026 19:34:11 +1000 Subject: [PATCH 6/8] emc: report the unexpected tool index through rcs_print_error emcops.cc and emccanon.cc each open coded fprintf(stderr,"UNEXPECTED idx %s %d\n",__FILE__,__LINE__); for a tooldata_get() that did not return IDX_OK. emc/nml_intf/interpl.cc already reports through rcs_print_error() in the same directory and it is the channel the rest of the NML code uses, so both sites take it. emcops.cc had no other stdio call, so the added for that one line goes away again. emccanon.cc keeps its own, it has 42 more. No new directory dependency, since emc/nml_intf and emc/task both already reach libnml/rcs. The report still counts 136 edges and the same three cycles. The same line is open coded at five further sites and the same macro is defined locally in six files. That is a wider cleanup than this PR. --- src/emc/nml_intf/emcops.cc | 4 ++-- src/emc/task/emccanon.cc | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/emc/nml_intf/emcops.cc b/src/emc/nml_intf/emcops.cc index c7eae4d7a6b..320e8805bd7 100644 --- a/src/emc/nml_intf/emcops.cc +++ b/src/emc/nml_intf/emcops.cc @@ -15,7 +15,7 @@ * Last change: ********************************************************************/ -#include +#include "libnml/rcs/rcs_print.hh" #include "emc.hh" #include "emc_nml.hh" #include "tooldata/tooldata.hh" @@ -222,7 +222,7 @@ EMC_TOOL_STAT& EMC_TOOL_STAT::operator =(const EMC_TOOL_STAT& s) #else //}{ struct CANON_TOOL_TABLE tdata; if (tooldata_get(&tdata,0) != IDX_OK) { - fprintf(stderr,"UNEXPECTED idx %s %d\n",__FILE__,__LINE__); + rcs_print_error("UNEXPECTED idx %s %d\n",__FILE__,__LINE__); } toolTableCurrent = tdata; #endif //} diff --git a/src/emc/task/emccanon.cc b/src/emc/task/emccanon.cc index b4685a1d26f..30ce5d54eaf 100644 --- a/src/emc/task/emccanon.cc +++ b/src/emc/task/emccanon.cc @@ -54,6 +54,7 @@ #include #include // strncpy() #include // isspace() +#include "libnml/rcs/rcs_print.hh" #include "nml_intf/emc.hh" // EMC NML #include "nml_intf/emc_nml.hh" #include "nml_intf/canon.hh" @@ -3703,7 +3704,7 @@ CANON_TOOL_TABLE GET_EXTERNAL_TOOL_TABLE(int idx) tdata.orientation = 0; } else { if (tooldata_get(&tdata,idx) != IDX_OK) { - fprintf(stderr,"UNEXPECTED idx %s %d\n",__FILE__,__LINE__); + rcs_print_error("UNEXPECTED idx %s %d\n",__FILE__,__LINE__); } } return tdata; From bb1fa4c089c003a6f968feab66344915e204b6f3 Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Thu, 20 Aug 2026 19:43:53 +1000 Subject: [PATCH 7/8] tree: name stdio in the headers that use FILE Three exported-or-not headers put FILE * in a prototype without including stdio, so they only compile where the includer happened to have it already: emc/rs274ngc/rs274ngc_interp.hh:648 FILE *find_ngc_file(...) hal/classicladder/files.h:30 char *cl_fgets(..., FILE *stream) hal/utils/scope_usr.h:261 void write_horiz_config(FILE *fp) They name it themselves now. stdio.h is a system header, so this adds no directory dependency and the report still counts 136 edges. This does not make files.h standalone. It has no include guard around its prototypes, only around the macro block at the top, and it names StrRung, StrTimer and StrMonostable without including classicladder.h. Both are classicladder problems rather than include-path ones. --- src/emc/rs274ngc/rs274ngc_interp.hh | 1 + src/hal/classicladder/files.h | 2 ++ src/hal/utils/scope_usr.h | 1 + 3 files changed, 4 insertions(+) diff --git a/src/emc/rs274ngc/rs274ngc_interp.hh b/src/emc/rs274ngc/rs274ngc_interp.hh index ebf77786fd9..af7d27e58bc 100644 --- a/src/emc/rs274ngc/rs274ngc_interp.hh +++ b/src/emc/rs274ngc/rs274ngc_interp.hh @@ -16,6 +16,7 @@ #ifndef RS274NGC_INTERP_H #define RS274NGC_INTERP_H +#include // FILE #include "rs274ngc.hh" #include "interp_internal.hh" #include "nml_intf/interp_return.hh" diff --git a/src/hal/classicladder/files.h b/src/hal/classicladder/files.h index cfc8b99e7d3..5c88f5e4fb8 100644 --- a/src/hal/classicladder/files.h +++ b/src/hal/classicladder/files.h @@ -13,6 +13,8 @@ // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +#include // FILE + #ifndef S_LINE //#define S_LINE "" diff --git a/src/hal/utils/scope_usr.h b/src/hal/utils/scope_usr.h index 9abc1621cec..10fc39e27f6 100644 --- a/src/hal/utils/scope_usr.h +++ b/src/hal/utils/scope_usr.h @@ -37,6 +37,7 @@ */ /* import the shared declarations */ +#include // FILE #include "scope_shm.h" /*********************************************************************** From f9606a8459cbe0662b9413f5053079122be04695 Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Thu, 20 Aug 2026 21:20:03 +1000 Subject: [PATCH 8/8] scripts: read the include statement in a comp declaration section A .comp only becomes C below its ;; line, but the declaration section above it has its own include statement, "include" Header ";" at halcompile.g:53, which halcompile copies into the generated C. Reading only below the line missed 22 includes across 14 components, five of them naming an in-tree header. One is a directory edge the report did not have: mesa_pktgyro_test.comp takes hal/drivers/mesa-hostmot2/hostmot2.h, so hal/components on hal/drivers, and the count goes from 136 to 137. Headers reached from both compiles go from 73 to 77. No cycle changes. --- scripts/include-dep-report.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/scripts/include-dep-report.py b/scripts/include-dep-report.py index 2dff665f0f9..35c29e54b1a 100755 --- a/scripts/include-dep-report.py +++ b/scripts/include-dep-report.py @@ -18,6 +18,7 @@ SKIP_DIRS = {"objects", "autom4te.cache", "m4", "depends"} EXTS = (".c", ".cc", ".cpp", ".h", ".hh", ".hpp", ".comp", ".icomp") INC_RE = re.compile(r'^\s*#\s*include\s*([<"])([^>"]+)[>"]') +COMP_INC_RE = re.compile(r'^\s*include\s*([<"])([^>"]+)[>"]\s*;') OBJS_RE = re.compile(r'^\s*[A-Za-z0-9_.-]+-objs\s*[:+]?=(.*)$') # The -I list differs between the two compiles. Userspace gets INCLUDE from @@ -83,13 +84,19 @@ def rt_source_files(stems, relfiles): def include_lines(path): - """#include lines of a source file. A .comp only becomes C after the ;; line, - so the declaration section above it is skipped.""" - body = False if path.endswith((".comp", ".icomp")) else True + """Includes of a source file. A .comp only becomes C below its ;; line, but + the declaration section above it has its own `include ;` statement, + which halcompile copies into the generated C, so both are read.""" + comp = path.endswith((".comp", ".icomp")) + body = not comp for lineno, line in enumerate(open(path, encoding="utf-8", errors="replace"), 1): if not body: if line.rstrip("\n") == ";;": body = True + continue + m = COMP_INC_RE.match(line) + if m: + yield lineno, m.group(1), m.group(2) continue m = INC_RE.match(line) if m: