From 8d314fa1b1191220150df412e9b4e49a36d2ae32 Mon Sep 17 00:00:00 2001 From: Derek Bankieris Date: Mon, 17 May 2021 11:08:04 -0500 Subject: [PATCH 1/2] Zip SWIG-generated python modules Closes #1144 --- .github/workflows/test_32.yml | 2 +- .github/workflows/test_linux.yml | 1 + bin/trick-CP | 2 +- .../Trickified-Project-Libraries.md | 39 ++++++++++--------- .../install_guide/Install-Guide.md | 7 ++-- libexec/trick/make_makefile_swig | 22 +++++++---- share/trick/makefiles/Makefile.common | 8 ++-- share/trick/makefiles/trickify.mk | 32 ++++++++++----- test/.gitignore | 3 +- test/SIM_trickified/RUN_test/unit_test.py | 4 ++ test/SIM_trickified/S_define | 21 ++++++++++ test/SIM_trickified/S_overrides.mk | 2 + test/SIM_trickified/models/Baz.hh | 9 +++++ .../trickified_project/include/Bar.hh | 17 ++++++++ .../trickified_project/include/Foo.hh | 15 +++++++ .../trickified_project/trickified/.gitignore | 4 ++ .../trickified_project/trickified/Makefile | 11 ++++++ .../trickified_project/trickified/S_source.hh | 2 + .../trickified/myproject.mk | 38 ++++++++++++++++++ test/makefile | 6 ++- .../sim_services/InputProcessor/IPPython.cpp | 2 +- 21 files changed, 201 insertions(+), 46 deletions(-) create mode 100644 test/SIM_trickified/RUN_test/unit_test.py create mode 100644 test/SIM_trickified/S_define create mode 100644 test/SIM_trickified/S_overrides.mk create mode 100644 test/SIM_trickified/models/Baz.hh create mode 100644 test/SIM_trickified/trickified_project/include/Bar.hh create mode 100644 test/SIM_trickified/trickified_project/include/Foo.hh create mode 100644 test/SIM_trickified/trickified_project/trickified/.gitignore create mode 100644 test/SIM_trickified/trickified_project/trickified/Makefile create mode 100644 test/SIM_trickified/trickified_project/trickified/S_source.hh create mode 100644 test/SIM_trickified/trickified_project/trickified/myproject.mk diff --git a/.github/workflows/test_32.yml b/.github/workflows/test_32.yml index 2a0433a5..14263bb0 100644 --- a/.github/workflows/test_32.yml +++ b/.github/workflows/test_32.yml @@ -29,7 +29,7 @@ jobs: glibc.i686 glibc-devel.i686 udunits2 udunits2-devel gtest-devel.i686 java-11-openjdk java-11-openjdk-devel expat-devel.i686 which gcc-gfortran git wget gsl-devel gtest-devel gsl-devel.i686 - maven udunits2 udunits2-devel + maven udunits2 udunits2-devel zip - name: Symlink python run: | cd /usr/lib diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index 47300822..df5ca427 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -35,6 +35,7 @@ jobs: make maven cmake + zip install_gtest: echo gtest already installed conf_pkg: echo package manager already configured install_cmd: install -y diff --git a/bin/trick-CP b/bin/trick-CP index f385bb74..9f8ef66d 100755 --- a/bin/trick-CP +++ b/bin/trick-CP @@ -280,7 +280,7 @@ tidy: clean: tidy -rm -f DP_Product/DP_rt_frame DP_Product/DP_rt_itimer -rm -f DP_Product/DP_rt_jobs DP_Product/DP_rt_timeline DP_Product/DP_mem_stats - -rm -rf build trick + -rm -rf build trick trick.zip -rm -f makefile spotless: clean diff --git a/docs/documentation/building_a_simulation/Trickified-Project-Libraries.md b/docs/documentation/building_a_simulation/Trickified-Project-Libraries.md index 280d576a..913ac34e 100644 --- a/docs/documentation/building_a_simulation/Trickified-Project-Libraries.md +++ b/docs/documentation/building_a_simulation/Trickified-Project-Libraries.md @@ -1,13 +1,13 @@ During a simulation build, Trick generates several rounds of files to support data recording, checkpointing, and Python access: * Trick generates `S_source.hh` from the `S_define` -* ICG recursively builds a tree of all header files included from `S_source.hh` and generates an `io_*.cpp` and `py_*.i` file for each -* SWIG converts all `py_*.i` to `py_*.cpp` files -* Trick compiles all `io_*.cpp` and `py_*.cpp` files +* ICG recursively builds a tree of all header files included from `S_source.hh` and generates an `io_*.cpp` and `*_py.i` file for each +* SWIG converts all `*_py.i` to `*_py.cpp` files +* Trick compiles all `io_*.cpp` and `*_py.cpp` files The time required grows with the number of included header files and can represent a significant portion of the build process. During subsequent builds, only headers files that have changed are reprocessed. However, `make clean` removes all of the generated files, so the next build will have to run the entire process again. -For external libraries, which do not change from build to build, this is unnecessary and wasteful. In this case, we would like to compile the `io_*.cpp` and `py_*.cpp` files once and simply link against them during simulation build. As of version 17.1, Trick supports "Trickifying" a set of headers via the `trickify.mk` makefile. +For external libraries, which do not change from build to build, this is unnecessary and wasteful. In this case, we would like to compile the `io_*.cpp` and `*_py.cpp` files once and simply link against them during simulation build. As of version 17.1, Trick supports "Trickifying" a set of headers via the `trickify.mk` makefile. # Who's Responsible? Support of Trickification is the project owner's responsibility. While it is possible for anyone to Trickify any set of headers, you really want the project to maintain the files we're going to talk about. This will ensure that's it done correctly, in one place, and stays synchronized with the project. @@ -57,10 +57,11 @@ The result should be: trickified/ S_source.hh trickified.o + python build/ - python/ + trick/ -`trickified.o` contains all of the `io_*.cpp` and `py_*.cpp` code. The name is configurable via the `TRICKIFY_OBJECT_NAME` variable, which can include directories, which will automatically be created if necessary. `build` contains a lot of ICG and SWIG artifacts. You can't change its name or location at this time, but it's useful to keep around as it will allow you to rebuild only the parts of the project that change in the future. `python` includes a bunch of crazily-named Python modules which serve as the input file interface to the content of the header files. You can configure its location via the `TRICKIFY_PYTHON_DIR` variable. Directories will be automatically created as needed. +`trickified.o` contains all of the compiled `io_*.cpp` and `*_py.cpp` code. The name is configurable via the `TRICKIFY_OBJECT_NAME` variable, which can include directories, which will automatically be created if necessary. `build` contains a lot of ICG and SWIG artifacts. You can't change its name or location at this time, but it's useful to keep around as it will allow you to rebuild only the parts of the project that change in the future, and sims that build against your project will need the `*_py.i` files within. `trick` includes a bunch of crazily-named Python modules which serve as the input file interface to the content of the header files. Those modules are compiled and zipped into `python`. The zip file name is configurable via the `TRICKIFY_PYTHON_DIR` variable, which can include directories, which will automatically be created if necessary. Your Trickified library can be produced in three different formats based on the value of `TRICKIFY_BUILD_TYPE`: 1. `STATIC` (.a) @@ -84,8 +85,9 @@ Let's be honest. You're not going to remember that command line. And who wants t Makefile S_source.hh trickified.o + python build/ - python/ + trick/ ```make ifndef TRICK_HOME @@ -105,7 +107,7 @@ all: @$(MAKE) -s -f $(TRICKIFY) clean: - @rm -rf build python $(TRICKIFY_OBJECT_NAME) + @rm -rf build python trick $(TRICKIFY_OBJECT_NAME) ``` Now just type `make` in `trickified` and everything is taken care of. I even added a check to make sure you're using a recent enough version of Trick. I've silenced a lot of make's output because I prefer to see echoed commands only when debugging, but you're welcome to get rid of the `@` and `-s` if you enjoy such verbosity. Note that I've used `TRICKIFY_OBJECT_NAME` to rename the default `trickified.o` to something a little less generic. If you're following along, you can remove the `trickified.o` we built earlier. @@ -114,9 +116,9 @@ Now just type `make` in `trickified` and everything is taken care of. I even add The only Trickification-related files you want under version control are `S_source.hh` and `Makefile`. You should ignore all of the generated files. For instance, the appropriate `.gitignore` for the `trickified` directory when using default values for the Trickification variables is: ```git -build +build/ +trick/ python -trick *.o ``` @@ -135,7 +137,7 @@ This line links in the Trickified object. Note that you may need additional flag TRICK_EXT_LIB_DIRS += :$(HOME)/myproject ``` -This line tells Trick to expect `io_*` and `py_*` code for the headers in the specified directory (and all directories below it), but not to generate it itself. This is different than `TRICK_ICG_EXCLUDE` and `TRICK_EXCLUDE`, which cause ICG to ignore the headers entirely. It also tells Trick not to compile any source files in the specified directory (and all directories below it) that may be referenced as `LIBRARY_DEPENDENCIES` in user files. Note that it is a colon-delimited list of paths. +This line tells Trick to expect `io_*` and `*_py` code for the headers in the specified directory (and all directories below it), but not to generate it itself. This is different than `TRICK_ICG_EXCLUDE` and `TRICK_EXCLUDE`, which cause ICG to ignore the headers entirely. It also tells Trick not to compile any source files in the specified directory (and all directories below it) that may be referenced as `LIBRARY_DEPENDENCIES` in user files. Note that it is a colon-delimited list of paths. You'll need to be more selective if the sim itself or additional non-Trickified headers or source are under the same directory as Trickified headers or source. You may have to resort to individually specifying the full path to every file to be excluded, perhaps using some fancy `find` options to automatically generate the list. @@ -143,13 +145,13 @@ You'll need to be more selective if the sim itself or additional non-Trickified TRICK_PYTHON_PATH += :$(HOME)/myproject/trickified/python ``` -This line tells Trick where to find the Python modules generated by SWIG so that you can access the Trickified project from the input file. It is also a colon-delimited list of paths. +This line tells Trick where to find the zipped Python modules generated by SWIG so that you can access the Trickified project from the input file. It is also a colon-delimited list of paths. ```make TRICK_SWIG_FLAGS += -I$(HOME)/myproject/trickified ``` -This line tells Trick where to find the `py_*.i` files generated by ICG and should point to the directory containing `build`. These are necessary if any of your headers include headers from the Trickified project, which is likely, since you otherwise wouldn't be using it. It is a space-delimited list of options. Don't forget to prepend the path with `-I`. +This line tells Trick where to find the `*_py.i` files generated by ICG and should point to the directory containing `build`. These are necessary if any of your headers include headers from the Trickified project, which is likely, since you otherwise wouldn't be using it. It is a space-delimited list of options. Don't forget to prepend the path with `-I`. ## Simplify Your Users' Lives While the above is sufficient to use a Trickified project, it's awfully inconvenient to have to add all that stuff to every sim's `S_overrides.mk`. Plus, there are probably more things that need to be added, like header paths and linker flags for any additional libraries on which the project depends. Your users will love your project even more if you provide them with a makefile they can simply include from their `S_overrides.mk`. And it's not just the users that benefit! You'll have to answer far fewer questions about why they can't get your project compiled into their sim if, when your project inevitably changes, all they have to do is pull down the new makefile instead of changing all of their `S_overrides.mk`. Everybody wins! Turning once again to our example, let's call the makefile `myproject.mk` and put it in the `trickified` directory. You may have a name or location that makes more sense for your project. Maybe you already have a `makefiles` directory, or maybe your project supports a number of third party tools and you have a directory for Trick support. But let's keep the example simple: @@ -164,8 +166,9 @@ While the above is sufficient to use a Trickified project, it's awfully inconven myproject.mk S_source.hh trickified_myproject.o + python build/ - python/ + trick/ Here's the contents of `myproject.mk`. It's everything from the previous section plus some other things you might find useful. @@ -193,7 +196,7 @@ TRICK_CFLAGS += $(MYPROJECT_INCLUDE) $(MYPROJECT_SOURCE) TRICK_CXXFLAGS += $(MYPROJECT_INCLUDE) $(MYPROJECT_SOURCE) # Enable Trickification support if Trick >= 17.1. -# Otherwise, let Trick generate all of the io_* and py_* code as usual. +# Otherwise, let Trick generate all of the io_* and *_py code as usual. ifneq ($(wildcard $(TRICK_HOME)/share/trick/makefiles/trickify.mk),) MYPROJECT_TRICK := $(MYPROJECT_HOME)/trickified/trickified_myproject.o @@ -202,10 +205,10 @@ ifneq ($(wildcard $(TRICK_HOME)/share/trick/makefiles/trickify.mk),) # Trickified project TRICK_EXT_LIB_DIRS += :$(MYPROJECT_HOME) - # Tell Trick where to find the Python modules generated by SWIG + # Tell Trick where to find the zipped Python modules generated by SWIG TRICK_PYTHON_PATH += :$(MYPROJECT_HOME)/trickified/python - # Tell SWIG where to find py_*.i files + # Tell SWIG where to find *_py.i files TRICK_SWIG_FLAGS += -I$(MYPROJECT_HOME)/trickified # Link in the Trickified object @@ -234,7 +237,7 @@ include /trickified/myproject.mk They'll have to replace `` with the location to which they installed your project, of course. They might choose to hardcode the path or use a variable, but that's up to them. # You Still Need a Core Library -Trickification is great and all, but it only builds the `io_*` and `py_*` code into an object. And because your project's headers and source are now under `TRICK_EXT_LIB_DIRS`, Trick won't be determining dependencies or compiling source code. Trickification thus necessitates that you build all of your source code into a library. There are plenty of internet tutorials available on that topic, so I won't be suggesting anything here. But once you've got that taken care of, you should incorporate it into your user-facing makefile by adding it to `TRICK_LDFLAGS`. You can also create a rule to call its build system and add the library as a prerequisite to `$(S_MAIN)` to have it built along with the sim if necessary. +Trickification is great and all, but it only builds the `io_*` and `*_py` code into an object. And because your project's headers and source are now under `TRICK_EXT_LIB_DIRS`, Trick won't be determining dependencies or compiling source code. Trickification thus necessitates that you build all of your source code into a library. There are plenty of internet tutorials available on that topic, so I won't be suggesting anything here. But once you've got that taken care of, you should incorporate it into your user-facing makefile by adding it to `TRICK_LDFLAGS`. You can also create a rule to call its build system and add the library as a prerequisite to `$(S_MAIN)` to have it built along with the sim if necessary. # A Real Life Trickified Project Here's a real project we used as the guinea pig for Trickification. It provides a makefile that a user can include from his `S_overrides.mk` that causes both a core library and Trickified object to be built if they don't already exist whenever the sim is compiled. The makefile is located at `3rdParty/trick/makefiles/trickified.mk`. The Trickified stuff is at `3rdParty/trick/lib`. diff --git a/docs/documentation/install_guide/Install-Guide.md b/docs/documentation/install_guide/Install-Guide.md index 49535d3c..ed7e6b4c 100644 --- a/docs/documentation/install_guide/Install-Guide.md +++ b/docs/documentation/install_guide/Install-Guide.md @@ -112,7 +112,8 @@ Trick also requires development packages from the base and epel repositories yum install -y bison clang flex git llvm make maven swig3 cmake clang-devel \ gcc gcc-c++ java-11-openjdk-devel libxml2-devel llvm-devel llvm-static \ ncurses-devel openmotif openmotif-devel perl perl-Digest-MD5 udunits2 \ -udunits2-devel which zlib-devel gtest-devel libX11-devel libXt-devel python-devel +udunits2-devel which zlib-devel gtest-devel libX11-devel libXt-devel python-devel \ +zip ``` Trick makes use of several optional packages if they are present on the system. These include using the HDF5 package for logging, the GSL packages for random number generation, and google test (gtest) for Trick's unit testing. These are available from the EPEL repository @@ -133,7 +134,7 @@ Trick requires development packages from the base repositories. dnf install -y bison clang flex git llvm make maven swig cmake clang-devel \ gcc gcc-c++ java-11-openjdk-devel libxml2-devel llvm-devel llvm-static \ ncurses-devel openmotif openmotif-devel perl perl-Digest-MD5 udunits2 udunits2-devel \ -which zlib-devel gtest-devel perl-Text-Balanced python-devel diffutils +which zlib-devel gtest-devel perl-Text-Balanced python-devel diffutils zip ``` Trick makes use of several optional packages if they are present on the system. These include using the HDF5 package for logging, the GSL packages for random number generation, and google test (gtest) for Trick's unit testing. These are available from the EPEL repository @@ -157,7 +158,7 @@ apt-get update apt-get install -y bison clang flex git llvm make maven swig cmake \ curl g++ libx11-dev libxml2-dev libxt-dev libmotif-common libmotif-dev \ python3-dev zlib1g-dev llvm-dev libclang-dev libudunits2-dev \ -libgtest-dev openjdk-11-jdk +libgtest-dev openjdk-11-jdk zip # On some versions of Ubuntu (18.04 as of 04/2021), there may be multiple installations of python. # Our new python3-dev will be linked to python3 and python3-config in your bin. diff --git a/libexec/trick/make_makefile_swig b/libexec/trick/make_makefile_swig index 04851f74..3046a5a8 100755 --- a/libexec/trick/make_makefile_swig +++ b/libexec/trick/make_makefile_swig @@ -153,8 +153,8 @@ else endif ifndef TRICK_VERBOSE_BUILD - PRINT_SWIG = \$(info \$(call COLOR,SWIGing) \$<) - PRINT_COMPILE_SWIG = \$(info \$(call COLOR,Compiling) \$<) + PRINT_SWIG = \$(info \$(call COLOR,SWIGing) \$<) + PRINT_COMPILE_SWIG = \$(info \$(call COLOR,Compiling) \$<) endif # TRICK_FIXED_PYTHON =========================================================== @@ -171,8 +171,6 @@ TRICK_FIXED_PYTHON = \\ \$(TRICK_FIXED_PYTHON): $swig_sim_dir/\% : \${TRICK_HOME}/share/trick/swig/\% \t\$(call ECHO_AND_LOG,/bin/cp -f \$< \$@) -all: \$(TRICK_FIXED_PYTHON) - # SWIG_I ======================================================================= SWIG_I =" ; @@ -201,7 +199,7 @@ SWIG_SRC = \$(subst .i,.cpp,\$(SWIG_I)) $swig_src_dir/top.cpp \$(SWIG_SRC) : %.cpp: %.i | %.d \$(SWIG_I) \t\$(PRINT_SWIG) -\t\$(call ECHO_AND_LOG,\$(SWIG) \$(TRICK_INCLUDE) \$(TRICK_DEFINES) \$(TRICK_VERSIONS) \$(TRICK_SWIG_FLAGS) -c++ -python -includeall -ignoremissing -w201 -w303 -w315 -w325 -w362 -w389 -w401 -w451 -MMD -MP -outdir trick -o \$@ \$<) +\t\$(call ECHO_AND_LOG,\$(SWIG) \$(TRICK_INCLUDE) \$(TRICK_DEFINES) \$(TRICK_VERSIONS) \$(TRICK_SWIG_FLAGS) -c++ -python -includeall -ignoremissing -w201 -w303 -w315 -w325 -w362 -w389 -w401 -w451 -MMD -MP -outdir $swig_sim_dir -o \$@ \$<) \$(SWIG_SRC:.cpp=.d): ; @@ -218,6 +216,16 @@ SWIG_OBJECTS = \$(subst .cpp,.o,\$(SWIG_SRC)) $swig_src_dir/init_swig_modules.o \$(S_MAIN): \$(SWIG_OBJECTS) LINK_LISTS += \$(LD_FILELIST)build/py_link_list + +# trick.zip ==================================================================== + +trick.zip: \$(SWIG_SRC) \$(TRICK_FIXED_PYTHON) $swig_sim_dir/__init__.py +\t\$(info \$(call COLOR,Compiling) Python modules) +\t\$(call ECHO_AND_LOG,\$(PYTHON) -m compileall -q $swig_sim_dir) +\t\$(info \$(call COLOR,Zipping) Python modules into $swig_sim_dir.zip) +\t\$(call ECHO_AND_LOG,zip -rq $swig_sim_dir $swig_sim_dir) + +all: $swig_sim_dir.zip " ; close MAKEFILE ; @@ -294,9 +302,9 @@ LINK_LISTS += \$(LD_FILELIST)build/py_link_list print INITFILE "__path__ = extend_path(__path__, __name__)\n" ; print INITFILE "import sys\n" ; print INITFILE "import os\n" ; - print INITFILE "sys.path.append(os.getcwd() + \"/trick\")\n" ; + print INITFILE "sys.path.append(os.getcwd() + \"/trick.zip/trick\")\n" ; foreach my $dir ( keys %python_module_dirs ) { - print INITFILE "sys.path.append(os.getcwd() + \"/trick/$dir\")\n" ; + print INITFILE "sys.path.append(os.getcwd() + \"/trick.zip/trick/$dir\")\n" ; } print INITFILE "\n" ; diff --git a/share/trick/makefiles/Makefile.common b/share/trick/makefiles/Makefile.common index 8a84fa6b..43d60124 100644 --- a/share/trick/makefiles/Makefile.common +++ b/share/trick/makefiles/Makefile.common @@ -3,8 +3,8 @@ CD := cd MV := /bin/mv RM := /bin/rm CP := /bin/cp -MAKE_OUT := build/MAKE_out -MAKE_ERR := build/MAKE_err +MAKE_OUT := $(abspath build/MAKE_out) +MAKE_ERR := $(abspath build/MAKE_err) PWD = $(shell /bin/pwd) COLOR = $(1) @@ -14,12 +14,12 @@ ifdef VERBOSE endif ifdef TRICK_VERBOSE_BUILD - ECHO_TO_TERMINAL = @echo $1 + ECHO_TO_TERMINAL = @echo '$1' endif define ECHO_AND_LOG $(call ECHO_TO_TERMINAL, $1) -@echo $1 >> $(MAKE_OUT) +@echo '$1' >> $(MAKE_OUT) @$1 > >($(TEE) -a $(MAKE_OUT)) 2> >($(TEE) -a $(MAKE_ERR) >&2) endef diff --git a/share/trick/makefiles/trickify.mk b/share/trick/makefiles/trickify.mk index a1efc23b..5a2511f2 100644 --- a/share/trick/makefiles/trickify.mk +++ b/share/trick/makefiles/trickify.mk @@ -1,9 +1,9 @@ # This file can be used to create an object file or library containing the io_* -# and py_* code that Trick would normally generate during the simulation build +# and *_py code that Trick would normally generate during the simulation build # process. Sims can then link against that, reducing compilation time. # # To use it, create a directory that includes a file named S_source.hh that -# includes all header files for which you want io_* and py_* code generated. +# includes all header files for which you want io_* and _py* code generated. # This is not the S_source.hh that Trick generates during compilation, but # Trick's tools assume this name for now, so you have to use it. Then, in that # directory, run the following command: @@ -46,8 +46,8 @@ # trickified.o. You should choose something more meaningful, especially if # you're using another build type. # -# TRICKIFY_PTYON_DIR (optional) -# The directory into which generated Python modules are placed. The default +# TRICKIFY_PYTHON_DIR (optional) +# The file into which generated Python modules are zipped. The default # value is python (in the current directory). # # ----------------------------------------------------------------------------- @@ -81,12 +81,12 @@ # $(MAKE) -f $(TRICKIFY) # # clean: -# rm -rf $(TRICKIFY_OBJECT_NAME) build python +# rm -rf $(TRICKIFY_OBJECT_NAME) build python trick # # ----------------------------------------------------------------------------- # # For more information, see: -# github.com/nasa/trick/wiki/Trickified-Project-Libraries +# https://nasa.github.io/trick/documentation/building_a_simulation/Trickified-Project-Libraries ifndef TRICKIFY_CXX_FLAGS $(error TRICKIFY_CXX_FLAGS must be set) @@ -94,7 +94,12 @@ endif TRICKIFY_BUILD_TYPE ?= PLO TRICKIFY_OBJECT_NAME ?= trickified.o + +# We started zipping the Python modules, and this variable is now misnamed :( +# python.zip would be a better default value, but leave it as python for backward compatibility. TRICKIFY_PYTHON_DIR ?= python +TRICKIFY_PYTHON_DIR := $(abspath $(TRICKIFY_PYTHON_DIR)) + include $(dir $(lastword $(MAKEFILE_LIST)))Makefile.common BUILD_DIR := $(dir $(MAKE_OUT)) @@ -111,6 +116,9 @@ TRICK_CXXFLAGS += $(TRICKIFY_CXX_FLAGS) # Ensure we can process all headers TRICK_EXT_LIB_DIRS := +.PHONY: all +all: $(TRICKIFY_OBJECT_NAME) $(TRICKIFY_PYTHON_DIR) + $(TRICKIFY_OBJECT_NAME): $(SWIG_OBJECTS) $(IO_OBJECTS) | $(dir $(TRICKIFY_OBJECT_NAME)) $(info $(call COLOR,Linking) $@) ifeq ($(TRICKIFY_BUILD_TYPE),PLO) @@ -121,7 +129,7 @@ else ifeq ($(TRICKIFY_BUILD_TYPE),STATIC) $(call ECHO_AND_LOG,ar rcs $@ $^) endif -$(dir $(TRICKIFY_OBJECT_NAME)) $(TRICKIFY_PYTHON_DIR) $(BUILD_DIR): +$(dir $(TRICKIFY_OBJECT_NAME)) $(BUILD_DIR) $(dir $(TRICKIFY_PYTHON_DIR)) trick: @mkdir -p $@ $(IO_OBJECTS): %.o: %.cpp @@ -136,9 +144,9 @@ $(SWIG_OBJECTS): %.o: %.cpp $(info $(call COLOR,Compiling) $<) $(call ECHO_AND_LOG,$(TRICK_CXX) $(TRICK_CXXFLAGS) $(TRICK_SYSTEM_CXXFLAGS) $(PYTHON_INCLUDES) -Wno-unused-parameter -Wno-shadow -c -o $@ $<) -$(SWIG_OBJECTS:.o=.cpp): %.cpp: %.i | %.d $(TRICKIFY_PYTHON_DIR) $(SWIG_OBJECTS:.o=.i) +$(SWIG_OBJECTS:.o=.cpp): %.cpp: %.i | %.d trick $(SWIG_OBJECTS:.o=.i) $(info $(call COLOR,SWIGing) $<) - $(call ECHO_AND_LOG,$(SWIG) $(TRICK_INCLUDE) $(TRICK_DEFINES) $(TRICK_VERSIONS) $(TRICK_SWIG_FLAGS) -c++ -python -includeall -ignoremissing -w201 -w303 -w315 -w325 -w362 -w389 -w401 -w451 -MMD -MP -outdir $(TRICKIFY_PYTHON_DIR) -o $@ $<) + $(call ECHO_AND_LOG,$(SWIG) $(TRICK_INCLUDE) $(TRICK_DEFINES) $(TRICK_VERSIONS) $(TRICK_SWIG_FLAGS) -c++ -python -includeall -ignoremissing -w201 -w303 -w315 -w325 -w362 -w389 -w401 -w451 -MMD -MP -outdir trick -o $@ $<) $(SWIG_OBJECTS:.o=.d): ; @@ -151,6 +159,12 @@ endef $(foreach EXTENSION,H h hh hxx h++ hpp,$(eval $(call create_convert_swig_rule,$(EXTENSION)))) +$(TRICKIFY_PYTHON_DIR): $(SWIG_OBJECTS:.o=.cpp) | $(dir $(TRICKIFY_PYTHON_DIR)) + $(info $(call COLOR,Compiling) Python modules) + $(call ECHO_AND_LOG,$(PYTHON) -m compileall -q trick) + $(info $(call COLOR,Zipping) Python modules into $@) + $(call ECHO_AND_LOG,cd trick && zip -Arq $@ .) + # SWIG_OBJECTS and IO_OBJECTS are meant to contain all of the *_py and io_* # object file names, respectively, by looking at products of ICG and # make_makefile_swig. However, we can't run a rule for ICG before those diff --git a/test/.gitignore b/test/.gitignore index e775fd0e..883ab8dd 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -21,6 +21,7 @@ S_source.cpp S_source.hh T_main_* trick +trick.zip jitlib build -S_sie.json \ No newline at end of file +S_sie.json diff --git a/test/SIM_trickified/RUN_test/unit_test.py b/test/SIM_trickified/RUN_test/unit_test.py new file mode 100644 index 00000000..f74fee0b --- /dev/null +++ b/test/SIM_trickified/RUN_test/unit_test.py @@ -0,0 +1,4 @@ +sandbox.foo.i = 5 +assert sandbox.foo.i == 5 +foo = trick.Foo() +trick.stop(10) diff --git a/test/SIM_trickified/S_define b/test/SIM_trickified/S_define new file mode 100644 index 00000000..f770e2a7 --- /dev/null +++ b/test/SIM_trickified/S_define @@ -0,0 +1,21 @@ +#include "sim_objects/default_trick_sys.sm" +##include "Foo.hh" +##include "Bar.hh" +##include "Baz.hh" + +class Sandbox : public Trick::SimObject { + + public: + + Foo foo; + Bar bar; + Baz baz; + + Sandbox() { + (1, "scheduled") foo.foo(); + } + +}; + + +Sandbox sandbox; diff --git a/test/SIM_trickified/S_overrides.mk b/test/SIM_trickified/S_overrides.mk new file mode 100644 index 00000000..ad344832 --- /dev/null +++ b/test/SIM_trickified/S_overrides.mk @@ -0,0 +1,2 @@ +include trickified_project/trickified/myproject.mk +TRICK_CXXFLAGS += -I$(CURDIR)/models diff --git a/test/SIM_trickified/models/Baz.hh b/test/SIM_trickified/models/Baz.hh new file mode 100644 index 00000000..fc91c1b2 --- /dev/null +++ b/test/SIM_trickified/models/Baz.hh @@ -0,0 +1,9 @@ +// @trick_parse{everything} + +class Baz { + + public: + int i; + int j; + +}; diff --git a/test/SIM_trickified/trickified_project/include/Bar.hh b/test/SIM_trickified/trickified_project/include/Bar.hh new file mode 100644 index 00000000..5640bfb0 --- /dev/null +++ b/test/SIM_trickified/trickified_project/include/Bar.hh @@ -0,0 +1,17 @@ +// @trick_parse{everything} + +#include "trick/Event.hh" + +/** + * Induce an `%import sim_services` statement in this class's Python module by inheriting from a + * Trick class. This allows us to test if `sys.path` contains the correct path to `sim_services.py` + * (and other modules generated during a sim build) for Trickified projects. + */ +class Bar : public Trick::Event { + + int process(long long) {return 0;} + void add() {} + void remove() {} + void restart() {} + +}; diff --git a/test/SIM_trickified/trickified_project/include/Foo.hh b/test/SIM_trickified/trickified_project/include/Foo.hh new file mode 100644 index 00000000..be7e761a --- /dev/null +++ b/test/SIM_trickified/trickified_project/include/Foo.hh @@ -0,0 +1,15 @@ +// @trick_parse{everything} + +#include + +class Foo { + + public: + + int i; + + void foo() { + std::cout << i++ << '\n'; + } + +}; diff --git a/test/SIM_trickified/trickified_project/trickified/.gitignore b/test/SIM_trickified/trickified_project/trickified/.gitignore new file mode 100644 index 00000000..0259157c --- /dev/null +++ b/test/SIM_trickified/trickified_project/trickified/.gitignore @@ -0,0 +1,4 @@ +build +python +trick +*.o diff --git a/test/SIM_trickified/trickified_project/trickified/Makefile b/test/SIM_trickified/trickified_project/trickified/Makefile new file mode 100644 index 00000000..ae6eb310 --- /dev/null +++ b/test/SIM_trickified/trickified_project/trickified/Makefile @@ -0,0 +1,11 @@ +PROJECT_HOME := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/..) +TRICK_HOME := $(abspath $(PROJECT_HOME)/../../..) + +export TRICKIFY_OBJECT_NAME := trickified_myproject.o +export TRICKIFY_CXX_FLAGS := -I$(PROJECT_HOME)/include -I$(TRICK_HOME)/include + +all: + @$(MAKE) -s -f $(TRICK_HOME)/share/trick/makefiles/trickify.mk + +clean: + @rm -rf build python trick $(TRICKIFY_OBJECT_NAME) diff --git a/test/SIM_trickified/trickified_project/trickified/S_source.hh b/test/SIM_trickified/trickified_project/trickified/S_source.hh new file mode 100644 index 00000000..a6dab172 --- /dev/null +++ b/test/SIM_trickified/trickified_project/trickified/S_source.hh @@ -0,0 +1,2 @@ +#include "Foo.hh" +#include "Bar.hh" diff --git a/test/SIM_trickified/trickified_project/trickified/myproject.mk b/test/SIM_trickified/trickified_project/trickified/myproject.mk new file mode 100644 index 00000000..7abe9af1 --- /dev/null +++ b/test/SIM_trickified/trickified_project/trickified/myproject.mk @@ -0,0 +1,38 @@ +# We know this file's position relative to the root directory of the project, +# and MAKEFILE_LIST will give us the full path to this file no matter where the +# user has installed this project. +export MYPROJECT_HOME := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/..) + +# Specify include paths for your headers. +MYPROJECT_INCLUDE := -I$(MYPROJECT_HOME)/include + +# Users may set different flags for C and C++, so you should really modify both +# to be safe. +TRICK_CFLAGS += $(MYPROJECT_INCLUDE) $(MYPROJECT_SOURCE) +TRICK_CXXFLAGS += $(MYPROJECT_INCLUDE) $(MYPROJECT_SOURCE) + +MYPROJECT_TRICK := $(MYPROJECT_HOME)/trickified/trickified_myproject.o + +# Tell Trick the headers and source at this location are part of a +# Trickified project +TRICK_EXT_LIB_DIRS += :$(MYPROJECT_HOME) + +# Tell Trick where to find the Python modules generated by SWIG +TRICK_PYTHON_PATH += :$(MYPROJECT_HOME)/trickified/python + +# Tell SWIG where to find py_*.i files +TRICK_SWIG_FLAGS += -I$(MYPROJECT_HOME)/trickified + +# Link in the Trickified object +TRICK_LDFLAGS += $(MYPROJECT_TRICK) + +# Append a prerequisite to the $(SWIG_SRC) target. This will build the +# Trickified library along with the sim if it does not already exist. Using +# $(SWIG_SRC) ensures that all Trickified .i files are created before SWIG is +# run on any simulation .i files, which may %import them. Note that this does +# NOT cause the Trickified library to be rebuilt if it already exists, even if +# the Trickified source code has changed. +$(SWIG_SRC): $(MYPROJECT_TRICK) + +$(MYPROJECT_TRICK): + @$(MAKE) -s -C $(MYPROJECT_HOME)/trickified diff --git a/test/makefile b/test/makefile index 6556eeb5..abeb9742 100644 --- a/test/makefile +++ b/test/makefile @@ -2,6 +2,9 @@ TRICK_HOME := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))..) export TRICK_HOST_CPU := $(shell $(TRICK_HOME)/bin/trick-gte TRICK_HOST_CPU) include ${TRICK_HOME}/share/trick/makefiles/Makefile.common +# Remove when issue #1147 is fixed: https://github.com/nasa/trick/issues/1147 +unexport TRICK_PYTHON_PATH + SIMS_TO_COMPILE_ONLY = \ SIM_alloc_test \ SIM_default_member_initializer \ @@ -31,7 +34,8 @@ SIMS_TO_COMPILE_AND_RUN = \ SIM_test_ip \ SIM_test_sched \ SIM_test_templates \ - SIM_threads + SIM_threads \ + SIM_trickified # Sims with problems, no purpose, or maybe shouldn't be a test # SIM_leaks ( should be deleted ) diff --git a/trick_source/sim_services/InputProcessor/IPPython.cpp b/trick_source/sim_services/InputProcessor/IPPython.cpp index 9a46614a..a26b01ed 100644 --- a/trick_source/sim_services/InputProcessor/IPPython.cpp +++ b/trick_source/sim_services/InputProcessor/IPPython.cpp @@ -110,7 +110,7 @@ int Trick::IPPython::init() { "import os\n" "import struct\n" "import binascii\n" - "sys.path.append(os.getcwd())\n" + "sys.path.append(os.getcwd() + '/trick.zip')\n" "sys.path.append(os.path.join(os.environ['TRICK_HOME'], 'share/trick/pymods'))\n" "sys.path += map(str.strip, os.environ['TRICK_PYTHON_PATH'].split(':'))\n" "import trick\n" From 2c06ae6eaf873d8d81e8e2dc986f1c0a56b6fd30 Mon Sep 17 00:00:00 2001 From: Derek Bankieris Date: Wed, 19 May 2021 17:21:21 -0500 Subject: [PATCH 2/2] Rename SIM_*/trick to SIM_*/.trick Hide the non-zipped Python modules to indicate to users that changing them will have no effect on the sim. Refs #1144 --- bin/trick-CP | 2 +- .../Trickified-Project-Libraries.md | 12 ++++---- libexec/trick/make_makefile_swig | 30 +++++++++++-------- share/trick/makefiles/trickify.mk | 12 ++++---- test/.gitignore | 2 +- trick_sims/.gitignore | 3 +- 6 files changed, 33 insertions(+), 28 deletions(-) diff --git a/bin/trick-CP b/bin/trick-CP index 9f8ef66d..f537fe5a 100755 --- a/bin/trick-CP +++ b/bin/trick-CP @@ -280,7 +280,7 @@ tidy: clean: tidy -rm -f DP_Product/DP_rt_frame DP_Product/DP_rt_itimer -rm -f DP_Product/DP_rt_jobs DP_Product/DP_rt_timeline DP_Product/DP_mem_stats - -rm -rf build trick trick.zip + -rm -rf build .trick trick.zip -rm -f makefile spotless: clean diff --git a/docs/documentation/building_a_simulation/Trickified-Project-Libraries.md b/docs/documentation/building_a_simulation/Trickified-Project-Libraries.md index 913ac34e..b4e3a938 100644 --- a/docs/documentation/building_a_simulation/Trickified-Project-Libraries.md +++ b/docs/documentation/building_a_simulation/Trickified-Project-Libraries.md @@ -59,9 +59,9 @@ The result should be: trickified.o python build/ - trick/ + .trick/ -`trickified.o` contains all of the compiled `io_*.cpp` and `*_py.cpp` code. The name is configurable via the `TRICKIFY_OBJECT_NAME` variable, which can include directories, which will automatically be created if necessary. `build` contains a lot of ICG and SWIG artifacts. You can't change its name or location at this time, but it's useful to keep around as it will allow you to rebuild only the parts of the project that change in the future, and sims that build against your project will need the `*_py.i` files within. `trick` includes a bunch of crazily-named Python modules which serve as the input file interface to the content of the header files. Those modules are compiled and zipped into `python`. The zip file name is configurable via the `TRICKIFY_PYTHON_DIR` variable, which can include directories, which will automatically be created if necessary. +`trickified.o` contains all of the compiled `io_*.cpp` and `*_py.cpp` code. The name is configurable via the `TRICKIFY_OBJECT_NAME` variable, which can include directories, which will automatically be created if necessary. `build` contains a lot of ICG and SWIG artifacts. You can't change its name or location at this time, but it's useful to keep around as it will allow you to rebuild only the parts of the project that change in the future, and sims that build against your project will need the `*_py.i` files within. `.trick` includes a bunch of crazily-named Python modules which serve as the input file interface to the content of the header files. Those modules are compiled and zipped into `python`. The zip file name is configurable via the `TRICKIFY_PYTHON_DIR` variable, which can include directories, which will automatically be created if necessary. Your Trickified library can be produced in three different formats based on the value of `TRICKIFY_BUILD_TYPE`: 1. `STATIC` (.a) @@ -87,7 +87,7 @@ Let's be honest. You're not going to remember that command line. And who wants t trickified.o python build/ - trick/ + .trick/ ```make ifndef TRICK_HOME @@ -107,7 +107,7 @@ all: @$(MAKE) -s -f $(TRICKIFY) clean: - @rm -rf build python trick $(TRICKIFY_OBJECT_NAME) + @rm -rf build python .trick $(TRICKIFY_OBJECT_NAME) ``` Now just type `make` in `trickified` and everything is taken care of. I even added a check to make sure you're using a recent enough version of Trick. I've silenced a lot of make's output because I prefer to see echoed commands only when debugging, but you're welcome to get rid of the `@` and `-s` if you enjoy such verbosity. Note that I've used `TRICKIFY_OBJECT_NAME` to rename the default `trickified.o` to something a little less generic. If you're following along, you can remove the `trickified.o` we built earlier. @@ -117,7 +117,7 @@ The only Trickification-related files you want under version control are `S_sour ```git build/ -trick/ +.trick/ python *.o ``` @@ -168,7 +168,7 @@ While the above is sufficient to use a Trickified project, it's awfully inconven trickified_myproject.o python build/ - trick/ + .trick/ Here's the contents of `myproject.mk`. It's everything from the previous section plus some other things you might find useful. diff --git a/libexec/trick/make_makefile_swig b/libexec/trick/make_makefile_swig index 3046a5a8..a5fa5647 100755 --- a/libexec/trick/make_makefile_swig +++ b/libexec/trick/make_makefile_swig @@ -133,7 +133,8 @@ sub purge_swig_no_files() { sub write_makefile_swig() { my $s_source_md5 = md5_hex(abs_path("S_source.hh")) ; - my $swig_sim_dir = "trick" ; + my $swig_sim_dir = ".trick" ; + my $swig_sim_zip = "trick.zip" ; my $swig_src_dir = "build" ; open MAKEFILE , ">build/Makefile_swig" or die "Could not open build/Makefile_swig for writing" ; @@ -217,15 +218,18 @@ SWIG_OBJECTS = \$(subst .cpp,.o,\$(SWIG_SRC)) $swig_src_dir/init_swig_modules.o LINK_LISTS += \$(LD_FILELIST)build/py_link_list -# trick.zip ==================================================================== +# $swig_sim_zip =================================================================== -trick.zip: \$(SWIG_SRC) \$(TRICK_FIXED_PYTHON) $swig_sim_dir/__init__.py +$swig_sim_zip: \$(SWIG_SRC) \$(TRICK_FIXED_PYTHON) $swig_sim_dir/__init__.py \t\$(info \$(call COLOR,Compiling) Python modules) \t\$(call ECHO_AND_LOG,\$(PYTHON) -m compileall -q $swig_sim_dir) -\t\$(info \$(call COLOR,Zipping) Python modules into $swig_sim_dir.zip) -\t\$(call ECHO_AND_LOG,zip -rq $swig_sim_dir $swig_sim_dir) +\t\$(info \$(call COLOR,Zipping) Python modules into \$@) +\t\$(call ECHO_AND_LOG,ln -sf $swig_sim_dir trick) +\t\$(call ECHO_AND_LOG,zip -rq $swig_sim_zip trick) +\t\$(call ECHO_AND_LOG,rm -f trick) -all: $swig_sim_dir.zip + +all: $swig_sim_zip " ; close MAKEFILE ; @@ -293,18 +297,18 @@ all: $swig_sim_dir.zip print INITSWIGFILE "#endif\n" ; close INITSWIGFILE ; - if ( ! -e "trick") { - mkdir "trick" ; + if ( ! -e $swig_sim_dir) { + mkdir $swig_sim_dir ; } - open INITFILE , ">trick/__init__.py" or die "Could not open trick/__init__.py for writing" ; + open INITFILE , ">$swig_sim_dir/__init__.py" or die "Could not open $swig_sim_dir/__init__.py for writing" ; print INITFILE "from pkgutil import extend_path\n" ; print INITFILE "__path__ = extend_path(__path__, __name__)\n" ; print INITFILE "import sys\n" ; print INITFILE "import os\n" ; - print INITFILE "sys.path.append(os.getcwd() + \"/trick.zip/trick\")\n" ; + print INITFILE "sys.path.append(os.getcwd() + \"/$swig_sim_zip/trick\")\n" ; foreach my $dir ( keys %python_module_dirs ) { - print INITFILE "sys.path.append(os.getcwd() + \"/trick.zip/trick/$dir\")\n" ; + print INITFILE "sys.path.append(os.getcwd() + \"/$swig_sim_zip/trick/$dir\")\n" ; } print INITFILE "\n" ; @@ -350,8 +354,8 @@ all: $swig_sim_dir.zip close INITFILE ; foreach my $dir ( keys %python_module_dirs ) { - system("mkdir -p trick/$dir"); - open MODULE_INITFILE, ">trick/$dir/__init__.py"; + system("mkdir -p $swig_sim_dir/$dir"); + open MODULE_INITFILE, ">$swig_sim_dir/$dir/__init__.py"; foreach my $file ( @files_to_process ) { if ( exists $trick_headers{$file}{python_module_dir} and $trick_headers{$file}{python_module_dir} eq $dir ) { print MODULE_INITFILE "# $file\n" ; diff --git a/share/trick/makefiles/trickify.mk b/share/trick/makefiles/trickify.mk index 5a2511f2..b7416fea 100644 --- a/share/trick/makefiles/trickify.mk +++ b/share/trick/makefiles/trickify.mk @@ -81,7 +81,7 @@ # $(MAKE) -f $(TRICKIFY) # # clean: -# rm -rf $(TRICKIFY_OBJECT_NAME) build python trick +# rm -rf $(TRICKIFY_OBJECT_NAME) build python .trick # # ----------------------------------------------------------------------------- # @@ -129,7 +129,7 @@ else ifeq ($(TRICKIFY_BUILD_TYPE),STATIC) $(call ECHO_AND_LOG,ar rcs $@ $^) endif -$(dir $(TRICKIFY_OBJECT_NAME)) $(BUILD_DIR) $(dir $(TRICKIFY_PYTHON_DIR)) trick: +$(dir $(TRICKIFY_OBJECT_NAME)) $(BUILD_DIR) $(dir $(TRICKIFY_PYTHON_DIR)) .trick: @mkdir -p $@ $(IO_OBJECTS): %.o: %.cpp @@ -144,9 +144,9 @@ $(SWIG_OBJECTS): %.o: %.cpp $(info $(call COLOR,Compiling) $<) $(call ECHO_AND_LOG,$(TRICK_CXX) $(TRICK_CXXFLAGS) $(TRICK_SYSTEM_CXXFLAGS) $(PYTHON_INCLUDES) -Wno-unused-parameter -Wno-shadow -c -o $@ $<) -$(SWIG_OBJECTS:.o=.cpp): %.cpp: %.i | %.d trick $(SWIG_OBJECTS:.o=.i) +$(SWIG_OBJECTS:.o=.cpp): %.cpp: %.i | %.d .trick $(SWIG_OBJECTS:.o=.i) $(info $(call COLOR,SWIGing) $<) - $(call ECHO_AND_LOG,$(SWIG) $(TRICK_INCLUDE) $(TRICK_DEFINES) $(TRICK_VERSIONS) $(TRICK_SWIG_FLAGS) -c++ -python -includeall -ignoremissing -w201 -w303 -w315 -w325 -w362 -w389 -w401 -w451 -MMD -MP -outdir trick -o $@ $<) + $(call ECHO_AND_LOG,$(SWIG) $(TRICK_INCLUDE) $(TRICK_DEFINES) $(TRICK_VERSIONS) $(TRICK_SWIG_FLAGS) -c++ -python -includeall -ignoremissing -w201 -w303 -w315 -w325 -w362 -w389 -w401 -w451 -MMD -MP -outdir .trick -o $@ $<) $(SWIG_OBJECTS:.o=.d): ; @@ -161,9 +161,9 @@ $(foreach EXTENSION,H h hh hxx h++ hpp,$(eval $(call create_convert_swig_rule,$( $(TRICKIFY_PYTHON_DIR): $(SWIG_OBJECTS:.o=.cpp) | $(dir $(TRICKIFY_PYTHON_DIR)) $(info $(call COLOR,Compiling) Python modules) - $(call ECHO_AND_LOG,$(PYTHON) -m compileall -q trick) + $(call ECHO_AND_LOG,$(PYTHON) -m compileall -q .trick) $(info $(call COLOR,Zipping) Python modules into $@) - $(call ECHO_AND_LOG,cd trick && zip -Arq $@ .) + $(call ECHO_AND_LOG,cd .trick && zip -Arq $@ .) # SWIG_OBJECTS and IO_OBJECTS are meant to contain all of the *_py and io_* # object file names, respectively, by looking at products of ICG and diff --git a/test/.gitignore b/test/.gitignore index 883ab8dd..38e6eddd 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -20,7 +20,7 @@ S_sie.resource S_source.cpp S_source.hh T_main_* -trick +.trick trick.zip jitlib build diff --git a/trick_sims/.gitignore b/trick_sims/.gitignore index 18797f68..60d1a30c 100644 --- a/trick_sims/.gitignore +++ b/trick_sims/.gitignore @@ -22,7 +22,8 @@ S_sie.json S_source.cpp S_source.hh T_main_* -**/SIM_*/trick +**/SIM_*/.trick +**/SIM_*/trick.zip **/graphics/dist **/graphics/manifest **/graphics/build