diff --git a/B5500/CMakeLists.txt b/B5500/CMakeLists.txt new file mode 100644 index 00000000..9bc6b381 --- /dev/null +++ b/B5500/CMakeLists.txt @@ -0,0 +1,34 @@ +## B5500 simulator +## +## This is an automagically generated file. Do NOT EDIT. +## Any changes you make will be overwritten!! +## +## Make changes to the SIMH top-level makefile and then run the +## "cmake/generate.py" script to regenerate these files. +## +## cd cmake; python -m generate --help +## +## ------------------------------------------------------------ + +if (HAVE_UNITY_FRAMEWORK AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/unit-tests/CMakeLists.txt") + add_subdirectory(unit-tests) +endif () + +add_simulator(b5500 + SOURCES + b5500_cpu.c + b5500_io.c + b5500_sys.c + b5500_dk.c + b5500_mt.c + b5500_urec.c + b5500_dr.c + b5500_dtc.c + INCLUDES + .. + DEFINES + B5500 + FEATURE_INT64 + LABEL B5500 + PKG_FAMILY b5500_family + TEST b5500) diff --git a/I7000/CMakeLists.txt b/I7000/CMakeLists.txt new file mode 100644 index 00000000..216db830 --- /dev/null +++ b/I7000/CMakeLists.txt @@ -0,0 +1,151 @@ +## I7000 simulators +## +## This is an automagically generated file. Do NOT EDIT. +## Any changes you make will be overwritten!! +## +## Make changes to the SIMH top-level makefile and then run the +## "cmake/generate.py" script to regenerate these files. +## +## cd cmake; python -m generate --help +## +## ------------------------------------------------------------ + +if (HAVE_UNITY_FRAMEWORK AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/unit-tests/CMakeLists.txt") + add_subdirectory(unit-tests) +endif () + +add_simulator(i701 + SOURCES + ${I7000D}/i701_cpu.c + ${I7000D}/i701_sys.c + ${I7000D}/i701_chan.c + ${I7000D}/i7090_cdr.c + ${I7000D}/i7090_cdp.c + ${I7000D}/i7090_lpr.c + ${I7000D}/i7000_mt.c + ${I7000D}/i7090_drum.c + ${I7000D}/i7000_chan.c + INCLUDES + ${I7000D} + DEFINES + I701 + FEATURE_INT64 + LABEL I7000 + PKG_FAMILY ibm_family + TEST i701) + +add_simulator(i7010 + SOURCES + ${I7000D}/i7010_cpu.c + ${I7000D}/i7010_sys.c + ${I7000D}/i7010_chan.c + ${I7000D}/i7000_cdp.c + ${I7000D}/i7000_cdr.c + ${I7000D}/i7000_con.c + ${I7000D}/i7000_chan.c + ${I7000D}/i7000_lpr.c + ${I7000D}/i7000_mt.c + ${I7000D}/i7000_chron.c + ${I7000D}/i7000_dsk.c + ${I7000D}/i7000_com.c + ${I7000D}/i7000_ht.c + INCLUDES + ${CMAKE_CURRENT_SOURCE_DIR} + DEFINES + I7010 + LABEL I7000 + PKG_FAMILY ibm_family + TEST i7010) + +add_simulator(i704 + SOURCES + ${I7000D}/i7090_cpu.c + ${I7000D}/i7090_sys.c + ${I7000D}/i7090_chan.c + ${I7000D}/i7090_cdr.c + ${I7000D}/i7090_cdp.c + ${I7000D}/i7090_lpr.c + ${I7000D}/i7000_mt.c + ${I7000D}/i7090_drum.c + ${I7000D}/i7000_chan.c + INCLUDES + ${I7000D} + DEFINES + I704 + FEATURE_INT64 + LABEL I7000 + PKG_FAMILY ibm_family + TEST i704) + +add_simulator(i7070 + SOURCES + ${I7000D}/i7070_cpu.c + ${I7000D}/i7070_sys.c + ${I7000D}/i7070_chan.c + ${I7000D}/i7000_cdp.c + ${I7000D}/i7000_cdr.c + ${I7000D}/i7000_con.c + ${I7000D}/i7000_chan.c + ${I7000D}/i7000_lpr.c + ${I7000D}/i7000_mt.c + ${I7000D}/i7000_chron.c + ${I7000D}/i7000_dsk.c + ${I7000D}/i7000_com.c + ${I7000D}/i7000_ht.c + INCLUDES + ${I7000D} + DEFINES + I7070 + FEATURE_INT64 + LABEL I7000 + PKG_FAMILY ibm_family + TEST i7070) + +add_simulator(i7080 + SOURCES + ${I7000D}/i7080_cpu.c + ${I7000D}/i7080_sys.c + ${I7000D}/i7080_chan.c + ${I7000D}/i7080_drum.c + ${I7000D}/i7000_cdp.c + ${I7000D}/i7000_cdr.c + ${I7000D}/i7000_con.c + ${I7000D}/i7000_chan.c + ${I7000D}/i7000_lpr.c + ${I7000D}/i7000_mt.c + ${I7000D}/i7000_chron.c + ${I7000D}/i7000_dsk.c + ${I7000D}/i7000_com.c + ${I7000D}/i7000_ht.c + INCLUDES + ${I7000D} + DEFINES + I7080 + LABEL I7000 + PKG_FAMILY ibm_family + TEST i7080) + +add_simulator(i7090 + SOURCES + i7090_cpu.c + i7090_sys.c + i7090_chan.c + i7090_cdr.c + i7090_cdp.c + i7090_lpr.c + i7000_chan.c + i7000_mt.c + i7090_drum.c + i7090_hdrum.c + i7000_chron.c + i7000_dsk.c + i7000_com.c + i7000_ht.c + INCLUDES + ${CMAKE_CURRENT_SOURCE_DIR} + DEFINES + I7090 + FEATURE_INT64 + LABEL I7000 + PKG_FAMILY ibm_family + TEST i7090) diff --git a/IBM360/CMakeLists.txt b/IBM360/CMakeLists.txt new file mode 100644 index 00000000..522a6f64 --- /dev/null +++ b/IBM360/CMakeLists.txt @@ -0,0 +1,40 @@ +## IBM360 simulator +## +## This is an automagically generated file. Do NOT EDIT. +## Any changes you make will be overwritten!! +## +## Make changes to the SIMH top-level makefile and then run the +## "cmake/generate.py" script to regenerate these files. +## +## cd cmake; python -m generate --help +## +## ------------------------------------------------------------ + +if (HAVE_UNITY_FRAMEWORK AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/unit-tests/CMakeLists.txt") + add_subdirectory(unit-tests) +endif () + +add_simulator(ibm360 + SOURCES + ibm360_cpu.c + ibm360_sys.c + ibm360_con.c + ibm360_chan.c + ibm360_cdr.c + ibm360_cdp.c + ibm360_mt.c + ibm360_lpr.c + ibm360_dasd.c + ibm360_com.c + ibm360_scom.c + ibm360_scon.c + ibm360_vma.c + INCLUDES + ${CMAKE_CURRENT_SOURCE_DIR} + DEFINES + IBM360 + DONT_USE_AIO_INTRINSICS + FEATURE_INT64 + LABEL IBM360 + PKG_FAMILY ibm_family + TEST ibm360) diff --git a/ICL1900/CMakeLists.txt b/ICL1900/CMakeLists.txt new file mode 100644 index 00000000..d207e022 --- /dev/null +++ b/ICL1900/CMakeLists.txt @@ -0,0 +1,37 @@ +## ICL1900 simulator +## +## This is an automagically generated file. Do NOT EDIT. +## Any changes you make will be overwritten!! +## +## Make changes to the SIMH top-level makefile and then run the +## "cmake/generate.py" script to regenerate these files. +## +## cd cmake; python -m generate --help +## +## ------------------------------------------------------------ + +if (HAVE_UNITY_FRAMEWORK AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/unit-tests/CMakeLists.txt") + add_subdirectory(unit-tests) +endif () + +add_simulator(icl1900 + SOURCES + icl1900_cpu.c + icl1900_sys.c + icl1900_stdio.c + icl1900_cty.c + icl1900_tr.c + icl1900_tp.c + icl1900_cr.c + icl1900_cp.c + icl1900_lp.c + icl1900_mta.c + icl1900_mt.c + icl1900_eds8.c + INCLUDES + ${CMAKE_CURRENT_SOURCE_DIR} + DEFINES + ICL1900 + LABEL ICL1900 + PKG_FAMILY ict_family + TEST icl1900) diff --git a/PDP10/CMakeLists.txt b/PDP10/CMakeLists.txt new file mode 100644 index 00000000..81b7808f --- /dev/null +++ b/PDP10/CMakeLists.txt @@ -0,0 +1,222 @@ +## PDP10 simulators +## +## This is an automagically generated file. Do NOT EDIT. +## Any changes you make will be overwritten!! +## +## Make changes to the SIMH top-level makefile and then run the +## "cmake/generate.py" script to regenerate these files. +## +## cd cmake; python -m generate --help +## +## ------------------------------------------------------------ + +if (HAVE_UNITY_FRAMEWORK AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/unit-tests/CMakeLists.txt") + add_subdirectory(unit-tests) +endif () + + +option(PANDA_LIGHTS + "Enable (=1)/disable (=0) KA-10/KI-11 simulator's Panda display. (def: disabled)" + FALSE) +option(PIDP10 + "Enable (=1)/disable (=0) PIDP10 display options (def: disabled)" + FALSE) + +### Ensure that the options are mutually exclusive: +if (PANDA_LIGHTS AND PIDP10) + message(FATAL_ERROR "PANDA_LIGHTS and PIDP10 options are mutually exclusive. Choose one.") +endif () + + +add_simulator(pdp10-ka + SOURCES + ${KA10D}/kx10_cpu.c + ${KA10D}/kx10_sys.c + ${KA10D}/kx10_df.c + ${KA10D}/kx10_dp.c + ${KA10D}/kx10_mt.c + ${KA10D}/kx10_cty.c + ${KA10D}/kx10_lp.c + ${KA10D}/kx10_pt.c + ${KA10D}/kx10_dc.c + ${KA10D}/kx10_rp.c + ${KA10D}/kx10_rc.c + ${KA10D}/kx10_dt.c + ${KA10D}/kx10_dk.c + ${KA10D}/kx10_cr.c + ${KA10D}/kx10_cp.c + ${KA10D}/kx10_tu.c + ${KA10D}/kx10_rs.c + ${KA10D}/ka10_pd.c + ${KA10D}/kx10_rh.c + ${KA10D}/kx10_imp.c + ${KA10D}/ka10_tk10.c + ${KA10D}/ka10_mty.c + ${KA10D}/ka10_imx.c + ${KA10D}/ka10_ch10.c + ${KA10D}/ka10_stk.c + ${KA10D}/ka10_ten11.c + ${KA10D}/ka10_auxcpu.c + ${KA10D}/ka10_pmp.c + ${KA10D}/ka10_dkb.c + ${KA10D}/pdp6_dct.c + ${KA10D}/pdp6_dtc.c + ${KA10D}/pdp6_mtc.c + ${KA10D}/pdp6_dsk.c + ${KA10D}/pdp6_dcs.c + ${KA10D}/ka10_dpk.c + ${KA10D}/kx10_dpy.c + ka10_ai.c + ${KA10D}/ka10_iii.c + ${KA10D}/kx10_disk.c + ka10_pclk.c + ka10_tv.c + ${KA10D}/kx10_ddc.c + ka10_dd.c + ${DISPLAY340} + INCLUDES + ${KA10D} + DEFINES + KA=1 + FEATURE_INT64 + FEATURE_VIDEO + FEATURE_DISPLAY + LABEL PDP10 + PKG_FAMILY pdp10_family + TEST ka10) + +if (PANDA_LIGHTS) + target_sources(pdp10-ka PUBLIC ${PDP10D}/kx10_lights.c) + target_compile_definitions(pdp10-ka PUBLIC PANDA_LIGHTS) + target_link_libraries(pdp10-ka PUBLIC usb-1.0) +endif () +if (PIDP10) + target_sources(pdp10-ka PUBLIC ${PDP10D}/ka10_pipanel.c) + target_compile_definitions(pdp10-ka PUBLIC PIDP10=1) +endif () + +add_simulator(pdp10-ki + SOURCES + ${KI10D}/kx10_cpu.c + ${KI10D}/kx10_sys.c + ${KI10D}/kx10_df.c + ${KI10D}/kx10_dp.c + ${KI10D}/kx10_mt.c + ${KI10D}/kx10_cty.c + ${KI10D}/kx10_lp.c + ${KI10D}/kx10_pt.c + ${KI10D}/kx10_dc.c + ${KI10D}/kx10_rh.c + ${KI10D}/kx10_rp.c + ${KI10D}/kx10_rc.c + ${KI10D}/kx10_dt.c + ${KI10D}/kx10_dk.c + ${KI10D}/kx10_cr.c + ${KI10D}/kx10_cp.c + ${KI10D}/kx10_tu.c + ${KI10D}/kx10_rs.c + ${KI10D}/kx10_imp.c + ${KI10D}/kx10_dpy.c + ${KI10D}/kx10_disk.c + ${KI10D}/kx10_ddc.c + ${KI10D}/kx10_tym.c + ${DISPLAY340} + INCLUDES + ${KI10D} + DEFINES + KI=1 + FEATURE_INT64 + FEATURE_VIDEO + FEATURE_DISPLAY + LABEL PDP10 + PKG_FAMILY pdp10_family + TEST ki10) + +add_simulator(pdp10-kl + SOURCES + ${KL10D}/kx10_cpu.c + ${KL10D}/kx10_sys.c + ${KL10D}/kx10_df.c + ${KA10D}/kx10_dp.c + ${KA10D}/kx10_mt.c + ${KA10D}/kx10_lp.c + ${KA10D}/kx10_pt.c + ${KA10D}/kx10_dc.c + ${KL10D}/kx10_rh.c + ${KA10D}/kx10_dt.c + ${KA10D}/kx10_cr.c + ${KA10D}/kx10_cp.c + ${KL10D}/kx10_rp.c + ${KL10D}/kx10_tu.c + ${KL10D}/kx10_rs.c + ${KL10D}/kx10_imp.c + ${KL10D}/kl10_fe.c + ${KL10D}/ka10_pd.c + ${KL10D}/ka10_ch10.c + ${KL10D}/kl10_nia.c + ${KL10D}/kx10_disk.c + ${KL10D}/kl10_dn.c + INCLUDES + ${KL10D} + DEFINES + KL=1 + FEATURE_INT64 + LABEL PDP10 + PKG_FAMILY pdp10_family + TEST kl10) + +add_simulator(pdp10-ks + SOURCES + ${KS10D}/kx10_cpu.c + ${KS10D}/kx10_sys.c + ${KS10D}/kx10_disk.c + ${KS10D}/ks10_cty.c + ${KS10D}/ks10_uba.c + ${KS10D}/kx10_rh.c + ${KS10D}/kx10_rp.c + ${KS10D}/kx10_tu.c + ${KS10D}/ks10_dz.c + ${KS10D}/ks10_tcu.c + ${KS10D}/ks10_lp.c + ${KS10D}/ks10_ch11.c + ${KS10D}/ks10_kmc.c + ${KS10D}/ks10_dup.c + ${KS10D}/kx10_imp.c + INCLUDES + ${KS10D} + DEFINES + KS=1 + FEATURE_INT64 + LABEL PDP10 + PKG_FAMILY pdp10_family + TEST ks10) + +add_simulator(pdp6 + SOURCES + ${PDP6D}/kx10_cpu.c + ${PDP6D}/kx10_sys.c + ${PDP6D}/kx10_cty.c + ${PDP6D}/kx10_lp.c + ${PDP6D}/kx10_pt.c + ${PDP6D}/kx10_cr.c + ${PDP6D}/kx10_cp.c + ${PDP6D}/pdp6_dct.c + ${PDP6D}/pdp6_dtc.c + ${PDP6D}/pdp6_mtc.c + ${PDP6D}/pdp6_dsk.c + ${PDP6D}/pdp6_dcs.c + ${PDP6D}/kx10_dpy.c + ${PDP6D}/pdp6_slave.c + ${PDP6D}/pdp6_ge.c + ${DISPLAY340} + INCLUDES + ${PDP6D} + DEFINES + PDP6=1 + FEATURE_INT64 + FEATURE_VIDEO + FEATURE_DISPLAY + USES_AIO + LABEL PDP10 + PKG_FAMILY pdp10_family + TEST pdp6) diff --git a/SEL32/CMakeLists.txt b/SEL32/CMakeLists.txt new file mode 100644 index 00000000..446ec26d --- /dev/null +++ b/SEL32/CMakeLists.txt @@ -0,0 +1,42 @@ +## SEL32 simulator +## +## This is an automagically generated file. Do NOT EDIT. +## Any changes you make will be overwritten!! +## +## Make changes to the SIMH top-level makefile and then run the +## "cmake/generate.py" script to regenerate these files. +## +## cd cmake; python -m generate --help +## +## ------------------------------------------------------------ + +if (HAVE_UNITY_FRAMEWORK AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/unit-tests/CMakeLists.txt") + add_subdirectory(unit-tests) +endif () + +add_simulator(sel32 + SOURCES + sel32_cpu.c + sel32_sys.c + sel32_chan.c + sel32_iop.c + sel32_com.c + sel32_con.c + sel32_clk.c + sel32_mt.c + sel32_lpr.c + sel32_scfi.c + sel32_fltpt.c + sel32_disk.c + sel32_hsdp.c + sel32_mfp.c + sel32_scsi.c + sel32_ec.c + sel32_ipu.c + INCLUDES + ${CMAKE_CURRENT_SOURCE_DIR} + DEFINES + SEL32 + LABEL SEL32 + PKG_FAMILY gould_family + TEST sel32) diff --git a/cmake/.gitignore b/cmake/.gitignore new file mode 100644 index 00000000..a85f9553 --- /dev/null +++ b/cmake/.gitignore @@ -0,0 +1,6 @@ +# Ignore captured compiler/build outputs (txt and out files) +*.txt +*.out +__pycache__/ +build-*/ +dependencies/ \ No newline at end of file diff --git a/cmake/CMake-Maintainers.md b/cmake/CMake-Maintainers.md new file mode 100644 index 00000000..6ba916bf --- /dev/null +++ b/cmake/CMake-Maintainers.md @@ -0,0 +1,710 @@ + +**Table of Contents** + +- [CMake Maintainer Walk-through](#cmake-maintainer-walk-through) + - [Introduction](#introduction) + - [*CMake* Basics](#cmake-basics) + - [`CMAKE_SOURCE_DIR` and `CMAKE_BINARY_DIR`](#cmake_source_dir-and-cmake_binary_dir) + - [Top-level `CMakeLists.txt`](#top-level-cmakeliststxt) + - [Build Configurations](#build-configurations) + - [Code Roadmap](#code-roadmap) + - [`${CMAKE_SOURCE_DIR}/CMakeLists.txt`](#cmake_source_dircmakeliststxt) + - [The `cmake` sub-directory](#the-cmake-sub-directory) + - [`add_simulator.cmake`](#add_simulatorcmake) + - [`find_package` support](#find_package-support) + - [`platform-quirks.cmake`: Platform-specific settings/tweaks](#platform-quirkscmake-platform-specific-settingstweaks) + - [`vcpkg.cmake`: vcpkg package installer/maintainer](#vcpkgcmake-vcpkg-package-installermaintainer) + - [`dep-locate.cmake`, `dep-link.cmake`: Dependency libraries](#dep-locatecmake-dep-linkcmake-dependency-libraries) + - [`simh-simulators.cmake`](#simh-simulatorscmake) + - [`generate.py`: Automagic `makefile` → CMake infrastructure](#generatepy-automagic-makefile--cmake-infrastructure) + - [`generate.py` Internals](#generatepy-internals) + - [Using `generate.py` outside of *open-simh*](#using-generatepy-outside-of-open-simh) + - [CPack configuration](#cpack-configuration) + + + +# CMake Maintainer Walk-through + +## Introduction + +This code walk-through is primarily intended for maintainers and developers interested in the +[CMake][cmake] build and packaging environment internals. + +The main idea is to make adding and compiling simulators easy for the simulator developers through a +single *CMake* function, `add_simulator`. All a simulator developer should have to do is create a +simulator `CMakeLists.txt` file, invokes the `add_simulator` function and update the +`cmake/simh-simulators.cmake` file to include the new simulator sub-directory. That's it. + +The remainder of this document explains the *CMake* infrastructure machinery that supports the +`add_simulator` function. + +## *CMake* Basics + +If you are not familiar with *CMake*, *CMake* is a meta-build platform that supports a wide cross section +of operating systems and build systems. Typical *CMake*-based development has four phases: + +- _Configure and generate_: Search for headers, dependency libraries, adjust compiler flags, add defines + and include paths, set up build targets (static, dynamic libraries and executables), setup test case + support and packaging, then generate the build system's input (e.g., a `makefile` for make, solution + hierarchy for Visual Studio, `build.ninja` for Ninja, project files for Mac OS X XCode, etc.) + +- _Build_: Invoke the build system (`make`, `ninja`, `msbuild`, ...) *CMake*'s build mode is a wrapper + around the underlying build system tool. It's perfectly acceptable to change to the build directory and + invoke the build tool directly. + +- _Test_: Execute the test cases using the CTest driver and collect test status (success, failure, failure + output logs.) + +- _Package_: Create installer packages using the CPack utility. + +### `CMAKE_SOURCE_DIR` and `CMAKE_BINARY_DIR` + +*CMake* has two variables that identify the top-level source directory, `CMAKE_SOURCE_DIR`, and the build +artifact directory, `CMAKE_BINARY_DIR`. These two directories should be separate from each other -- you +should not try to create build artifacts in the top-level source directory (this is enforced by the +top-level `CMakeLists.txt` code.) + +### Top-level `CMakeLists.txt` + +The top-level `CMakeLists.txt` file, located in `CMAKE_SOURCE_DIR`, drives the configure/generate +phase. Each simulator sub-directory has its own `CMakeLists.txt` file included by top-level +`CMakeLists.txt`. These per-simulator `CMakeLists.txt` files define one or more simulator executables +along with each executable's simulator-specific defines, include paths and needed SIMH features (i.e., +video support, 64-bit integer, 64-bit address support.) + +### Build Configurations + +*CMake* understands multi-configuration build system tools, notably _msbuild_ and _XCode_, versus single +configuration build system tools, such as _make_ and _ninja_. Multi-configuration build system tools +require an explicit configuration at the build phase, e.g. `--config Release` or `--config Debug` to build +optimized vs. debug simulators. Single configuration build system tools specify `CMAKE_BUILD_TYPE` at the +configure/generate phase. If you need to change the build configuration for a single configuration tool, +you have to reconfigure. Note that if you specify a configuration during the build phase for a single +configuration build system tool, it will be silently ignored. + +The SIMH *CMake* infrastructure has a strong preference for the *Release* and *Debug* build +configurations. The *MinSizeRel* and *RelDbgInfo* configurations are weakly supported; their compile flags +are not altered the way that the Release and Debug configurations are customized. + +## Code Roadmap + +``` +open-simh This is CMAKE_SOURCE_DIR ++-- .github/workflows +| + build.yml GitHub CI/CD driver for push, pull request +| | builds. Uses cmake-builds.yml for CMake builds. +| + cmake-builds.yml Reusable GitHub CI/CD workflow for CMake +| | builds. +| + release.yml GitHub CI/CD driver for packaging and publishing +| releases. Uses cmake-builds.yml to create the +| packaged artifacts (simulators and docs.) +| ++-- CMakeLists.txt Top-level CMake configure/generate driver. ++-- cmake CMake support code +| +-- CMake-Walkthrough.md Documentation. You're reading it. +| +-- FindEDITLINE.cmake find_package support for libeditline +| +-- FindPCAP.cmake find_package support for libpcap +| +-- FindPCRE.cmake find_package support for PCRE +| +-- FindPCRE2.cmake find_package support for PCRE2 +| +-- FindPTW.cmake Platform thread support +| +-- FindVDE.cmake find_package support for VDE networking +| +-- GitHub-release.md Pre-built binary package install instructions +| +-- add_simulator.cmake The add_simulator function, SIMH core libraries +| +-- build-* cmake-builder script output (CMAKE_BINARY_DIR) +| | directories +| +-- build_dep_matrix.cmake Release and Debug build scaffolding for +| | external dependency libraries +| +-- cmake-builder.ps1 PowerShell builder script +| +-- cmake-builder.sh Bash builder script +| +-- cpack-setup.cmake CPack packaging setup code +| +-- dep-link.cmake CMake interface library configuration from +| | previously located dependency libraries +| +-- dep-locate.cmake Dependency library location code, e.g., find +| | PCRE, SDL2, SDL2_ttf, ... +| +-- dependencies External dependency library installation +| | | hierarchy, if building dep. libraries +| | +-- Windows-10-MSVC-19.34 (Example 32-bit Windows 10 dependencies) +| | +-- Windows-10-MSVC-19.34-64 (Example 64-bit Windows 10 dependencies) +| +-- file-link-copy.cmake CMake script to symlink, hard link or copy a +| | file +| +-- fpintrin.cmake Unused. Detects SSE, SSE2 and SSE3. +| +-- generate.py Python script that generates the simulator +| | CMakeLists.txt from the makefile +| +-- git-commit-id.cmake CMake script to update .git-commit-id and +| | .git-commit-id.h +| +-- github_v141_xp.ps1 Visual Studio "XP toolkit" installer PowerShell +| | script +| +-- installer-customizations Installer-specific customization files +| +-- os-features.cmake Operating system feature probes, e.g., -lm +| +-- patches Patches applied to external dependency libs +| +-- platform-quirks.cmake Platform quirks: compiler flags, HomeBrew +| | includes, ... +| +-- pthreads-dep.cmake Detect platform thread support, configure the +| | thread_lib interface library +| +-- simgen generate.py script support +| +-- simh-packaging.cmake Simulator packaging, adds simulators to +| | CPack components (simulator "families") +| +-- simh-simulators.cmake Simulator add_subdirectory includes, variable +| | definitions +| +-- vcpkg-setup.cmake vcpkg package manager setup code +``` + +## `${CMAKE_SOURCE_DIR}/CMakeLists.txt` + +The top-level `CMakeLists.txt` drives CMake's configure/generate phase, which has the following flow: + +- Initial sanity checks + - Ensure *CMake*'s version is greater or equal to version 3.14. *CMake* will terminate configuration if + the minimum version is not met. + - Check `CMAKE_SOURCE_DIR` and `CMAKE_BINARY_DIR`, terminating with an error message if + `CMAKE_SOURCE_DIR` and `CMAKE_BINARY_DIR` are the same directory. + - If *CMake*'s version is below 3.21, emit a warning that creating installers will be unsuccessful. This + doesn't prevent building simulators. This warning is only emitted once. + - Emit a fatal error message and terminate if Windows XP compatibility was requested via `-T v141_xp` on + the command line and the `VCPKG_ROOT` environment variable is set. + +- Set the SIMH version variables and call the `project` function to initiate project configuration. + +- [Configure `vcpkg`](#vcpkgcmake-vcpkg-package-installermaintainer). + +- Set [GNUInstallDirs][gnuinstalldirs] installation directory layout. + +- If the build system tool only supports a single configuration, such as _make_ and _ninja_, default to a + Release build configuration if `CMAKE_BUILD_TYPE` isn't set. + +- Generate a system identifier, `SIMH_SYSTEM_ID`. + + `SIMH_SYSTEM_ID` is used as a `dependencies` subdirectory name on platforms for which missing external + dependency libraries need to be built (Windows, exclusively.) + +- Define and process options, specified on the command line with `-DFOO=BAR` arguments. + - `NO_DEP_BUILD_OPTVAL`: This is the default value for `NO_DEP_BUILD`. + - If `NO_DEP_BUILD` has a cached value already, leave it alone. + - Initialize `NO_DEP_BUILD_OPTVAL` to `False` so that dependencies are never built. + - When the detected system is Windows and NOT a MinGW variant and NOT using [vcpkg](#vcpkg), + `NO_DEP_BUILD_OPTVAL` is set to `True` so that missing dependencies are built. + - `MAC_UNIVERSAL_OPTVAL`: This is the default value for `MAC_UNIVERSAL`. + - If `MAC_UNIVERSAL` has a cached value already, leave it alone. + - Initialize `MAC_UNIVERSAL_OPTVAL` to `False` unless `MAC_UNIVERSAL` was specified as an option on + the command line. + - This is a placeholder for future work to support macOS universal binaries. + +- Set `CMAKE_INSTALL_PREFIX` to `${CMAKE_SOURCE_DIR}/SIMH-install` as a default installation + destination. Otherwise, a platform-specific prefix such as `/usr/local` might be used and cause + unexpected surprises. + +- Set the default `CMAKE_RUNTIME_OUTPUT_DIRECTORY` value to `SIMH_LEGACY_INSTALL`. `SIMH_LEGACY_INSTALL` + is set to `${CMAKE_SOURCE_DIR}/BIN`, appending `Win32` if on Windows, to emulate the SIMH `makefile`'s + executable output structure. + +- Tweak CMake's library and include search paths so that *CMake* can find the externally built dependency + libraries, if needed. + +- [Deal with platform-specific quirkiness](#platform-quirkscmake-platform-specific-settingstweaks), such + as compiler flags, optimization levels, HomeBrew include and library directories on macOS. + +- Locate dependencies, create and populate the `os_features`, `thread_lib`, `simh_regexp`, `simh_video` + and `simh_network` interface libraries. + - *CMake* interface libraries encapsulate defines, include paths and dynamic/static link libraries as an + integrated package. + - `add_simulator` references the interface libraries via CMake's `target_link_libraries` function, which + adds the interface libary's defines and include paths to the simulator compile flags. + + Note: If the interface library is empty, i.e., nothing was added to it because particular + functionality wasn't found or undesired, the interface library adds nothing to the simulator's compile + flags or link libraries. This permits consistency and simplicity in `add_simulator`'s implementation. + - `os_features` (`os-features.cmake`): Operating-system specific features, such testing whether `-lrt` + contains the definitions for `shm_open`, tests for assorted system headers, adds the `winmm` and + Windows socket libraries on Windows. + + Note: `os-features.cmake` could potentially fold itself into `platform-quirks.cmake` in the future. It + is separate for the time being for functional clarity. + - `thread_lib` (`pthreads-dep.cmake`): Platform threading support. Usually empty for Linux and macOS + platforms, adds the _PThreads4W_ external dependency for Windows native and XP platforms. + - `simh_regexp`: Regular expression support, if the `WITH_REGEX` option is `True`. PCRE is effectively + the only regular expression library that ends up in the `simh_regexp` interface library at the + moment. There is support for PCRE2, but it requires changes to `scp.c` to actually make it useful. + - `simh_video`: Graphics, input controller and sound support, based on [SDL2][libsdl2], if the + `WITH_VIDEO` option is `True`. Empty interface library if `WITH_VIDEO` is `False`. `simh_video` also + pulls in `libpng` and `SDL_ttf`, along with their dependencies. + - `simh_network`: This interface library collects defines and include paths for network support when + *VDE* networking is enabled and used, as well as *TUN* network defines and includes. + +- Output a summary of dependencies and features. + +- If the `NO_DEP_BUILD` option is `True` (which is usually the case) and there are missing dependency + libraries, print a fatal error message with the missing libraries, suggest to the user how they might + fix this (`.travis/deps.sh`) and exit. + +- Initiate a "superbuild" if missing dependency libraries need to be built and `NO_DEP_BUILD` is `False`. + - A superbuild is CMake's terminology for building external software and libraries (see *CMake's* + [ExternalProject module][external_project]). When the superbuild successfully builds the external + projects, it will re-execute the *CMake* configure/generate phase to re-detect them and continue + building the SIMH simulators. + - The superbuild normally happens only once. A superbuild can be reinitiated if the compiler's version + changes or the dependencies subdirectory is cleaned. + +- Add the simulators (`simh-simulators.cmake`) + +- Configure packaging + - `cpack-setup.cmake`: Per-packager configuration, i.e., customizations for the [Nullsoft Scriptable + Install System][nullsoft], macOS installer, Debian `.deb` packager. + - `simh-packaging.cmake`: Define simulator package families (CPack components), add documentation to the + default runtime support component. + +- End of file. Configuration is complete and *CMake* generates the build system's files appropriate to the + specified generator, i.e., `makefile` for Unix Makefiles, `build.ninja` for the Ninja build system, etc. + + +## The `cmake` sub-directory + +### `add_simulator.cmake` + +*add_simulator* is the centerpiece around which the rest of the CMake-based infrastructure revolves. The +basic principle is to make simulator compiles very straightforward with a simple *CMake* function. The +function's full documentation for usage and options are in [README-CMake.md][cmake_readme]. + +`add_simulator.cmake` decomposes into eight (8) sections: + +- Update `${CMAKE_SOURCE_DIR}/.git-commit-id` and `${CMAKE_SOURCE_DIR}/.git-commit-id.h` with the current + Git hash identifier, if it has changed. These files are only rewritten if the hash identifier has + changed, i.e., there has been a new commit. + +- `build_simcore` function: This is the workhorse function that compiles the six (6) simulator core + libraries: `simhcore`, `simhi64`, `simhz64` and their `_video` counterparts. Network support is always + included in the core libraries. Networking is enabled by default, and must be explicitly disabled. + + Each core library includes the `simh_network`, `simh_regexp`, `os_features` and `thread_lib` interface + libraries, which causes the core library to inherit the interface libraries' command line defines, + include directories and link libraries. Consequently, a core library is a single `target_link_library` + unit from *CMake*'s perspective and simplifies `add_simulator`. + + `build_simcore` also adds a `${core_libray}_cppcheck`, e.g., `simhcore_cppcheck`, to execute `cppcheck` + static analysis on the library's source. `cppcheck` rules are only added if the `ENABLE_CPPCHECK` option + is `True` (*CMake* configuration option) and the `cppcheck` executable is available. + +- `simh_executable_template`: Common code used by the `add_simulator` and `add_unit_test` functions to + compile an executable. The basic flow is: + + - `add_executable` to create the simulator's executable target in the build system, add the simulator's + sources to get executable target. + - Set the C dialect to C99, which causes the generator to add the necessary flags to the compile + command line to request the C99 dialect. Note that some compilers can ignore the request. + - Set `${CMAKE_RUNTIME_OUTPUT_DIRECTORY}` to `${SIMH_LEGACY_INSTALL}` to mimic the SIMH `makefile`'s + binary output structure. + - Add extra target compiler flags and linker flags; these flags are usually set or modified in + `platform_quirks.cmake`. + - MINGW and MSVC: Set the console subsystem linker option. + - Add simulator-specific defines and includes, define `USE_DISPLAY` on the command line if video + support requested. + - Add the appropriate SIMH core library to executable's target link library list, i.e., select one of + the six core librarys. + +- `add_simulator`: The simulator developer's "compile this simulator" function. + + - Call `simh_executable_template` to create the simulator executable target. + - Add the simulator executable target to the *CPack* installed executables list. If using *CMake* 3.21 + or later, also add the imported runtime artifacts. These are primarily Windows DLLs, but could also + include macOS shared libraries. + - *CTest* simulator test setup. Each simulator always executes `RegisterSanityCheck`. If the `TEST` + option is passed to `add_simulator` **and** the simulator's `tests` subdirectory and test script + exist, the test script will execute after `RegisterSanityCheck`. + - Create a simulator `cppcheck` static analysis target if the `ENABLE_CPPCHECK` option is `True` and the + `cppcheck` executable exists. + - Add the `DONT_USE_INTERNAL_ROM` to the executable's command line if the `DONT_USE_ROMS` option is + `True`. (Probably could move this earlier to after the call to `simh_executable_template`.) + +- `add_unit_test`: This function is intended for future use to execute non-simulator C-code unit tests, to + potentially subsume SIMH's `testlib` command. It is not currently used. These non-simulator unit tests + are supposed to utilize the [Unity test framework][unity_framework], written in "pure" C. + +- Add the SIMH core support libraries targets the `build_simcore` function. + +- Add the `BuildROMs` executable, add the ROM header files as outputs from `BuildROMs`. Future **FIXME**: + Add the ROM header files as build targets that depend on their respective ROM binary files so that + they're automagically rebuilt. However, there is no corresponding rule or set of rules in the SIMH + `makefile`, which is why this is a **FIXME**. + +- Add the `frontpaneltest` executable. `frontpaneltest` provides its own `main` function, which prevents + it from being linked directly with a SIMH core library, e.g., `simhcore`. It has to be its own special + executable target. + +### `find_package` support + +[`find_package`][find_package] is *CMake*'s functionality to find a package and set variables for compile +defines, includes and link libraries, when found. *CMake* has [a collection][cmake_modules] of +`find_package` modules for well known, commonly used packages. *CMake* searches `${CMAKE_MODULE_PATH}` +for modules outside of its packaged collection; SIMH adds `${CMAKE_SOURCE_DIR}/cmake` to +`${CMAKE_MODULE_PATH}`. + +The `Find.cmake` modules used by SIMH and provided by *CMake* include: + +- ZLIB: The zlib compression library +- Freetype: The Freetype font library +- PNG: The PNG graphics library + +SIMH includes six `find_package` scripts: + +- `FindEDITLINE.cmake`: Locates *libeditline*, adds *termcap* to the linker's library list. Applicable to + non-Windows systems to provide command line history. + +- `FindPCAP.cmake`: Locates the packet capture library's headers. SIMH does not need the `libpcap` packet + capture library; `libpcap` and it's Win32 equivalent are dynamically loaded at run time. The headers are + only needed for `libpcap`'s function prototypes. + +- `FindPCRE.cmake`, `FindPCRE2.cmake`: Locate PCRE and PCRE2 libraries and headers. SIMH does not support + PCRE2 yet, however, finding the PCRE2 headers and library are included to make PCRE2 support easier at + some point in the future. + +- `FindPTW.cmake`: "PTW" is shorthand for the Windows PThreads4w POSIX threads compatibility library. + +- `FindVDE.cmake`: Locates the VDE networking headers and library if the `WITH_VDE` option is `True`. + +In addition to `Find.cmake` modules, packages can also supply *CMake* configuration +modules. SDL2 and SDL2-ttf generate and install *CMake* cofiguration files that are used in lieu of a +`find_package` module. + +### `platform-quirks.cmake`: Platform-specific settings/tweaks + +`platform_quirks.cmake` is the code container for managing platform-specific compiler and linker +settings. Specific "quirks": + +- Set *CMake*'s libary architecture variables (`CMAKE_C_LIBRARY_ARCHITECTURE`, + `CMAKE_LIBRARY_ARCHITECTURE`) on Linux. + +- Windows compiler-specific quirks: + - Ensure that SIMH links with the correct multi-threaded Visual C runtime. + - Ensure that SIMH's source compiles with single byte character sets. + - Increase warning verbosity if the `DEBUG_WALL` configuration option is `True`. + - Make warnings fatal if the `WARNINGS_FATAL` configuration option is `True`. + +- There are no specific quirks for Linux. + +- Adjust the GNU and Clang compilers' `Release` build configuration flags, such as link-time optimization + (LTO) and *-O2* optimization, fatal warnings, ... + +- Construct the macOS *HomeBrew* and *MacPorts* header and library search paths so that `find_package` + succeeds. + +### `vcpkg.cmake`: vcpkg package installer/maintainer + +Setting the `VCPKG_ROOT` environment variable enables [vcpkg][vcpkg] support in SIMH. _vcpkg_ is a package +management system designed primarily for Windows Visual C/C++ software development with integrated *CMake* +support. _vcpkg_ also supports [MinGW-w64][mingw-w64], Linux and macOS. The GitHub CI/CD pipeline does not +use _vcpkg_ for either the Linux or macOS builds. [MinGW-w64][mingw-w64] is a SIMH CMake-supported +platform, but is not normally targeted in a CI/CD pipeline. Also, [MinGW-w64][mingw-w64] uses the +[Pacman][pacman] package manager, which makes _vcpkg_ redundant. + +_vcpkg_ does not support producing Windows XP binaries. *CMake* will emit a fatal error message if +`VCPKG_ROOT` is present __and__ the *CMake* configure/generate command line specified the Windows +XP-compatible toolset `-T vc141_xp`. + +`CMakeLists.txt` sets the globally visible variable `USING_VCPKG`. This makes better shorthand than having +to use `DEFINED VCPKG_ROOT` in conditionals and makes those tests more readable. + +Functional flow: +- If `USING_VCPKG` is `False`, return from `vcpkg-setup.cmake` immediately. + +- Construct the _vcpkg_ "(architecture)-(platform)-(runtime)" triplet + - Use the user's desired triplet if `VCPKG_DEFAULT_TRIPLET` exists as an environment variable. + - Windows: Always choose a statically linked, static runtime triple for MS Visual C/C++ or Clang -- + "(x64|x86|arm|arm64)-windows-static". + - MinGW-w64: Triplets are "(x64|x86)-mingw-dynamic". Note: Waiting for a new community triplet that + supports the statically linked runtime. + +- Set `VCPKG_CRT_LINKAGE` to "static" if the triplet ends in "-static". If not, set `VCPKG_CRT_LINKAGE` to + "dynamic". + +- Execute the _vcpkg_ *CMake* toolchain initialization. This installs the packages listed as dependencies + in `vcpkg.json`: + - pthreads + - pcre + - libpng + - sdl2 + - sdl2-ttf + +### `dep-locate.cmake`, `dep-link.cmake`: Dependency libraries + +`dep-locate.cmake` is the consolidated code container for locating SIMH's dependency libraries: +*PCRE/PCRE2* regular expressions; *libpng* graphics for screen captures; *SDL2*, *SDL2-ttf*, *freetype* +video support, *VDE* network support, the *zlib* compression needed by *libpng* and *PCRE/PCRE2*, +etc. It is divided into two sections: + +- Locate packages: First try `find_package` to locate required packages, falling back to `pkgconfig` if + `pkgconfig` is available. `dep-locate.cmake` also understands how to use `vcpkg`-based packages, since + some of the package names differ from what would normally be expected. *PCRE*, in particular, is named + *unofficial-pcre* vs. *PCRE*. + +- Superbuild setup: `dep-locate.cmake` constructs the external project builds for missing dependency + libraries in preparation for the *superbuild*. Currently, this only applies to Windows. There is a list + of source URL variables that immediately follows `include (ExternalProject)` that specify where the + *superbuild*'s external projects download the dependency's source code. + + - *The source URL list needs to be periodically reviewed to bump library version numbers as new library + versions are released.* + - `sourceforce.net` mirrors' SSL responses are **very** quirky and requires multiple mirror URLs to + succeed. + + **Future FIXME**: The superbuild would be entirely unnecessary if SIMH didn't have to support Windows XP + binaries. `vcpkg` should be the future dependency library package manager for Windows, but it does not + support the XP toolkit and likely never will. + +`dep-link.cmake` is the consolidated code container that populates the `simh_regexp`, `simh_video` and +`simh_network` interface libraries. Note that the order in which dependency libraries are added to these +interface libraries is important: *SDL2-ttf* must precede *freetype* and *SDL2*, for example. This reverse +ordering is standard library linking practice, given that linkers only make a single pass through each +library to resolve symbols. + +### `simh-simulators.cmake` + +`simh-simulators.cmake` is the list of SIMH simulator subdirectories added as *CMake* subprojects. Each +simulator subdirectory has its own `CMakeLists.txt` file that contains calls to +`add_simulator`. Subprojects are a *CMake* idiom and maps well to build system generators, such as +Visual Studio and XCode, which assume a subdirectory-subproject organization. + +This file is currently autogenerated by the `generate.py` Python script, although, this may not the the +case in the future. + +### `generate.py`: Automagic `makefile` → CMake infrastructure + +The SIMH `makefile` is still considered the authoritative source for simulator compiler command lines and +source code. `generate.py` was built to scrape the `makefile`'s `all` and `exp` rules, gather simulator +source code lists, simulator-specific defines, and emit the simulator subdirectory `CMakeLists.txt` and +the `simh-simulators.cmake` files. + +To synchronize the *CMake* infrastructure with the `makefile` when new simulators are added to the +`makefile`, when compiler command lines change or new simulator source code is added: + +``` shell +$ (cd cmake; python3 -m generate) + +## Alternatively: + +$ python3 cmake/generate.py +``` + +Note that `generate.py` ensures that it has collected all of the `makefile`-s simulators by cross-referencing +the simulators enumerated in the `cmake/simgen/packaging.py` script to the collected simulators scraped from +the `makefile`. When the expected simulators do not match the `generate.py`-collected simulator list, +`generate.py` will list the missing simulators and exit. If you are maintaining a separate simulator source +repository, please customize your `cmake/simgen/packaging.py` script to reflect the expected simulators in +your source tree. + + +#### `generate.py` Internals + +`generate.py` has three principal classes defined in the `cmake/simgen` subdirectory: `CMakeBuildSystem`, +`SimCollection` and `SIMHBasicSimulator`. + +- `CMakeBuildSystem` (`cmake_container.py`): The top-level container for the entire SIMH simulator collection + scraped from the `makefile`. It's a container that maps a `SimCollection` simulator group to a subdirectory. + The `CMakeBuildSystem.extract` method interprets the `makefile`'s parsed contents, and builds up the + per-subdirectory `SimCollection` simulator groups. + +- `SimCollection` (`sim_collection.py`): A group of simulators, e.g., all of the VAX or PDP-11 simulators, + or the PDP-8 simulator. It also maps simulator source macro names to source lists that become *CMake* + variables to make the emitted `CMakeLists.txt` files more readable. The `SimCollection.write_simulators` + method emits the simulator subdirectory `CMakeLists.txt` file. + +- `SIMHBasicSimulator` (`basic_simulator.py`): An individual SIMH simulator stored inside a `SimCollection`. + This class tracks of the simulator's sources, simulator-specific defines and include paths, as well as + detects when the simulator requires 64-bit address and 64-bit data, when the simulator requires video + support. The `SIMHBasicSimulator.write_section` method emits the individual simulator's `add_simulator` + function call to the `CMakeLists.txt` file stream passed by the parent `SimCollection`. + + `SIMHBasicSimulator` has several subclasses that specialize the `write_section` method. For example, the + *BESM6* simulator requires a Cyrillic font, which requires additional *CMake* code to search for an + available font from a list of known Cyrillic-supporting fonts. The `VAXSimulator` class injects *CMake* + code to symlink, hard link or copy the `vax` executable to `microvax3900`. The + `SimCollection.special_simulators` dictionary maps simulator names to specialized `SIMHBasicSimulator` + subclasses. If the simulator's name is not present in the `special_simulators` dictionary, + `SIMHBasicSimulator` is used. + +#### Using `generate.py` outside of *open-simh* + +`generate.py` can be used outside of the *open-simh* project for separately maintained simulator +repositories. If you do use `generate.py` in your own simulator repository, you **must** customize +the `simgen/packaging.py` script so that the expected simulators matches the collected simulators. + +An example `packaging.py` script that can be copied, pasted and customized (leave the boilerplate +in place, edit and customize after the "Your customizations here...") + +```python +### packaging.py boilerplate starts: + +import os +import functools + +## Initialize package_info to an empty dictionary here so +## that it's visible to write_packaging(). +package_info = {} + + +class SIMHPackaging: + def __init__(self, family, install_flag = True) -> None: + self.family = family + self.processed = False + self.install_flag = install_flag + + def was_processed(self) -> bool: + return self.processed == True + + def encountered(self) -> None: + self.processed = True + +class PkgFamily: + def __init__(self, component_name, display_name, description) -> None: + self.component_name = component_name + self.display_name = display_name + self.description = description + + def write_component_info(self, stream, indent) -> None: + pkg_description = self.description + if pkg_description[-1] != '.': + pkg_description += '.' + sims = [] + for sim, pkg in package_info.items(): + if pkg.family is self and pkg.was_processed(): + sims.append(sim) + sims.sort() + + if len(sims) > 0: + sims.sort() + pkg_description += " Simulators: " + ', '.join(sims) + indent0 = ' ' * indent + indent4 = ' ' * (indent + 4) + stream.write(indent0 + "cpack_add_component(" + self.component_name + "\n") + stream.write(indent4 + "DISPLAY_NAME \"" + self.display_name + "\"\n") + stream.write(indent4 + "DESCRIPTION \"" + pkg_description + "\"\n") + stream.write(indent0 + ")\n") + + def __lt__(self, obj): + return self.component_name < obj.component_name + def __eq__(self, obj): + return self.component_name == obj.component_name + def __gt__(self, obj): + return self.component_name > obj.component_name + def __hash__(self): + return hash(self.component_name) + +def write_packaging(toplevel_dir) -> None: + families = set([sim.family for sim in package_info.values()]) + pkging_file = os.path.join(toplevel_dir, 'cmake', 'simh-packaging.cmake') + print("==== writing {0}".format(pkging_file)) + with open(pkging_file, "w") as stream: + ## Runtime support family: + stream.write("""## The default runtime support component/family: +cpack_add_component(runtime_support + DISPLAY_NAME "Runtime support" + DESCRIPTION "Required SIMH runtime support (documentation, shared libraries)" + REQUIRED +) + +## Basic documentation for SIMH +install(FILES doc/simh.doc TYPE DOC COMPONENT runtime_support) + +""") + + ## Simulators: + for family in sorted(families): + family.write_component_info(stream, 0) + +## The default packaging family for simulators not associated with +## any particular family. Also used for runtime and documentation: +default_family = PkgFamily("default_family", "Default SIMH simulator family.", + """The SIMH simulator collection of historical processors and computing systems that do not belong to +any other simulated system family""" +) + +### packaging.py boilerplate ends... + +### Your customizations here: + +### Instantiate a simulator package family: +foosim_family = PkgFamily("foosim_family", "A collection of simulators", + """Description of your simulators, may span multiple lines within the three quote +marks.""" +) + +### Add simulators to the package famil(y|ies) +package_info["sim1"] = SIMHPackaging(foosim_family) +package_info["sim2"] = SIMHPackaging(foosim_family) +``` + + +### CPack configuration + +[*CPack*][cpack] is the *CMake* utility for packaging SIMH's pre-built binaries. Like *CMake*, *CPack* has +its own generators that support different packaging systems. Some packaging systems only support a single, +all-in-one package, like Debian `.deb` packages. Other packaging systems support a finer grained approach +with individually selectable package components, like the NullSoft Installation System (NSIS) and macOS' +"productbuild" packagers. + +`cpack-setup.cmake` imports the *CPack* module and customizes *CPack* variables and generator +settings. Principally, `cpack-setup.cmake` sets the output name for the package, which is +`simh-major.minor-suffix`. The suffix defaults to the system's name, `${CMAKE_SYSTEM_NAME}` for +non-Windows operating systems, and `(win64|win32)-(Debug|Release)-(vs2022|vs2019|vs2017|vs2015)[-xp]` for +Windows systems. The `-xp` suffix is only appended when building Windows XP-compatible binaries. Setting +the `SIMH_PACKAGE_SUFFIX` environment variable overrides the default suffix. + +Platform and generator-specific settings in `cpack-setup.cmake` include: + +- Runtime installation exclusions: A list of files that should be excluded from the resulting installation + package. For Windows, *CPack* should not install well known DLLs or anything from the `system32` + directory. Linux and macOS do not currently have a runtime exclusion list. If that changes, edit the + `pre_runtime_exclusions` and `post_runtime_exclusions` variables' regular expressions. + +- NullSoft Installation System (NSIS): Arrange to use the `installer-customizations/NSIS.template.in` + script timeplate (see below.) NSIS also uses "SIMH-major.minor" (e.g., "SIMH-4.0") as the installation + directory's name and defaults to installing SIMH in the user's `$LocalAppData\Programs". + +- WIX Windows Installer: Sets the WIX GUID for SIMH. This GUID was entirely fabricated by a GUID + generator. + +- Debian: Adds Debian package dependencies for *libsd2*, *libsd2-ttf*, *libpcap*, *libvdeplug2* and + *libedit2*. **This dependency list needs to be periodically revisited when dependencies change.** + +`simh-packaging.cmake` defines package components and basic runtime support installation common to all +simulators. Package components are groups of SIMH simulator "families" -- there is a strong correlation +between a simulator family and the SIMH simulator subdirectories. For example, all of the VAX simulators +belong to the `vax_family` component. `simh-packaging.cmake` creates the `vax_family` *CPack* component +and `add_simulator` adds a simulator to its package family via its `PKG_FAMILY ` option. + +The `experimental_family` is the exception to the simulator family-subdirectory rule, serving as the +package family for the currently experimental simulators. + +The `runtime_support` family is intended for executables and documentation common to all +simulators. Currently, only the `simh.doc` SIMH documentation is part of this family. Future **FIXME**: +populate this family with additional, non-simulator-specific documentation. + +The `cmake/installer-customizations` subdirectory is where *CPack*-specific generator customizations +should be kept. The NSIS installer template was altered so that the resulting SIMH installer executable +only required user privileges to installer; the default escalates to "admin" privileges, which are +unnecessary for SIMH. + + +[cmake]: https://cmake.org +[cmake_modules]: https://gitlab.kitware.com/cmake/cmake/-/tree/master/Modules +[cmake_readme]: ../README-CMake.md +[cpack]: https://cmake.org/cmake/help/latest/module/CPack.html +[cpack_generators]: https://cmake.org/cmake/help/latest/manual/cpack-generators.7.html#manual:cpack-generators(7) +[external_project]: https://cmake.org/cmake/help/latest/module/ExternalProject.html +[find_package]: https://cmake.org/cmake/help/latest/command/find_package.html +[gnuinstalldirs]: https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html +[libsdl2]: https://github.com/libsdl-org/SDL +[mingw-w64]: https://www.mingw-w64.org/ +[nullsoft]: https://sourceforge.net/projects/nsis/ +[pacman]: https://archlinux.org/pacman/ +[unity_framework]: http://www.throwtheswitch.org/unity +[vcpkg]: https://vcpkg.io +[vde_network]: https://wiki.virtualsquare.org/#!index.md + + diff --git a/cmake/FindEDITLINE.cmake b/cmake/FindEDITLINE.cmake new file mode 100644 index 00000000..c04c28a2 --- /dev/null +++ b/cmake/FindEDITLINE.cmake @@ -0,0 +1,142 @@ +# +# This module is designed to find/handle editline library +# +# Requirements: +# - CMake >= 2.8.3 (for new version of find_package_handle_standard_args) +# +# The following variables will be defined for your use: +# - EDITLINE_INCLUDE_DIRS : editline include directory +# - EDITLINE_LIBRARIES : editline libraries +# - EDITLINE_VERSION : complete version of editline (x.y) +# - EDITLINE_MAJOR_VERSION : major version of editline +# - EDITLINE_MINOR_VERSION : minor version of editline +# +# How to use: +# 1) Copy this file in the root of your project source directory +# 2) Then, tell CMake to search this non-standard module in your project directory by adding to your CMakeLists.txt: +# set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}) +# 3) Finally call find_package(EditLine) once +# +# Here is a complete sample to build an executable: +# +# set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}) +# +# find_package(EditLine REQUIRED) # Note: name is case sensitive +# +# include_directories(${EDITLINE_INCLUDE_DIRS}) +# add_executable(myapp myapp.c) +# target_link_libraries(myapp ${EDITLINE_LIBRARIES}) +# + + +#============================================================================= +# Copyright (c) 2014, julp +# +# Distributed under the OSI-approved BSD License +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +#============================================================================= + +########## Private ########## +if(NOT DEFINED EDITLINE_PUBLIC_VAR_NS) + set(EDITLINE_PUBLIC_VAR_NS "EDITLINE") +endif(NOT DEFINED EDITLINE_PUBLIC_VAR_NS) +if(NOT DEFINED EDITLINE_PRIVATE_VAR_NS) + set(EDITLINE_PRIVATE_VAR_NS "_${EDITLINE_PUBLIC_VAR_NS}") +endif(NOT DEFINED EDITLINE_PRIVATE_VAR_NS) + +function(editline_debug _VARNAME) + if(${EDITLINE_PUBLIC_VAR_NS}_DEBUG) + if(DEFINED ${EDITLINE_PUBLIC_VAR_NS}_${_VARNAME}) + message("${EDITLINE_PUBLIC_VAR_NS}_${_VARNAME} = ${${EDITLINE_PUBLIC_VAR_NS}_${_VARNAME}}") + else(DEFINED ${EDITLINE_PUBLIC_VAR_NS}_${_VARNAME}) + message("${EDITLINE_PUBLIC_VAR_NS}_${_VARNAME} = ") + endif() + endif() +endfunction() + +# Alias all EditLine_FIND_X variables to EDITLINE_FIND_X +# Workaround for find_package: no way to force case of variable's names it creates (I don't want to change MY coding standard) +# --- +# NOTE: only prefix is considered, not full name of the variables to minimize conflicts with string(TOUPPER) for example +# EditLine_foo becomes EDITLINE_foo not EditLine_FOO as this is two different variables +set(${EDITLINE_PRIVATE_VAR_NS}_FIND_PKG_PREFIX "EditLine") +get_directory_property(${EDITLINE_PRIVATE_VAR_NS}_CURRENT_VARIABLES VARIABLES) +foreach(${EDITLINE_PRIVATE_VAR_NS}_VARNAME ${${EDITLINE_PRIVATE_VAR_NS}_CURRENT_VARIABLES}) + if(${EDITLINE_PRIVATE_VAR_NS}_VARNAME MATCHES "^${${EDITLINE_PRIVATE_VAR_NS}_FIND_PKG_PREFIX}") + string(REGEX REPLACE "^${${EDITLINE_PRIVATE_VAR_NS}_FIND_PKG_PREFIX}" "${EDITLINE_PUBLIC_VAR_NS}" ${EDITLINE_PRIVATE_VAR_NS}_NORMALIZED_VARNAME ${${EDITLINE_PRIVATE_VAR_NS}_VARNAME}) + set(${${EDITLINE_PRIVATE_VAR_NS}_NORMALIZED_VARNAME} ${${${EDITLINE_PRIVATE_VAR_NS}_VARNAME}}) + endif() +endforeach() + +########## Public ########## +find_path( + ${EDITLINE_PUBLIC_VAR_NS}_INCLUDE_DIRS + NAMES histedit.h +) + +if(${EDITLINE_PUBLIC_VAR_NS}_INCLUDE_DIRS) + + find_library( + ${EDITLINE_PUBLIC_VAR_NS}_LIBRARIES + NAMES edit + ) + + find_library( + ${EDITLINE_PUBLIC_VAR_NS}_TERMCAP + NAMES termcap + ) + +# file(READ "${${EDITLINE_PUBLIC_VAR_NS}_INCLUDE_DIRS}/histedit.h" ${EDITLINE_PRIVATE_VAR_NS}_H_CONTENT) +# string(REGEX REPLACE ".*# *define +LIBEDIT_MAJOR +([0-9]+).*" "\\1" ${EDITLINE_PUBLIC_VAR_NS}_MAJOR_VERSION ${${EDITLINE_PRIVATE_VAR_NS}_H_CONTENT}) +# string(REGEX REPLACE ".*# *define +LIBEDIT_MINOR +([0-9]+).*" "\\1" ${EDITLINE_PUBLIC_VAR_NS}_MINOR_VERSION ${${EDITLINE_PRIVATE_VAR_NS}_H_CONTENT}) +# set(${EDITLINE_PUBLIC_VAR_NS}_VERSION "${${EDITLINE_PUBLIC_VAR_NS}_MAJOR_VERSION}.${${EDITLINE_PUBLIC_VAR_NS}_MINOR_VERSION}") + + include(FindPackageHandleStandardArgs) + if(${EDITLINE_PUBLIC_VAR_NS}_FIND_REQUIRED AND NOT ${EDITLINE_PUBLIC_VAR_NS}_FIND_QUIETLY) + find_package_handle_standard_args( + ${EDITLINE_PUBLIC_VAR_NS} + REQUIRED_VARS ${EDITLINE_PUBLIC_VAR_NS}_LIBRARIES ${EDITLINE_PUBLIC_VAR_NS}_INCLUDE_DIRS +# VERSION_VAR ${EDITLINE_PUBLIC_VAR_NS}_VERSION + ) + else(${EDITLINE_PUBLIC_VAR_NS}_FIND_REQUIRED AND NOT ${EDITLINE_PUBLIC_VAR_NS}_FIND_QUIETLY) + find_package_handle_standard_args(${EDITLINE_PUBLIC_VAR_NS} "editline not found" ${EDITLINE_PUBLIC_VAR_NS}_LIBRARIES ${EDITLINE_PUBLIC_VAR_NS}_INCLUDE_DIRS) + endif() + +else() + + if(${EDITLINE_PUBLIC_VAR_NS}_FIND_REQUIRED AND NOT ${EDITLINE_PUBLIC_VAR_NS}_FIND_QUIETLY) + message(FATAL_ERROR "Could not find editline include directory") + endif(${EDITLINE_PUBLIC_VAR_NS}_FIND_REQUIRED AND NOT ${EDITLINE_PUBLIC_VAR_NS}_FIND_QUIETLY) + +endif() + +mark_as_advanced( + ${EDITLINE_PUBLIC_VAR_NS}_INCLUDE_DIRS + ${EDITLINE_PUBLIC_VAR_NS}_LIBRARIES +) + +if (${EDITLINE_PUBLIC_VAR_NS}_FOUND) + add_library(Editline::Editline UNKNOWN IMPORTED) + set_target_properties(Editline::Editline PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${${EDITLINE_PUBLIC_VAR_NS}_INCLUDE_DIRS}" + INTERFACE_COMPILE_DEFINITIONS "HAVE_EDITLINE" + INTERFACE_LINK_LIBRARIES "$<$:${${EDITLINE_PUBLIC_VAR_NS}_TERMCAP}>") + set_property(TARGET Editline::Editline APPEND PROPERTY + IMPORTED_LOCATION "${${EDITLINE_PUBLIC_VAR_NS}_LIBRARIES}") +endif () + +# IN (args) +editline_debug("FIND_REQUIRED") +editline_debug("FIND_QUIETLY") +editline_debug("FIND_VERSION") +# OUT +# Linking +editline_debug("INCLUDE_DIRS") +editline_debug("LIBRARIES") +editline_debug("TERMCAP") +# Version +# editline_debug("MAJOR_VERSION") +# editline_debug("MINOR_VERSION") +# editline_debug("VERSION") diff --git a/cmake/FindPCAP.cmake b/cmake/FindPCAP.cmake new file mode 100644 index 00000000..956b413b --- /dev/null +++ b/cmake/FindPCAP.cmake @@ -0,0 +1,76 @@ +# Locate the PCAP library +# +# This module defines: +# +# :: +# +# PCAP_LIBRARIES, the name of the library to link against +# PCAP_INCLUDE_DIRS, where to find the headers +# PCAP_FOUND, if false, do not try to link against +# PCAP_VERSION_STRING - human-readable string containing the version of SDL_ttf +# +# Tweaks: +# 1. PCAP_PATH: A list of directories in which to search +# 2. PCAP_DIR: An environment variable to the directory where you've unpacked or installed PCAP. +# +# "scooter me fecit" + +find_path(PCAP_INCLUDE_DIR + NAMES + pcap.h + HINTS + ENV PCAP_DIR + PATHS + pcap + PCAP + ${PCAP_PATH} + ) + +# if (CMAKE_SIZEOF_VOID_P EQUAL 8) +# set(LIB_PATH_SUFFIXES lib64 x64 amd64 x86_64-linux-gnu aarch64-linux-gnu lib) +# else () +# set(LIB_PATH_SUFFIXES x86) +# endif () + +# find_library(PCAP_LIBRARY +# NAMES +# pcap pcap_static libpcap libpcap_static +# HINTS +# ENV PCAP_DIR +# PATH_SUFFIXES +# ${LIB_PATH_SUFFIXES} +# PATHS +# ${PCAP_PATH} +# ) +# ## message(STATUS "LIB_PATH_SUFFIXES ${LIB_PATH_SUFFIXES}") +# ## message(STATUS "PCAP_LIBRARY is ${PCAP_LIBRARY}") + +# if (WIN32 AND PCAP_LIBRARY) +# ## Only worry about the packet library on Windows. +# find_library(PACKET_LIBRARY +# NAMES +# packet Packet +# HINTS +# ENV PCAP_DIR +# PATH_SUFFIXES +# ${LIB_PATH_SUFFIXES} +# PATHS +# ${PCAP_PATH} +# ) +# else (WIN32 AND PCAP_LIBRARY) +# set(PACKET_LIBRARY) +# endif (WIN32 AND PCAP_LIBRARY) +# ## message(STATUS "PACKET_LIBRARY is ${PACKET_LIBRARY}") + +# set(PCAP_LIBRARIES ${PCAP_LIBRARY} ${PACKET_LIBRARY}) +set(PCAP_INCLUDE_DIRS ${PCAP_INCLUDE_DIR}) +unset(PCAP_LIBRARY) +unset(PCAP_INCLUDE_DIR) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + PCAP + REQUIRED_VARS + ## PCAP_LIBRARIES + PCAP_INCLUDE_DIRS +) diff --git a/cmake/FindPCRE.cmake b/cmake/FindPCRE.cmake new file mode 100644 index 00000000..a96c6547 --- /dev/null +++ b/cmake/FindPCRE.cmake @@ -0,0 +1,102 @@ +# Locate the PCRE library +# +# This module defines: +# +# :: +# +# PCRE_LIBRARIES, the name of the library to link against +# PCRE_INCLUDE_DIRS, where to find the headers +# PCRE_FOUND, if false, do not try to link against +# +# Tweaks: +# 1. PCRE_PATH: A list of directories in which to search +# 2. PCRE_DIR: An environment variable to the directory where you've unpacked or installed PCRE. +# +# "scooter me fecit" + +function(findpcre_debug _VARNAME) + if(FINDPCRE_DEBUG) + if(DEFINED PCRE_${_VARNAME}) + message("PCRE_${_VARNAME} = ${PCRE_${_VARNAME}}") + else(DEFINED PCRE_${_VARNAME}) + message("PCRE_${_VARNAME} = ") + endif(DEFINED PCRE_${_VARNAME}) + endif() +endfunction(findpcre_debug) + +## Normal path to find the PCRE header and library: +find_path(PCRE_INCLUDE_DIR pcre.h + HINTS + ${PC_PCRE_INCLUDEDIR} + ${PC_PCRE_INCLUDE_DIRS} + ENV PCRE_DIR + # path suffixes to search inside ENV{PCRE_DIR} + PATHS ${PCRE_PATH} + PATH_SUFFIXES + pcre + PCRE + ) + +find_library(PCRE_LIBRARY_RELEASE + NAMES + pcre + libpcre + HINTS + ${PC_PCRE_LIBDIR} + ${PC_PCRE_LIBRARY_DIRS} + ENV PCRE_DIR + PATH_SUFFIXES + ${LIB_PATH_SUFFIXES} + PATHS + ${PCRE_PATH} +) + +find_library(PCRE_LIBRARY_DEBUG + NAMES + pcred + libpcred + HINTS + ENV PCRE_DIR + PATH_SUFFIXES + ${LIB_PATH_SUFFIXES} + PATHS + ${PCRE_PATH} +) + +if (PCRE_INCLUDE_DIR) + if (EXISTS "${PCRE_INCLUDE_DIR}/pcre.h") + file(STRINGS "${PCRE_INCLUDE_DIR}/pcre.h" PCRE_VERSION_MAJOR_LINE REGEX "^#define[ \t]+PCRE_MAJOR[ \t]+[0-9]+$") + file(STRINGS "${PCRE_INCLUDE_DIR}/pcre.h" PCRE_VERSION_MINOR_LINE REGEX "^#define[ \t]+PCRE_MINOR[ \t]+[0-9]+$") + endif () + + string(REGEX REPLACE "^#define[ \t]+PCRE?_MAJOR[ \t]+([0-9]+)$" "\\1" PCRE_VERSION_MAJOR "${PCRE_VERSION_MAJOR_LINE}") + string(REGEX REPLACE "^#define[ \t]+PCRE?_MINOR[ \t]+([0-9]+)$" "\\1" PCRE_VERSION_MINOR "${PCRE_VERSION_MINOR_LINE}") + + set(PCRE_VERSION_STRING "${PCRE_VERSION_MAJOR}.${PCRE_VERSION_MINOR}") + unset(PCRE_VERSION_MAJOR_LINE) + unset(PCRE_VERSION_MINOR_LINE) + unset(PCRE_VERSION_MAJOR) + unset(PCRE_VERSION_MINOR) +endif () + +include(SelectLibraryConfigurations) +select_library_configurations(PCRE) + +set(PCRE_LIBRARIES ${PCRE_LIBRARY}) +set(PCRE_INCLUDE_DIRS ${PCRE_INCLUDE_DIR}) + +findpcre_debug(LIBRARY) +findpcre_debug(LIBRARIES) +findpcre_debug(LIBRARY_DEBUG) +findpcre_debug(LIBRARY_RELEASE) +findpcre_debug(VERSION_STRING) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + PCRE + REQUIRED_VARS + PCRE_LIBRARY + PCRE_INCLUDE_DIR + VERSION_VAR + PCRE_VERSION_STRING +) diff --git a/cmake/FindPCRE2.cmake b/cmake/FindPCRE2.cmake new file mode 100644 index 00000000..e2f16dcd --- /dev/null +++ b/cmake/FindPCRE2.cmake @@ -0,0 +1,97 @@ +# Locate the PCRE library +# +# This module defines: +# +# :: +# +# PCRE2_LIBRARIES, the name of the pcre2 library to link against +# PCRE2_INCLUDE_DIRS, where to find the pcre2 headers +# PCRE2_FOUND, if false, do not try to compile or link with pcre2 +# PCRE2_VERSION_STRING - human-readable string containing the version of pcre or pcre2 +# +# Tweaks: +# 1. PCRE_PATH: A list of directories in which to search +# 2. PCRE_DIR: An environment variable to the directory where you've unpacked or installed PCRE. +# +# "scooter me fecit" + +find_path(PCRE2_INCLUDE_DIR pcre2.h + HINTS + ENV PCRE_DIR + # path suffixes to search inside ENV{PCRE_DIR} + PATHS ${PCRE_PATH} + PATH_SUFFIXES + include/pcre + include/PCRE + include + ) + +if (CMAKE_SIZEOF_VOID_P EQUAL 8) + set(LIB_PATH_SUFFIXES lib64 x64 amd64 x86_64-linux-gnu aarch64-linux-gnu lib) +else () + set(LIB_PATH_SUFFIXES x86) +endif () + +find_library(PCRE2_LIBRARY_RELEASE + NAMES + pcre2-8 + pcre2-8-static + HINTS + ENV PCRE_DIR + PATH_SUFFIXES + ${LIB_PATH_SUFFIXES} + PATHS + ${PCRE_PATH} + ) + +find_library(PCRE2_LIBRARY_DEBUG + NAMES + pcre2-8d + pcre2-8-staticd + HINTS + ENV PCRE_DIR + PATH_SUFFIXES + ${LIB_PATH_SUFFIXES} + PATHS + ${PCRE_PATH} + ) + +if (PCRE2_INCLUDE_DIR) + if (EXISTS "${PCRE2_INCLUDE_DIR}/pcre2.h") + file(STRINGS "${PCRE2_INCLUDE_DIR}/pcre2.h" PCRE2_VERSION_MAJOR_LINE REGEX "^#define[ \t]+PCRE2_MAJOR[ \t]+[0-9]+$") + file(STRINGS "${PCRE2_INCLUDE_DIR}/pcre2.h" PCRE2_VERSION_MINOR_LINE REGEX "^#define[ \t]+PCRE2_MINOR[ \t]+[0-9]+$") + endif () + + string(REGEX REPLACE "^#define[ \t]+PCRE2?_MAJOR[ \t]+([0-9]+)$" "\\1" PCRE2_VERSION_MAJOR "${PCRE2_VERSION_MAJOR_LINE}") + string(REGEX REPLACE "^#define[ \t]+PCRE2?_MINOR[ \t]+([0-9]+)$" "\\1" PCRE2_VERSION_MINOR "${PCRE2_VERSION_MINOR_LINE}") + + set(PCRE2_VERSION_STRING "${PCRE2_VERSION_MAJOR}.${PCRE2_VERSION_MINOR}") + unset(PCRE2_VERSION_MAJOR_LINE) + unset(PCRE2_VERSION_MINOR_LINE) + unset(PCRE2_VERSION_MAJOR) + unset(PCRE2_VERSION_MINOR) +endif () + +include(SelectLibraryConfigurations) + +SELECT_LIBRARY_CONFIGURATIONS(PCRE2) + +## message("== PCRE_INCLUDE_DIR ${PCRE_INCLUDE_DIR}") +## message("== PCRE2_LIBRARY ${PCRE2_LIBRARY}") +## message("== PCRE2_LIBRARIES ${PCRE2_LIBRARIES}") +## message("== PCRE2_LIBRARY_DEBUG ${PCRE2_LIBRARY_DEBUG}") +## message("== PCRE2_LIBRARY_RELEASE ${PCRE2_LIBRARY_RELEASE}") + +set(PCRE2_INCLUDE_DIRS ${PCRE2_INCLUDE_DIR}) +set(PCRE2_LIBRARIES ${PCRE2_LIBRARY}) + +include(FindPackageHandleStandardArgs) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS( + PCRE2 + REQUIRED_VARS + PCRE2_LIBRARY + PCRE2_INCLUDE_DIR + VERSION_VAR + PCRE2_VERSION_STRING +) diff --git a/cmake/FindPTW.cmake b/cmake/FindPTW.cmake new file mode 100644 index 00000000..6b16f3e0 --- /dev/null +++ b/cmake/FindPTW.cmake @@ -0,0 +1,75 @@ +# Locate the pthreads4w library and pthreads.h header +# +# This module defines: +# +# :: +# +# PTW_C_LIBRARY, the "pthread[GV]C3" library +# PTW_CE_LIBRARY, the "pthread[GV]CE3" library +# PTW_SE_LIBRARY, the "pthread[GV]SE3" library +# PTW_INCLUDE_DIR, where to find the headers +# PTW_FOUND, if false, do not try to link against +# +# Tweaks: +# 1. PTW_PATH: A list of directories in which to search +# 2. PTW_DIR: An environment variable to the directory where you've unpacked or installed PCRE. +# +# "scooter me fecit" + +if (WIN32) + include(SelectLibraryConfigurations) + + find_path(PTW_INCLUDE_DIR pthread.h + HINTS + ENV PTW_DIR + # path suffixes to search inside ENV{PTW_DIR} + PATH_SUFFIXES include include/pthreads4w + PATHS ${PTW_PATH} + ) + + # if (CMAKE_SIZEOF_VOID_P EQUAL 8) + # set(LIB_PATH_SUFFIXES lib64 x64 amd64 x86_64-linux-gnu) + # else () + # set(LIB_PATH_SUFFIXES x86) + # endif () + + foreach (flavor C CE SE) + if (MSVC) + set(libflavor V${flavor}3) + elseif (MINGW) + set(libflavor G${flavor}3) + endif (MSVC) + + find_library(PTW_${flavor}_LIBRARY_RELEASE + NAMES + libpthread${libflavor} pthread${libflavor} + HINTS + ENV PTW_ DIR + PATH_SUFFIXES + ${LIB_PATH_SUFFIXES} + PATHS + ${PTW_PATH} + ) + + find_library(PTW_${flavor}_LIBRARY_DEBUG + NAMES + libpthread${libflavor}d pthread${libflavor}d + HINTS + ENV PTW_ DIR + PATH_SUFFIXES + ${LIB_PATH_SUFFIXES} + PATHS + ${PTW_PATH} + ) + + SELECT_LIBRARY_CONFIGURATIONS(PTW_${flavor}) + endforeach () + + include(FindPackageHandleStandardArgs) + + # Minimally, we want the include directory and the C library... + find_package_handle_standard_args( + PTW + REQUIRED_VARS PTW_C_LIBRARY PTW_INCLUDE_DIR + ) +endif () diff --git a/cmake/FindVDE.cmake b/cmake/FindVDE.cmake new file mode 100644 index 00000000..231270f5 --- /dev/null +++ b/cmake/FindVDE.cmake @@ -0,0 +1,64 @@ +## Locate the VDE2/VDE4 libvdeplug headers and library. +## +## ::: +## +## Author: B. Scott Michel +## "scooter me fecit" + +if (WITH_VDE) + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(PC_VDE QUIET VDEPLUG) + + if (PC_VDE_VERSION) + set(VDEPLUG_VERSION "${PC_VDE_VERSION}") + endif (PC_VDE_VERSION) + endif () + + find_path(VDEPLUG_INCLUDE_DIR libvdeplug.h + HINTS + ${PC_VDE_INCLUDE_DIRS} + PATH_SUFFIXES + include/libvdeplug + include/vde2 + include/VDE2 + include + ) + + if (CMAKE_SIZEOF_VOID_P EQUAL 8) + set(LIB_PATH_SUFFIXES lib64 x64 amd64 x86_64-linux-gnu aarch64-linux-gnu) + else () + set(LIB_PATH_SUFFIXES x86) + endif () + + find_library(VDEPLUG_LIBRARY_RELEASE + NAMES + vdeplug + HINTS + ${PC_VDE_LIBRARY_DIRS} + PATH_SUFFIXES + ${LIB_PATH_SUFFIXES} + ) + + if (VDEPLUG_INCLUDE_DIR) + ## TBD: Get version info. The header file doesn't provide a way to grep + ## for it. vde_switch will output a version number, but can't really + ## depend on the user having installed the whole VDE package. + endif () + + include(SelectLibraryConfigurations) + + select_library_configurations(VDEPLUG) + + set(VDEPLUG_LIBRARIES ${VDEPLUG_LIBRARY}) + set(VDEPLUG_INCLUDE_DIRS ${VDEPLUG_INCLUDE_DIR}) + + include(FindPackageHandleStandardArgs) + + FIND_PACKAGE_HANDLE_STANDARD_ARGS(VDE + REQUIRED + VDEPLUG_LIBRARY + VDEPLUG_INCLUDE_DIR + # VERSION_VAR VDEPLUG_VERSION_STRING + ) +endif (WITH_VDE) diff --git a/cmake/GitHub-release.md b/cmake/GitHub-release.md new file mode 100644 index 00000000..7575702b --- /dev/null +++ b/cmake/GitHub-release.md @@ -0,0 +1,41 @@ +# SIMH pre-built binaries + +Welcome to SIMH's pre-built binaries! + +- `.deb`: Debian packages for Linux. To install: + + ``` + $ sudo dpkg -i + ``` + +- `.zip`: ZIP-ed executable archive. Use your favorite or appropriate ZIP + software to unzip the archive. The `simh-/bin` subdirectory is the + location of the simulators. + +- `.exe`: Nullsoft Scriptable Install System (NSIS)-created Windows installer. + + NOTE: The executble is not code-signed. DO NOT DOUBLE CLICK ON THE EXECUTABLE + TO INSTALL. If you do, you are likely to get a Windows Defender popup box that + will prevent you from installing SIMH. Instead, use a CMD or PowerShell + command window and execute the `.exe` from the command line prompt. For + example, to install `simh-4.1.0-win32-native.exe`: + + ``` + ## PowerShell: + PS> .\simh-4.1.0-win32-native + + ## CMD: + > .\simh-4.1.0-win32-native + ``` + +- `.msi`: WiX toolkit-created Windows MSI installer. + + These MSI files are not code-signed. DO NOT DOUBLE CLICK ON THE EXECUTABLE TO + INSTALL. If you do, you are likely to get a Windows Defender popup box that + will prevent you from installing SIMH. Instead, use a CMD or PowerShell command + window to manually invoke `msiexec` to install SIMH. From either a PowerShell + or CMD command window: + + ``` + > msiexec /qf /i simh-4.1.0-win32-native.msi + ``` diff --git a/cmake/add_simulator.cmake b/cmake/add_simulator.cmake new file mode 100644 index 00000000..f1e6d896 --- /dev/null +++ b/cmake/add_simulator.cmake @@ -0,0 +1,449 @@ +## Put everything together into one nice function. + +include (CTest) + +## Regenerate the git commit ID if git exists. +find_program(GIT_COMMAND git) +if (GIT_COMMAND) + message(STATUS "Git command is ${GIT_COMMAND}") +else () + message(STATUS "Git not found -- will not update or include .git-commit-id.h") +endif () + +add_custom_target(update_sim_commit ALL + COMMAND ${CMAKE_COMMAND} + -D GIT_COMMIT_DEST=${CMAKE_SOURCE_DIR} + -P ${CMAKE_SOURCE_DIR}/cmake/git-commit-id.cmake + BYPRODUCTS + ${CMAKE_SOURCE_DIR}/.git-commit-id + ${CMAKE_SOURCE_DIR}/.git-commit-id.h + WORKING_DIRECTORY + ${CMAKE_SOURCE_DIR} +) + +## Simulator sources and library: +set(SIM_SOURCES + ${CMAKE_SOURCE_DIR}/scp.c + ${CMAKE_SOURCE_DIR}/sim_card.c + ${CMAKE_SOURCE_DIR}/sim_console.c + ${CMAKE_SOURCE_DIR}/sim_disk.c + ${CMAKE_SOURCE_DIR}/sim_ether.c + ${CMAKE_SOURCE_DIR}/sim_fio.c + ${CMAKE_SOURCE_DIR}/sim_imd.c + ${CMAKE_SOURCE_DIR}/sim_scsi.c + ${CMAKE_SOURCE_DIR}/sim_serial.c + ${CMAKE_SOURCE_DIR}/sim_sock.c + ${CMAKE_SOURCE_DIR}/sim_tape.c + ${CMAKE_SOURCE_DIR}/sim_timer.c + ${CMAKE_SOURCE_DIR}/sim_tmxr.c + ${CMAKE_SOURCE_DIR}/sim_video.c) + +set(SIM_VIDEO_SOURCES + ${CMAKE_SOURCE_DIR}/display/display.c + ${CMAKE_SOURCE_DIR}/display/sim_ws.c) + +## Build a simulator core library, with and without AIO support. The AIO variant +## has "_aio" appended to its name, e.g., "simhz64_aio" or "simhz64_video_aio". +function(build_simcore _targ) + cmake_parse_arguments(SIMH "VIDEO;INT64;ADDR64;BESM6_SDL_HACK" "" "" ${ARGN}) + + # Additional library targets that depend on simulator I/O: + add_library(${_targ} STATIC ${SIM_SOURCES}) + + set(sim_aio_lib "${_targ}_aio") + add_library(${sim_aio_lib} STATIC ${SIM_SOURCES}) + + # Components that need to be turned on while building the library, but + # don't export out to the dependencies (hence PRIVATE.) + foreach (lib IN ITEMS "${_targ}" "${sim_aio_lib}") + set_target_properties(${lib} PROPERTIES + C_STANDARD 99 + EXCLUDE_FROM_ALL True + ) + target_compile_definitions(${lib} PRIVATE USE_SIM_CARD USE_SIM_IMD) + target_compile_options(${lib} PRIVATE ${EXTRA_TARGET_CFLAGS}) + target_link_options(${lib} PRIVATE ${EXTRA_TARGET_LFLAGS}) + + # Make sure that the top-level directory is part of the libary's include path: + target_include_directories("${lib}" PUBLIC "${CMAKE_SOURCE_DIR}") + + if (SIMH_INT64) + target_compile_definitions(${lib} PUBLIC USE_INT64) + endif (SIMH_INT64) + + if (SIMH_ADDR64) + target_compile_definitions(${lib} PUBLIC USE_ADDR64) + endif (SIMH_ADDR64) + + if (SIMH_VIDEO) + if (WITH_VIDEO) + # It's the video library + target_sources(${lib} PRIVATE ${SIM_VIDEO_SOURCES}) + target_link_libraries(${lib} PUBLIC simh_video) + endif () + if (CMAKE_HOST_APPLE AND NOT SIMH_BESM6_SDL_HACK) + ## (a) The BESM6 SDL hack is temporary. If SDL_MAIN_AVAILABLE needs + ## to be defined, it belongs in the simh_video interface library. + ## (b) BESM6 doesn't use SIMH's video capabilities correctly and + ## the makefile filters out SDL_MAIN_AVAILABLE on macOS. + ## (c) This shouldn't be just an Apple platform quirk; SDL_main should + ## be used by all platforms. + target_compile_definitions("${lib}" PUBLIC SDL_MAIN_AVAILABLE) + endif () + endif () + + # Define SIM_BUILD_TOOL for the simulator' + target_compile_definitions("${lib}" PRIVATE + "SIM_BUILD_TOOL=CMake (${CMAKE_GENERATOR})" + ) + + target_link_libraries(${lib} PUBLIC + simh_network + simh_regexp + os_features + thread_lib + ) + + # Ensure that sim_rev.h picks up .git-commit-id.h if the git command is + # available. + if (GIT_COMMAND) + target_compile_definitions("${lib}" PRIVATE SIM_NEED_GIT_COMMIT_ID) + endif () + + add_dependencies(${lib} update_sim_commit) + endforeach () + + target_compile_definitions(${sim_aio_lib} PUBLIC ${AIO_FLAGS}) + + # Create target cppcheck rule, if detected. + if (ENABLE_CPPCHECK AND cppcheck_cmd) + get_property(cppcheck_includes TARGET ${_targ} PROPERTY INCLUDE_DIRECTORIES) + get_property(cppcheck_defines TARGET ${_targ} PROPERTY COMPILE_DEFINITIONS) + list(TRANSFORM cppcheck_includes PREPEND "-I") + list(TRANSFORM cppcheck_defines PREPEND "-D") + + add_custom_target("${_targ}_cppcheck" + COMMAND ${cppcheck_cmd} ${cppcheck_defines} ${cppcheck_includes} ${SIM_SOURCES} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + VERBATIM + ) + + add_dependencies(cppcheck "${_targ}_cppcheck") + endif (ENABLE_CPPCHECK AND cppcheck_cmd) +endfunction(build_simcore _targ) + + +##+ +## Basic template for simh executables and unit tests. +##- +## FEATURE_INT64: Use the simhi64 library (defines USE_INT64) +## FEATURE_FULL64: Use the simhz64 library (defines USE_INT64, USE_ADDR64) +## BUILDROMS: Build the hardcoded boot rooms +## FEATURE_VIDEO: Add video support +## FEATURE_DISPLAY: Add display support +## +## BESM6_SDL_HACK: A TEMPORARY feature to work around issues with BESM6 and +## SDL and SIMH video. Because it's a day ending in "y", there's going to +## be another makefile quirk. +list(APPEND ADD_SIMULATOR_OPTIONS + "FEATURE_INT64" + "FEATURE_FULL64" + "BUILDROMS" + "FEATURE_VIDEO" + "FEATURE_DISPLAY" + "NO_INSTALL" + "BESM6_SDL_HACK" + "USES_AIO" +) + +## TEST: The test script name that will be executed by the simulator within CTest. +## LABEL: The test name label, used to group tests, e.g., "VAX" for all of the +## VAX simulator tests. If you want to run a subset of tests, add the "-L " +## argument to the ctest command line. +## PKG_FAMILY: The simulator family to which a simulator belongs. If not specificed, +## defaults to "default_family". +list(APPEND ADD_SIMULATOR_1ARG + "TEST" + "LABEL" + "PKG_FAMILY" +) + +## DEFINES: List of extra command line manifest constants ("-D" items) +## INCLUDES: List of extra include directories +## SOURCES: List of source files +list(APPEND ADD_SIMULATOR_NARG + "DEFINES" + "INCLUDES" + "SOURCES" +) + +function (simh_executable_template _targ) + cmake_parse_arguments(SIMH "${ADD_SIMULATOR_OPTIONS}" "${ADD_SIMULATOR_1ARG}" "${ADD_SIMULATOR_NARG}" ${ARGN}) + + if (NOT DEFINED SIMH_SOURCES) + message(FATAL_ERROR "${_targ}: No source files?") + endif (NOT DEFINED SIMH_SOURCES) + + if (SIMH_USES_AIO AND NOT WITH_ASYNC) + message(WARNING + "!!! ${_targ}: Asynchronous I/O not enabled, but this simulator specifies USES_AIO\n" + "!!! Some features will be crippled, notably networking.") + endif () + + add_executable("${_targ}" "${SIMH_SOURCES}") + set_target_properties(${_targ} PROPERTIES + C_STANDARD 99 + RUNTIME_OUTPUT_DIRECTORY ${SIMH_LEGACY_INSTALL} + ) + target_compile_options(${_targ} PRIVATE ${EXTRA_TARGET_CFLAGS}) + target_link_options(${_targ} PRIVATE ${EXTRA_TARGET_LFLAGS}) + + if (MINGW) + ## target_compile_options(${_targ} PUBLIC "-fms-extensions") + target_link_options(${_targ} PUBLIC "-mconsole") + elseif (MSVC) + target_link_options(${_targ} PUBLIC "/SUBSYSTEM:CONSOLE") + endif () + + if (DEFINED SIMH_DEFINES) + target_compile_definitions("${_targ}" PUBLIC "${SIMH_DEFINES}") + endif (DEFINED SIMH_DEFINES) + + # This is a quick cheat to make sure that all of the include paths are + # absolute paths. + if (DEFINED SIMH_INCLUDES) + set(_normalized_includes) + foreach (inc IN LISTS SIMH_INCLUDES) + if (NOT IS_ABSOLUTE "${inc}") + message(">> Fixing include for ${_targ}: ${inc} -> ${CMAKE_SOURCE_DIR}/${inc}") + get_filename_component(inc "${CMAKE_SOURCE_DIR}/${inc}" ABSOLUTE) + endif () + list(APPEND _normalized_includes "${inc}") + endforeach () + + target_include_directories("${_targ}" PUBLIC "${_normalized_includes}") + endif () + + if (WITH_VIDEO) + if (SIMH_FEATURE_DISPLAY) + target_compile_definitions(${_targ} PUBLIC USE_DISPLAY) + endif () + endif () + + set(SIMH_SIMLIB simhcore) + if (SIMH_FEATURE_INT64) + set(SIMH_SIMLIB simhi64) + elseif (SIMH_FEATURE_FULL64) + set(SIMH_SIMLIB simhz64) + endif () + + if (SIMH_FEATURE_VIDEO OR SIMH_FEATURE_DISPLAY) + if (NOT SIMH_BESM6_SDL_HACK) + string(APPEND SIMH_SIMLIB "_video") + else () + string(APPEND SIMH_SIMLIB "_besm6") + endif () + endif () + + # Uses AIO... + if (SIMH_USES_AIO) + set(SIMH_SIMLIB "${SIMH_SIMLIB}_aio") + endif() + + target_link_libraries("${_targ}" PUBLIC "${SIMH_SIMLIB}") +endfunction () + + +function (add_simulator _targ) + simh_executable_template(${_targ} "${ARGN}") + cmake_parse_arguments(SIMH "${ADD_SIMULATOR_OPTIONS}" "${ADD_SIMULATOR_1ARG}" "${ADD_SIMULATOR_NARG}" ${ARGN}) + + set(pkg_family "default_family") + if (SIMH_PKG_FAMILY) + set(pkg_family ${SIMH_PKG_FAMILY}) + endif () + + set(_install_opts) + if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.21") + list(APPEND _install_opts RUNTIME_DEPENDENCY_SET simhRuntime) + endif () + + install(TARGETS ${_targ} + ${_install_opts} + RUNTIME + COMPONENT ${pkg_family} + ) + + if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.21") + ## Collect runtime dependencies. The simhRuntime install() is invoked by cpack-setup.cmake + install(IMPORTED_RUNTIME_ARTIFACTS ${_targ} COMPONENT ${pkg_family} BUNDLE COMPONENT ${pkg_family}) + endif() + + ## Simulator-specific tests: + list(APPEND test_cmd "${_targ}" "RegisterSanityCheck") + + if (DEFINED SIMH_TEST) + string(APPEND test_fname ${CMAKE_CURRENT_SOURCE_DIR} "/tests/${SIMH_TEST}_test.ini") + IF (EXISTS "${test_fname}") + list(APPEND test_cmd "${test_fname}" "-v") + ENDIF () + endif () + + add_test(NAME "simh-${_targ}" COMMAND ${test_cmd}) + + if (SIMH_LABEL) + set_tests_properties("simh-${_targ}" PROPERTIES LABELS "simh-${SIMH_LABEL}") + endif () + + if (BUILD_SHARED_DEPS) + ## Make sure that the tests can find the DLLs/shared objects: + file(TO_NATIVE_PATH "${SIMH_DEP_TOPDIR}/bin" native_dep_bindir) + file(TO_NATIVE_PATH "${CMAKE_BINARY_DIR}" native_binary_dir) + endif () + + ## Build up test environment: + ## - Simulators that link SDL2 need to use the dummy (null) drivers on tests within + ## the CI/CD environment. While not STRICTLY necessary, it's a good idea. + list(APPEND test_add_env "SDL_VIDEODRIVER=dummy") + list(APPEND test_add_env "SDL_AUDIODRIVER=dummy") + + if (WIN32) + if (BUILD_SHARED_DEPS) + set(test_path "PATH=${native_dep_bindir}\\\$${native_binary_dir}\\$") + string(REPLACE ";" "\\\$" escaped_path "$ENV{PATH}") + string(APPEND test_path "${escaped_path}") + list(APPEND test_add_env "${test_path}") + endif () + else () + if (BUILD_SHARED_DEPS) + list(APPEND test_add_env "LD_LIBRARY_PATH=${native_dep_bindir}:${native_binary_dir}:\$LD_LIBRARY_PATH") + endif () + endif () + + set_property(TEST "simh-${_targ}" PROPERTY ENVIRONMENT "${test_add_env}") + + if (DONT_USE_ROMS) + target_compile_definitions(DONT_USE_INTERNAL_ROM) + elseif (SIMH_BUILDROMS) + add_dependencies(${_targ} BuildROMs) + endif () + + # Create target 'cppcheck' rule, if cppcheck detected: + if (ENABLE_CPPCHECK AND cppcheck_cmd) + get_property(cppcheck_includes TARGET ${_targ} PROPERTY INCLUDE_DIRECTORIES) + get_property(cppcheck_defines TARGET ${_targ} PROPERTY COMPILE_DEFINITIONS) + list(TRANSFORM cppcheck_includes PREPEND "-I") + list(TRANSFORM cppcheck_defines PREPEND "-D") + add_custom_target("${_targ}_cppcheck" + COMMAND ${cppcheck_cmd} ${cppcheck_defines} ${cppcheck_includes} ${SIMH_SOURCES} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + + add_dependencies(cppcheck "${_targ}_cppcheck") + endif (ENABLE_CPPCHECK AND cppcheck_cmd) +endfunction () + + +function(add_unit_test _targ) + set(UNIT_TARGET "simbase-${_targ}") + set(UNIT_TEST "simbase-${_targ}") + + simh_executable_template(${UNIT_TARGET} "${ARGN}") + cmake_parse_arguments(SIMH "FEATURE_INT64;FEATURE_FULL64;BUILDROMS;FEATURE_VIDEO,FEATURE_DISPLAY" + "SOURCE_DIR;LABEL" + "DEFINES;INCLUDES;SOURCES" + ${ARGN}) + + target_link_libraries(${UNIT_TARGET} PUBLIC unittest) + add_test(NAME ${UNIT_TEST} COMMAND ${UNIT_TARGET}) + + set(TEST_LABEL "simbase;unit") + if (SIMH_LABEL) + list(APPEND TEST_LABEL "simbase-${SIMH_LABEL}") + endif () + set_tests_properties(${UNIT_TEST} PROPERTIES LABELS "${TEST_LABEL}") +endfunction () + +##~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~= +## Now build things! +##~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~= + +if (ENABLE_CPPCHECK AND cppcheck_cmd) + add_custom_target(cppcheck) +endif () + +build_simcore(simhcore) +build_simcore(simhi64 INT64) +build_simcore(simhz64 INT64 ADDR64) +build_simcore(simhcore_video VIDEO) +build_simcore(simhi64_video VIDEO INT64) +build_simcore(simhz64_video VIDEO INT64 ADDR64) + +## Temporary hack for BESM6's breakage. +build_simcore(simhi64_besm6 VIDEO INT64 BESM6_SDL_HACK) + +if (NOT DONT_USE_ROMS) + add_executable(BuildROMs sim_BuildROMs.c) + target_include_directories(BuildROMs PUBLIC "${CMAKE_SOURCE_DIR}") + target_link_libraries(BuildROMs os_features) + add_custom_command( + OUTPUT + ${CMAKE_SOURCE_DIR}/VAX/vax_ka655x_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka620_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka630_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka610_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka410_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka411_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka412_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka41a_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka41d_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka42a_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka42b_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka43a_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka46a_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka47a_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka48a_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_is1000_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka410_xs_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka420_rdrz_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka420_rzrz_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka4xx_4pln_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka4xx_8pln_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka4xx_dz_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka4xx_spx_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka750_bin_new.h + ${CMAKE_SOURCE_DIR}/VAX/vax_ka750_bin_old.h + ${CMAKE_SOURCE_DIR}/VAX/vax_vcb02_bin.h + ${CMAKE_SOURCE_DIR}/VAX/vax_vmb_exe.h + ${CMAKE_SOURCE_DIR}/PDP11/pdp11_vt_lunar_rom.h + ${CMAKE_SOURCE_DIR}/PDP11/pdp11_dazzle_dart_rom.h + ${CMAKE_SOURCE_DIR}/PDP11/pdp11_11logo_rom.h + ${CMAKE_SOURCE_DIR}/swtp6800/swtp6800/swtp_swtbugv10_bin.h + MAIN_DEPENDENCY BuildROMs + COMMAND BuildROMS + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) +endif () + +## Front panel test. +## +## From all evidence in makefile, sim_frontpanel isn't used yet by any targets. +## +## Needs curses... +add_executable(frontpaneltest + ${CMAKE_SOURCE_DIR}/frontpanel/FrontPanelTest.c + ${CMAKE_SOURCE_DIR}/sim_sock.c + ${CMAKE_SOURCE_DIR}/sim_frontpanel.c) + +target_include_directories(frontpaneltest PUBLIC "${CMAKE_SOURCE_DIR}") +target_link_libraries(frontpaneltest PUBLIC os_features thread_lib) + +if (WIN32) + target_link_libraries(frontpaneltest PUBLIC simh_network) + + if (MSVC) + target_link_options(frontpaneltest PUBLIC "/SUBSYSTEM:CONSOLE") + elseif (MINGW) + target_link_options(frontpaneltest PUBLIC "-mconsole") + endif () +endif (WIN32) diff --git a/cmake/build_dep_matrix.cmake b/cmake/build_dep_matrix.cmake new file mode 100644 index 00000000..edc4974d --- /dev/null +++ b/cmake/build_dep_matrix.cmake @@ -0,0 +1,85 @@ +#~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~= +# build_dep_matrix.cmake +# +# This is a minor hack to build all of the various library compile +# configurations. Might take a bit more time upfront to build the +# dependencies, but the user doesn't have to go backward and attempt +# to build the dependencies themselves. +# +# Author: B. Scott Michel +# "scooter me fecit" +#~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~= + + +function(BuildDepMatrix dep pretty) + cmake_parse_arguments(_BDM "" "RELEASE_BUILD;DEBUG_BUILD" "CMAKE_ARGS" ${ARGN}) + + set(cmake_cfg_args + "-G${CMAKE_GENERATOR}" + "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}" + "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}") + + if (CMAKE_GENERATOR_PLATFORM) + list(APPEND cmake_cfg_args "-A" "${CMAKE_GENERATOR_PLATFORM}") + endif () + if (CMAKE_GENERATOR_TOOLSET) + list(APPEND cmake_cfg_args "-T" "${CMAKE_GENERATOR_TOOLSET}") + endif () + string(REPLACE ";" "$" _amend_cmake_prefix_path "${SIMH_PREFIX_PATH_LIST}") + string(REPLACE ";" "$" _amend_cmake_include_path "${SIMH_INCLUDE_PATH_LIST}") + + list(APPEND cmake_cfg_args ${DEP_CMAKE_ARGS}) + list(APPEND cmake_cfg_args -DCMAKE_PREFIX_PATH=${_amend_cmake_prefix_path} + -DCMAKE_INCLUDE_PATH=${_amend_cmake_include_path} + ${_BDM_CMAKE_ARGS} + "" + ) + + if (NOT _BDM_RELEASE_BUILD) + set(_BDM_RELEASE_BUILD "Release") + endif (NOT _BDM_RELEASE_BUILD) + + if (NOT _BDM_DEBUG_BUILD) + set(_BDM_DEBUG_BUILD "Debug") + endif (NOT _BDM_DEBUG_BUILD) + + set(dep_cmds) + foreach (cfg IN ITEMS ${_BDM_DEBUG_BUILD} ${_BDM_RELEASE_BUILD}) + ## Set the MSVC runtime. Can't use a generator expression here, + ## have to "nail it down." + set(use_msvcrt "MultiThreaded") + if (cfg STREQUAL ${_BDM_DEBUG_BUILD}) + string(APPEND use_msvcrt "Debug") + endif () + if (BUILD_SHARED_DEPS) + string(APPEND use_msvcrt "DLL") + endif () + list(APPEND dep_cmds COMMAND ${CMAKE_COMMAND} -E echo "-- Building ${pretty} '${cfg}' configuration") + list(APPEND dep_cmds COMMAND ${CMAKE_COMMAND} -E remove -f CMakeCache.txt) + list(APPEND dep_cmds COMMAND ${CMAKE_COMMAND} -E remove_directory CMakeFiles) + list(APPEND dep_cmds COMMAND ${CMAKE_COMMAND} + ${cmake_cfg_args} + -DCMAKE_BUILD_TYPE:STRING=${cfg} + -DCMAKE_INSTALL_PREFIX:STRING=${SIMH_DEP_TOPDIR} + -DCMAKE_POLICY_DEFAULT_CMP0091:STRING=NEW + -DCMAKE_MSVC_RUNTIME_LIBRARY:STRING=${use_msvcrt} + ) + list(APPEND dep_cmds COMMAND ${CMAKE_COMMAND} --build --config "${cfg}" --clean-first) + list(APPEND dep_cmds COMMAND ${CMAKE_COMMAND} --install --config ${cfg}) + endforeach () + + ## Unset CMAKE_MODULE_PATH temporarily for external projects + set(_saved_cmake_module_path ${CMAKE_MODULE_PATH}) + set(CMAKE_MODULE_PATH "") + + ## message("${dep_cmds}") + ExternalProject_Add_Step(${dep} build-dbg-release + DEPENDEES configure + WORKING_DIRECTORY + ${dep_cmds} + ) + + set(CMAKE_MODULE_PATH ${_saved_cmake_module_path}) + unset(_saved_cmake_module_path) +endfunction () + diff --git a/cmake/cmake-builder.ps1 b/cmake/cmake-builder.ps1 new file mode 100644 index 00000000..30b81dba --- /dev/null +++ b/cmake/cmake-builder.ps1 @@ -0,0 +1,584 @@ +# Author: B. Scott Michel (scooter.phd@gmail.com) +# "scooter me fecit" + +<# +.SYNOPSIS +Configure and build SIMH's dependencies and simulators using the Microsoft Visual +Studio C compiler or MinGW-W64-based gcc compiler. + +.DESCRIPTION +This script executes the three (3) phases of building the entire suite of SIMH +simulators using the CMake meta-build tool. The phases are: + +1. Configure and generate the build environment selected by '-flavor' option. +2. Build missing runtime dependencies and the simulator suite with the compiler + configuration selected by the '-config' option. The "Release" configuration + generates optimized executables; the "Debug" configuration generates + development executables with debugger information. +3. Test the simulators + +There is an install phase that can be invoked separately as part of the SIMH +packaging process. + +The test and install phases can be enabled or disabled by the appropriate command line +flag (e.g., '-noInstall', '-noTest', '-testOnly', '-installOnly'.) + +Build environment and artifact locations: +----------------------------------------- +cmake/build-vs* MSVC build products and artifacts +cmake/build-mingw MinGW-W64 products and artifacts +cmake/build-ninja Ninja builder products and artifacts + +.EXAMPLE +PS> cmake-builder.ps1 -flavor vs2022 -config Release + +Generate/configure, build, test and install the SIMH simulator suite using +the Visual Studio 2022 toolchain in the Release (optimized) compile +configuration. + +.EXAMPLE +PS> cmake-builder.ps1 vs2022 Release + +Another way to generate/configure, build, test and install the SIMH simulator +suite using the Visual Studio 2022 toolchain in the Release (optimized) +compile configuration. + +.EXAMPLE +PS> cmake-builder.ps1 vs2019 Debug -notest -noinstall + +Generate/configure and build the SIMH simulator suite with the Visual Studio +2019 toolchain in the Debug compile configuration. Does not execute tests and +does not install the simulators under the BIN subdirectory in the top of the +source tree. + +.EXAMPLE + +PS> cmake-builder.ps1 -flavor vs2019 -config Release -installonly + +Install the simulators under the BIN subdirectory in the top of the source +tree. Does not generate/configure, but will build to ensure that compile +targets (simulator executables) are up-to-date. +#> + +param ( + ## String arguments are positional, so if the user invokes this script + ## as "cmake-builder.ps1 vs2022 Debug", it's the same as saying + ## "cmake-builder.ps1 -flavor vs2022 -config Debug" + + + ## The build environment's "flavor" that determines which CMake generator is used + ## to create all of the build machinery to compile the SIMH simulator suite + ## and the target compiler. + ## + ## Supported flavors: + ## ------------------ + ## vs2022 Visual Studio 2022 (default) + ## vs2022-xp Visual Studio 2022 XP compat + ## vs2022-x64 Visual Studio 2022 64-bit + ## vs2019 Visual Studio 2019 + ## vs2019-xp Visual Studio 2019 XP compat + ## vs2019-x64 Visual Studio 2019 64-bit + ## vs2017 Visual Studio 2017 + ## vs2017-xp Visual Studio 2017 XP compat + ## vs2017-x64 Visual Studio 2017 64-bit + ## vs2015 Visual Studio 2015 + ## mingw-make MinGW GCC/mingw32-make + ## mingw-ninja MinGW GCC/ninja + [Parameter(Mandatory=$false)] + [string] $flavor = "vs2022", + + ## The target build configuration. Valid values: "Release" and "Debug" + [Parameter(Mandatory=$false)] + [string] $config = "Release", + + ## Supply a suffix for CPack package names via -DSIMH_PACKAGE_SUFFIX + [Parameter(Mandatory=$false)] + [string] $cpack_suffix = "", + + ## (optional) Build a specific simulator or simulators. Separate multiple + ## targets with a comma, ## e.g. "--target pdp8,pdp11,vax750,altairz80,3b2" + [Parameter(Mandatory=$false)] + [string[]] $target = "", + + ## The rest are flag arguments + + ## Clean (remove) the CMake build directory before configuring + [Parameter(Mandatory=$false)] + [switch] $clean = $false, + + ## Get help. + [Parameter(Mandatory=$false)] + [switch] $help = $false, + + ## Compile the SIMH simulator suite without network support. + [Parameter(Mandatory=$false)] + [switch] $nonetwork = $false, + + ## Compile the SIMH simulator suite without video support. + [Parameter(Mandatory=$false)] + [switch] $novideo = $false, + + ## Compile the SIMH simulator without AIO support. + [Parameter(Mandatory=$false)] + [switch] $noaio = $false, + + ## Compile the SIMH simulator without AIO instrinsics ("lock-free" AIO), + ## using lock-based AIO via thread mutexes instead. + [Parameter(Mandatory=$false)] + [switch] $noaiointrinsics = $false, + + ## Disable the build's tests. + [Parameter(Mandatory=$false)] + [switch] $notest = $false, + + ## Do not install the simulator suite in the source directory's BIN + ## subdirectory. + [Parameter(Mandatory=$false)] + [switch] $noinstall = $false, + + ## Enable parallel builds. + [Parameter(Mandatory=$false)] + [switch] $parallel = $false, + + ## Configure and generate the build environment. Don't compile, test or install. + [Parameter(Mandatory=$false)] + [switch] $generate = $false, + + ## Only run the tests. + [Parameter(Mandatory=$false)] + [switch] $testonly = $false, + + ## Only install the SIMH simulator suite in the source directory's BIN + ## subdirectory. + [Parameter(Mandatory=$false)] + [switch] $installOnly = $false, + + ## Turn on Windows API deprecation warnings. NOTE: These warnings are OFF by + ## default. + [Parameter(Mandatory=$false)] + [switch] $windeprecation = $false, + + ## Enable Link-Time Optimization (LTO). + [Parameter(Mandatory=$false)] + [switch] $lto = $false, + + ## Turn on maximal compiler warnings for Debug builds (e.g. "-Wall" or "/W3") + [Parameter(Mandatory=$false)] + [switch] $debugWall = $false, + + ## Enable the cppcheck static code analysis rules + [Parameter(Mandatory=$false)] + [switch] $cppcheck = $false +) + +$scriptName = $(Split-Path -Leaf $PSCommandPath) +$scriptCmd = ${PSCommandPath} + +function Show-Help +{ + Get-Help -full ${scriptCmd} + exit 0 +} + + +## CMake generator info: +class GeneratorInfo +{ + [string] $Generator + [bool] $SingleConfig + [bool] $UCRT + [string] $UCRTVersion + [string[]]$ArchArgs + + GeneratorInfo([string]$gen, $configFlag, $ucrtFlag, $ucrtVer, [string[]]$arch) + { + $this.Generator = $gen + $this.SingleConfig = $configFlag + $this.UCRT = $ucrtFlag + $this.UCRTVersion = $ucrtVer + $this.ArchArgs = $arch + } +} + +## Multiple build configurations selected at compile time +$multiConfig = $false +## Single configuration selected at configuration time +$singleConfig = $true + +$cmakeGenMap = @{ + "vs2022" = [GeneratorInfo]::new("Visual Studio 17 2022", $multiConfig, $false, "", @("-A", "Win32")); + "vs2022-xp" = [GeneratorInfo]::new("Visual Studio 17 2022", $multiConfig, $false, "", @("-A", "Win32", "-T", "v141_xp")); + "vs2022-x64" = [GeneratorInfo]::new("Visual Studio 17 2022", $multiConfig, $false, "", @("-A", "x64", "-T", "host=x64")); + "vs2019" = [GeneratorInfo]::new("Visual Studio 16 2019", $multiConfig, $false, "", @("-A", "Win32")); + "vs2019-xp" = [GeneratorInfo]::new("Visual Studio 16 2019", $multiConfig, $false, "", @("-A", "Win32", "-T", "v141_xp")); + "vs2019-x64" = [GeneratorInfo]::new("Visual Studio 17 2022", $multiConfig, $false, "", @("-A", "x64", "-T", "host=x64")); + "vs2017" = [GeneratorInfo]::new("Visual Studio 15 2017", $multiConfig, $false, "", @("-A", "Win32")); + "vs2017-xp" = [GeneratorInfo]::new("Visual Studio 15 2017", $multiConfig, $false, "", @("-A", "Win32", "-T", "v141_xp")); + "vs2017-x64" = [GeneratorInfo]::new("Visual Studio 17 2022", $multiConfig, $false, "", @("-A", "x64", "-T", "host=x64")); + "vs2015" = [GeneratorInfo]::new("Visual Studio 14 2015", $multiConfig, $false, "", @()); + "mingw-make" = [GeneratorInfo]::new("MinGW Makefiles", $singleConfig, $false, "", @()); + "mingw-ninja" = [GeneratorInfo]::new("Ninja", $singleConfig, $false, "", @()) +} + + +function Get-GeneratorInfo([string]$flavor) +{ + return $cmakeGenMap[$flavor] +} + +function Quote-Args([string[]]$arglist) +{ + return ($arglist | foreach-object { if ($_ -like "* *") { "`"$_`"" } else { $_ } }) +} + + +## Output help early and exit. +if ($help) +{ + Show-Help +} + +### CTest params: +## timeout is 180 seconds +$ctestTimeout = "300" + +## Sanity checking: Check that utilities we expect exist... +## CMake: Save the location of the command because we'll invoke it later. Same +## with CTest +$cmakeCmd = $(Get-Command -Name cmake.exe -ErrorAction Ignore).Path +$ctestCmd = $(Get-Command -Name ctest.exe -ErrorAction Ignore).Path +if ($cmakeCmd.Length -gt 0) +{ + Write-Host "** ${scriptName}: cmake is '${cmakeCmd}'" + Write-Host "** $(& ${cmakeCmd} --version)" +} +else { + @" +!! ${scriptName} error: + +The 'cmake' command was not found. Please ensure that you have installed CMake +and that your PATH environment variable references the directory in which it +was installed. +"@ + + exit 1 +} + +if (!$testonly) +{ + ## Check for GCC and mingw32-make if user wants the mingw flavor build. + if ($flavor -eq "mingw" -or $flavor -eq "ninja") + { + if ($(Get-Command gcc -ErrorAction Ignore).Path.Length -eq 0) { + @" + !! ${scriptName} error: + + Did not find 'gcc', the GNU C/C++ compiler toolchain. Please ensure you have + installed gcc and that your PATH environment variables references the directory + in which it was installed. +"@ + exit 1 + } + + if ($(Get-Command mingw32-make -ErrorAction Ignore).Path.Length -eq 0) { + @" + !! ${scriptName} error: + + Did not find 'mingw32-make'. Please ensure you have installed mingw32-make and + that your PATH environment variables references the directory in which it was + installed. + + See the .travis/deps.sh functions mingw64() and ucrt64() for the pacman packages + that should be installed. +"@ + exit 1 + } + } +} + +## Validate the requested configuration. +if (!@("Release", "Debug").Contains($config)) +{ + @" +${scriptName}: Invalid configuration: "${config}". + +"@ + Show-Help +} + +## Look for Git's /usr/bin subdirectory: CMake (and other utilities) have issues +## with the /bin/sh installed there (Git's version of MinGW.) + +$tmp_path = $env:PATH +$git_usrbin = "${env:ProgramFiles}\Git\usr\bin" +$tmp_path = ($tmp_path.Split(';') | Where-Object { $_ -ne "${git_usrbin}"}) -join ';' +if ($tmp_path -ne ${env:PATH}) +{ + Write-Host "** ${scriptName}: Removed ${git_usrbin} from PATH (Git MinGW problem)" + $env:PATH = $tmp_path +} + +## Also make sure that none of the other cmake-* directories are in the user's PATH +## because CMake's find_package does traverse PATH looking for potential candidates +## for dependency libraries. + +$origPath = $env:PATH +$modPath = $origPath + +if (Test-Path -Path cmake\dependencies) { + $bdirs = $(Get-ChildItem -Attribute Directory cmake\dependencies\*).ForEach({ $_.FullName + "\bin" }) + $modPath = (${env:Path}.Split(';') | Where-Object { $bdirs -notcontains $_ }) -join ';' + if ($modPath -ne $origPath) { + Write-Host "** ${scriptName}: Removed cmake\dependencies 'bin' directories from PATH." + } +} + +## Setup: +$simhTopDir = $(Split-Path -Parent $(Resolve-Path -Path $PSCommandPath).Path) +While (!([String]::IsNullOrEmpty($simhTopDir) -or (Test-Path -Path ${simhTopDir}\CMakeLists.txt))) { + $simhTopDir = $(Split-Path -Parent $simhTopDir) +} +if ([String]::IsNullOrEmpty($simhTopDir)) { + @" +!! ${scriptName}: Cannot locate SIMH top-level source directory from +the script's path name. You should really not see this message. +"@ + + exit 1 +} else { + Write-Host "** ${scriptName}: SIMH top-level source directory is ${simhTopDir}" +} + +$buildDir = "${simhTopDir}\cmake\build-${flavor}" +$genInfo = $(Get-GeneratorInfo $flavor) +if ($null -eq $genInfo) +{ + Write-Host "" + Write-Host "!! ${scriptName}: Unrecognized build flavor '${flavor}'." + Write-Host "" + Show-Help +} + +if ($testonly) +{ + $scriptPhases = @("test") +} +elseif ($generate) +{ + $scriptPhases = @("generate") +} +elseif ($installOnly) +{ + $scriptPhases = @("install") +} +else +{ + $scriptPhases = @( "generate", "build", "test") + if ($notest) + { + $scriptPhases = $scriptPhases | Where-Object { $_ -ne 'test' } + } + if ($noinstall -or ![String]::IsNullOrEmpty($target)) + { + $scriptPhases = $scriptPhases | Where-Object { $_ -ne 'install' } + } +} + +if (($scriptPhases -contains "generate") -or ($scriptPhases -contains "build")) +{ + ## Clean out the build subdirectory + if ((Test-Path -Path ${buildDir}) -and $clean) + { + Write-Host "** ${scriptName}: Removing ${buildDir}" + Remove-Item -recurse -force -Path ${buildDir} -ErrorAction SilentlyContinue | Out-Null + } + + if (!(Test-Path -Path ${buildDir})) + { + Write-Host "** ${scriptName}: Creating ${buildDir} subdirectory" + New-Item -Path ${buildDir} -ItemType Directory | Out-Null + } + else + { + Write-Host "** ${scriptName}: ${buildDir} exists." + } + + ## Unconditionally remove the CMake cache. + Remove-Item -Force -Path ${buildDir}/CMakeCache.txt -ErrorAction SilentlyContinue | Out-Null + Remove-Item -Recurse -Force -Path ${buildDir}/CMakeFiles -ErrorAction SilentlyContinue | Out-Null + + ## Where we do the heaving lifting: + $generateArgs = @("-G", $genInfo.Generator) + if ($genInfo.SingleConfig) { + ## Single configuration set at compile time: + $generateArgs += @("-DCMAKE_BUILD_TYPE=${config}") + } + if ($genInfo.UCRT) { + ## Universal Windows Platform + $generateArgs += @("-DCMAKE_SYSTEM_NAME=WindowsStore", "-DCMAKE_SYSTEM_VERSION=$($genInfo.UCRTVersion)") + } + $generateArgs += $genInfo.ArchArgs + @("-Wno-dev", "--no-warn-unused-cli") + if ($nonetwork) + { + $generateArgs += @("-DWITH_NETWORK:Bool=Off") + } + if ($novideo) + { + $generateArgs += @("-DWITH_VIDEO:Bool=Off") + } + if ($noaio) + { + $generateArgs += @("-DWITH_ASYNC:Bool=Off") + } + if ($noaiointrinsics) + { + $generateArgs += @("-DDONT_USE_AIO_INTRINSICS:Bool=On") + } + if ($lto) + { + $generateArgs += @("-DRELEASE_LTO:Bool=On") + } + if ($debugWall) + { + $generateArgs += @("-DDEBUG_WALL:Bool=On") + } + if ($cppcheck) + { + $generateArgs += @("-DENABLE_CPPCHECK:Bool=On") + } + if (![String]::IsNullOrEmpty($cpack_suffix)) + { + $generateArgs += @("-DSIMH_PACKAGE_SUFFIX:Bool=${cpack_suffix}") + } + + $buildArgs = @("--build", "${buildDir}", "--config", "${config}") + if ($parallel) + { + $buildArgs += "--parallel" + } + if ($verbose) + { + $buildArgs += "--verbose" + } + if ($windeprecation) + { + $buildArgs += "-DWINAPI_DEPRECATION:Bool=TRUE" + } + if (![String]::IsNullOrEmpty($target)) { + foreach ($targ in $target) { + $buildArgs += @("--target", "$targ") + } + } + + $buildSpecificArgs = @() + if ($flavor -eq "mingw" -and $parallel) + { + ## Limit the number of parallel jobs mingw32-make can spawn. Otherwise + ## it'll overwhelm the machine. + $buildSpecificArgs += @("-j", "8") + } +} + +$exitval = 0 + +foreach ($phase in $scriptPhases) { + $savedPATH = $env:PATH + $argList = @() + $phaseCommand = "Write-Output" + + switch -exact ($phase) + { + "generate" { + $generateArgs += @("-S", ${simhTopDir}) + $generateArgs += @("-B", ${buildDir}) + + Write-Host "** ${scriptName}: Configuring and generating" + + $phaseCommand = ${cmakeCmd} + $argList = Quote-Args $generateArgs + } + + "build" { + Write-Host "** ${scriptName}: Building simulators." + + $phaseCommand = ${cmakeCmd} + $argList = $(Quote-Args $buildArgs) + $(Quote-Args $buildSpecificArgs) + } + + "test" { + Write-Host "** ${scriptName}: Testing simulators." + + ## CTest arguments: + $testArgs = @("-C", $config, "--timeout", $ctestTimeout, "-T", "test", + "--output-on-failure") + + ## Output gets confusing (and tests can time out when executing in parallel) + ## if ($parallel) + ## { + ## $testArgs += @("--parallel", $ctestParallel) + ## } + + if ($verbose) + { + $testArgs += @("--verbose") + } + + if (![String]::IsNullOrEmpty($target)) { + $tests = "simh-(" + ($target -join "|") + ")`$" + $testArgs += @("-R", $tests) + } + + $phaseCommand = ${ctestCmd} + $argList = Quote-Args $testArgs + + $env:PATH = $modPath + + $depTopDir = $(& $cmakeCmd -L -N ${buildDir} | Select-String "SIMH_DEP_TOPDIR") + if ($depTopDir) { + ## RHS of the cached variable's value. + $depTopDir = $depTopDir.Line.Split('=')[1] + $env:PATH = "${depTopdir}\bin;${env:PATH}" + } + } + + "install" { + Write-Host "** ${scriptName}: Installing simulators." + + $installPrefix = $(& $cmakeCmd -L -N ${buildDir} | Select-String "CMAKE_INSTALL_PREFIX") + $installPrefix = $installPrefix.Line.Split('=')[1] + $installPath = $installPrefix + + Write-Host "** ${scriptName}: Install directory ${installPath}" + if (!(Test-Path -Path ${installPath})) + { + Write-Host "** ${scriptName}: Creating ${installPath}" + New-Item -${installPath} -ItemType Directory -ErrorAction SilentlyContinue + } + + $phaseCommand = ${cmakeCmd} + $argList = Quote-Args @( "--install", "${buildDir}", "--config", "${config}") + } + } + + try { + Push-Location ${buildDir} + Write-Host "** ${phaseCommand} ${argList}" + & $phaseCommand @arglist + if ($LastExitCode -gt 0) { + $printPhase = (Get-Culture).TextInfo.ToTitleCase($phase) + Write-Error $("${printPhase} phase exited with non-zero status: " + $LastExitCode) + exit 1 + } + } + catch { + Write-Host "Error running '${phaseCommand} ${argList}' command: $($_.Exception.Message)" -ForegroundColor Red + throw $_ + } + finally { + Pop-Location + } + + $env:PATH = $savedPATH +} + +exit $exitval diff --git a/cmake/cmake-builder.sh b/cmake/cmake-builder.sh new file mode 100644 index 00000000..fc9015f1 --- /dev/null +++ b/cmake/cmake-builder.sh @@ -0,0 +1,426 @@ +#!/bin/bash + +##-- Bash functions -- +showHelp() +{ + [ x"$1" != x ] && { echo "${scriptName}: $1"; echo ""; } + cat < /dev/null 2>&1 + if [[ $? -eq 4 ]]; then + getopt_prog="${p}/getopt" + break + fi +done +IFS="${IFS_SAVE}" + +if [[ "x${getopt_prog}" = "x" ]]; then + echo "${scriptName}: GNU getopt needed for this script to function properly." + echo "${scriptName}: Specifically, a 'getopt' that supports the '-T' flag (enhanced getopt)" + exit 1 +fi + +## This script also needs GNU coreutils +realpath=$(which realpath) || { + echo "${scriptName}: Could not find 'realpath'. Please install and re-execute this script." + echo "${scriptName}: 'realpath' is a component of the GNU coreutils collection." +} +dirname=$(which dirname) || { + echo "${scriptName}: Could not find 'dirname'. Please install and re-execute this script." + echo "${scriptName}: 'dirname' is a component of the GNU coreutils collection." +} + +## Check if CMake supports parallel +cmake=$(which cmake) || { + echo "${scriptName}: Could not find 'cmake'. Please install and re-execute this script." + exit 1 +} + +ctest=$(which ctest) || { + echo "${scriptName}: Could not find 'ctest'. Please check your 'cmake' installation." + exit 1 +} + +echo "** $(${cmake} --version)" + +$(${cmake} -h 2>&1 | grep -- "-S" > /dev/null) && { + cmakeSFlag=yes +} + +canParallel=no +(${cmake} --build /tmp --help 2>&1 | grep parallel > /dev/null) && { + canParallel=yes +} + +canTestParallel=no +# (${ctest} --help 2>&1 | grep parallel > /dev/null) && { +# canTestParallel=yes +# } + +if [[ "x${MSYSTEM}" != x ]]; then + case "${MSYSTEM}" in + MSYS|MINGW64) + buildFlavor="MinGW Makefiles" + buildSubdir=build-mingw + ;; + UCRT64) + buildFlavor="Ninja" + buildSubdir=build-ninja + ;; + esac +fi + +longopts=clean,help,flavor:,config:,nonetwork,novideo,notest,parallel,generate,testonly +longopts=${longopts},noinstall,installonly,verbose,target:,lto,debugWall,cppcheck,cpack_suffix: +longopts=${longopts},cache,no-aio,no-aio-intrinsics + +ARGS=$(${getopt_prog} --longoptions $longopts --options xhf:c:pg -- "$@") +if [ $? -ne 0 ] ; then + showHelp "${scriptName}: Usage error (use -h for help.)" +fi + +eval set -- ${ARGS} +while true; do + case $1 in + -x | --clean) + buildClean=yes; shift + ;; + -h | --help) + showHelp + ;; + -f | --flavor) + case "$2" in + unix) + buildFlavor="Unix Makefiles" + buildSubdir=build-unix + shift 2 + ;; + ninja|ucrt64) + buildFlavor=Ninja + buildSubdir=build-ninja + shift 2 + ;; + xcode) + buildFlavor=Xcode + buildSubdir=build-xcode + shift 2 + ;; + xcode-universal) + buildFlavor=Xcode + buildSubdir=build-xcode-universal + generateArgs="${generateArgs} -DMAC_UNIVERSAL:Bool=On" + shift 2 + ;; + mingw|mingw64|msys|msys2) + buildFlavor="MinGW Makefiles" + buildSubdir=build-mingw + shift 2 + ;; + *) + showHelp "Invalid build flavor: $2" + ;; + esac + ;; + -c | --config) + case "$2" in + Release|Debug) + buildConfig=$2 + shift 2 + ;; + *) + showHelp "Invalid build configuration: $2" + ;; + esac + ;; + --nonetwork) + generateArgs="${generateArgs} -DWITH_NETWORK:Bool=Off" + shift + ;; + --novideo) + generateArgs="${generateArgs} -DWITH_VIDEO:Bool=Off" + shift + ;; + --no-aio) + generateArgs="${generateArgs} -DWITH_ASYNC:Bool=Off" + shift + ;; + --no-aio-intrinsics) + generateArgs="${generateArgs} -DDONT_USE_AIO_INTRINSICS:Bool=On" + shift + ;; + --notest) + notest=yes + shift + ;; + --noinstall) + noinstall=yes + shift + ;; + --lto) + generateArgs="${generateArgs} -DRELEASE_LTO:Bool=On" + shift + ;; + --debugWall) + generateArgs="${generateArgs} -DDEBUG_WALL:Bool=On" + shift + ;; + --cppcheck) + generateArgs="${generateArgs} -DENABLE_CPPCHECK:Bool=On" + shift + ;; + --cpack_suffix) + generateArgs="${generateArgs} -DSIMH_PACKAGE_SUFFIX=$2" + shift 2 + ;; + -p | --parallel) + buildParallel=yes + shift + ;; + -g | --generate) + generateOnly=yes + shift + ;; + --cache) + generateOnly=yes + generateArgs="${generateArgs} -LA" + shift + ;; + --testonly) + testOnly=yes + shift + ;; + --installonly) + installOnly=yes + shift + ;; + --verbose) + verboseMode="--verbose" + shift + ;; + --target) + noinstall=yes + simTarget="${simTarget} $2" + shift 2 + ;; + --) + ## End of options. we'll ignore. + shift + break + ;; + esac +done + +# Sanity check: buildSubdir should be set, unless the '-f' flag wasn't present. +if [ "x${buildSubdir}" = x ]; then + echo "" + echo "${scriptName}: Build flavor is NOT SET -- see the \"--flavor\"/\"-f\" flag in the help." + echo "" + showHelp +fi + +## Determine the SIMH top-level source directory: +simhTopDir=$(${dirname} $(${realpath} $0)) +while [ "x${simhTopDir}" != x -a ! -f "${simhTopDir}/CMakeLists.txt" ]; do + simhTopDir=$(${dirname} "${simhTopDir}") +done + +if [[ "x${simhTopDir}" = x ]]; then + echo "${scriptName}: Can't determine SIMH top-level source directory." + echo "Did this really happen?" + exit 1 +else + buildSubdir=$(${realpath} "${simhTopDir}/cmake/")"/${buildSubdir}" + echo "${scriptName}: SIMH top-evel directory: ${simhTopDir}" + echo "${scriptName}: Build directory: ${buildSubdir}" +fi + +if [[ x"$buildClean" != x ]]; then + echo "${scriptName}: Cleaning ${buildSubdir}" + rm -rf ${buildSubdir} +fi + +if [[ ! -d ${buildSubdir} ]]; then + mkdir ${buildSubdir} +fi + +## Setup test arguments (and add parallel later) +testArgs="-C ${buildConfig} --timeout 180 --output-on-failure" + +## Parallel only applies to the unix flavor. GNU make will overwhelm your +## machine if the number of jobs isn't capped. +if [[ x"$canParallel" = xyes ]] ; then + if [ x"$buildParallel" = xyes -a "$buildFlavor" != Ninja ] ; then + (${cmake} --build . --help 2>&1 | grep parallel 2>&1 > /dev/null) && { + buildArgs="${buildArgs} --parallel" + buildPostArgs="${buildPostArgs} -j 8" + } + + # Don't execute ctest in parallel... + # [ x${canTestParallel} = xyes ] && { + # testArgs="${testArgs} --parallel 4" + # } + fi +else + buildParallel= +fi + +if [[ x"${simTarget}" != x ]]; then + simTests="" + for tgt in $(echo ${simTarget} | sed 's/,/ /g'); do + buildArgs="${buildArgs} --target ${tgt}" + [[ x"${simTests}" != x ]] && simTests="${simTests}|" + simTests="${simTests}${tgt}" + done + testArgs="${testArgs} -R simh-(${simTests})\$" +fi + +buildArgs="${buildArgs} --config ${buildConfig}" + +if [[ x$generateOnly = xyes ]]; then + phases=generate +elif [[ x$testOnly = xyes ]]; then + phases=test +elif [[ x$installOnly = xyes ]]; then + phases=install +else + phases="generate build" + if [[ x${notest} != xyes ]]; then + phases="${phases} test" + fi +fi + +for ph in ${phases}; do + case $ph in + generate) + ## Uncondintionally remove the CMake cache. + echo "${scriptName}: Removing CMakeCache.txt and CMakeFiles" + rm -rf ${buildSubdir}/CMakeCache.txt ${buildSubdir}/CMakefiles + + if [[ "x${cmakeSFlag}" != x ]]; then + echo "${cmake} -G "\"${buildFlavor}\"" -DCMAKE_BUILD_TYPE="${buildConfig}" -S "${simhTopDir}" -B ${buildSubdir} ${generateArgs}" + ${cmake} -G "${buildFlavor}" -DCMAKE_BUILD_TYPE="${buildConfig}" -S "${simhTopDir}" -B "${buildSubdir}" ${generateArgs} || { \ + echo "*** ${scriptName}: Errors detected during environment generation. Exiting." + exit 1 + } + else + echo "${cmake} -G "\"${buildFlavor}\"" -DCMAKE_BUILD_TYPE="${buildConfig}" "${simhTopDir}" ${generateArgs}" + ( cd "${buildSubdir}"; \ + ${cmake} -G "${buildFlavor}" -DCMAKE_BUILD_TYPE="${buildConfig}" "${simhTopDir}" ${generateArgs}) || { \ + echo "*** ${scriptName}: Errors detected during environment generation. Exiting."; + exit 1 + } + fi + ;; + build) + ${cmake} --build "${buildSubdir}" ${buildArgs} ${verboseMode} -- ${buildPostArgs} || { + echo "*** ${scriptName}: Build errors detected. Exiting." + exit 1 + } + ;; + test) + (cd "${buildSubdir}" \ + && echo ${ctest} ${testArgs} ${verboseMode} \ + && ${ctest} ${testArgs} ${verboseMode}) || { + echo "*** ${scriptName}: Errors detected during testing. Exiting." + exit 1 + } + ;; + install) + ${cmake} --build "${buildSubdir}" --target install --config "${buildConfig}" + ;; + package) + (cd "${buildSubdir}" \ + && ${cpack} -G ZIP -C ${buildConfig} ${verboseMode} \ + && mv *.zip ${simhTopDir}/PACKAGES \ + ) + ;; + esac +done diff --git a/cmake/cpack-setup.cmake b/cmake/cpack-setup.cmake new file mode 100644 index 00000000..0dcbeaad --- /dev/null +++ b/cmake/cpack-setup.cmake @@ -0,0 +1,139 @@ +## CPack setup -- sets the CPACK_* variables for the sundry installers +## +## Author: B. Scott Michel (scooter.phd@gmail.com) +## "scooter me fecit" + + +## pre_runtime_exclusions: These are the names of dependency libraries, esp. on Windows +## that should not get installed as runtime or library dependencies. +## +## post_runtime_exclusions: These are regex expressions for the dependency paths to filter out, +## notably Windows system32 DLLs. +set(pre_runtime_exclusions) +list(APPEND pre_runtime_exclusions + ## Windows: + "(ext|api)-ms-.*" + "hvsifiletrust.dll" + "pdmutilities.dll" +) +set(post_runtime_exclusions) +list(APPEND post_runtime_exclusions + ".*system32/.*\\.dll" +) + +## Make runtime_support the default component (vice "Unspecified") +set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME "runtime_support") + +# After we know where everything will install, let CPack figure out +# how to assemble it into a package file. +set(CPACK_PACKAGE_VENDOR "The Open-SIMH project") + +if (SIMH_PACKAGE_SUFFIX) + set(buildSuffix "${SIMH_PACKAGE_SUFFIX}") +else () + set(buildSuffix "") + if (WIN32) + if (CMAKE_SIZEOF_VOID_P EQUAL 8) + list(APPEND buildSuffix "win64") + else () + list(APPEND buildSuffix "win32") + endif () + + list(APPEND buildSuffix "\${CPACK_BUILD_CONFIG}") + ## If using Visual Studio, append the compiler and toolkit: + if (CMAKE_GENERATOR MATCHES "Visual Studio 17 .*") + list(APPEND buildSuffix "vs2022") + elseif (CMAKE_GENERATOR MATCHES "Visual Studio 16 .*") + list(APPEND buildSuffix "vs2019") + elseif (CMAKE_GENERATOR MATCHES "Visual Studio 15 .*") + list(APPEND buildSuffix "vs2017") + elseif (CMAKE_GENERATOR MATCHES "Visual Studio 14 .*") + list(APPEND buildSuffix "vs2015") + endif () + if (CMAKE_GENERATOR_TOOLSET MATCHES "v[0-9][0-9][0-9]_xp") + string(APPEND buildSuffix "xp") + endif () + else () + list(APPEND buildSuffix ${CMAKE_SYSTEM_NAME}) + endif () + + list(JOIN buildSuffix "-" buildSuffix) + + message(STATUS "No SIMH_PACKAGE_SUFFIX supplied, default is ${buildSuffix}.") +endif () + +string(JOIN "-" CPACK_PACKAGE_FILE_NAME + "${CMAKE_PROJECT_NAME}" + "${CMAKE_PROJECT_VERSION}" + "${buildSuffix}" +) + +message(STATUS "CPack output file name: ${CPACK_PACKAGE_FILE_NAME}") +unset(buildSuffix) + +## When applicable (e.g., NSIS Windows), install under the SIMH-x.y directory: +set(CPACK_PACKAGE_INSTALL_DIRECTORY "SIMH-${SIMH_VERSION_MAJOR}.${SIMH_VERSION_MINOR}") +## License file: +set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/LICENSE.txt) + +set(CPACK_PACKAGE_CONTACT "open-simh@nowhere.org") +set(CPACK_PACKAGE_MAINTAINER "open-simh@nowhere.org") + +## Runtime dependencies: +if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.21") + ## Don't install runtime dependencies on Linux platforms. The platform's + ## package management system will take care of this for us. + if (NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") + install(RUNTIME_DEPENDENCY_SET simhRuntime + COMPONENT runtime_support + PRE_EXCLUDE_REGEXES ${pre_runtime_exclusions} + POST_EXCLUDE_REGEXES ${post_runtime_exclusions} + ) + endif () +endif () + + +## Extra properties and variables: +set(CPACK_PROJECT_CONFIG_FILE ${CMAKE_BINARY_DIR}/CPackSimhCustom.cmake) +configure_file(${CMAKE_SOURCE_DIR}/cmake/installer-customizations/CPackSimhCustom.cmake.in + ${CMAKE_BINARY_DIR}/CPackSimhCustom.cmake + @ONLY) + +## CPack generator-specific configs: + +##+ +## NullSoft Installation System (NSIS) Windows installer. Creates an installer EXE. +##- +set(CPACK_NSIS_PACKAGE_NAME ${CPACK_PACKAGE_INSTALL_DIRECTORY}) +set(CPACK_NSIS_INSTALL_ROOT "$LocalAppData\\\\Programs") + +## CPack does this configure_file on its own to genreate the project.nsi file. +## Keeping these lines for history. +# configure_file(${CMAKE_SOURCE_DIR}/cmake/installer-customizations/NSIS.template.in +# ${CMAKE_BINARY_DIR}/NSIS.template +# @ONLY) + +###+ +### WIX MSI Windows installer. +### +### +### Upgrade GUID shouldn't really change. +###- +set(CPACK_WIX_UPGRADE_GUID "ed5dba4c-7c9e-4af8-ac36-37e14c637696") + +##+ +## Debian: +##- + +list(APPEND debian_depends + libsdl2-2.0-0 + libsdl2-ttf-2.0-0 + libpcap0.8 + libvdeplug2 + libedit2 +) + +string(JOIN ", " CPACK_DEBIAN_PACKAGE_DEPENDS ${debian_depends}) + + +include(CPack) diff --git a/cmake/dep-link.cmake b/cmake/dep-link.cmake new file mode 100644 index 00000000..9ebfa305 --- /dev/null +++ b/cmake/dep-link.cmake @@ -0,0 +1,367 @@ +##+ +## dep-link.cmake: Create the dependency interface libraries +##- + +add_library(simh_regexp INTERFACE) +add_library(simh_video INTERFACE) +add_library(simh_network INTERFACE) + +## LIBPCAP is a special case +set(LIBPCAP_PROJECT "libpcap") +set(LIBPCAP_ARCHIVE_NAME "libpcap") +set(LIBPCAP_RELEASE "1.10.1") +set(LIBPCAP_ARCHIVE_TYPE "tar.gz") +set(LIBPCAP_TAR_ARCHIVE "${LIBPCAP_ARCHIVE_NAME}-${LIBPCAP_RELEASE}.${LIBPCAP_ARCHIVE_TYPE}") +set(LIBPCAP_SOURCE_URL "https://github.com/the-tcpdump-group/libpcap/archive/refs/tags/${LIBPCAP_TAR_ARCHIVE}") + +function(fix_interface_libs _targ) +get_target_property(_aliased ${_targ} ALIASED_TARGET) + if(NOT _aliased) + set(fixed_libs) + get_property(orig_libs TARGET ${_targ} PROPERTY INTERFACE_LINK_LIBRARIES) + foreach(each_lib IN LISTS ${_lib}) + get_filename_component(stripped_lib "${each_lib}" DIRECTORY) + if (stripped_lib) + string(STRIP ${each_lib} stripped_lib) + file(TO_CMAKE_PATH "${stripped_lib}" stripped_lib) + if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.19") + file(REAL_PATH "${stripped_lib}" stripped_lib) + endif () + endif () + list(APPEND fixed_libs ${stripped_lib}) + message("** \"${each_lib}\" -> \"${stripped_lib}\"") + endforeach () + set_property(TARGET ${_targ} PROPERTY INTERFACE_LINK_LIBRARIES ${fixed_libs}) + endif () +endfunction () + +## Ubuntu 16.04 -- when we find the SDL2 library, there are trailing spaces. Strip +## spaces from SDL2_LIBRARIES (and potentially others as we find them). +function (fix_libraries _lib) + set(fixed_libs) + foreach(each_lib IN LISTS ${_lib}) + get_filename_component(stripped_lib "${each_lib}" DIRECTORY) + if (stripped_lib) + string(STRIP ${stripped_lib} stripped_lib) + file(TO_CMAKE_PATH "${stripped_lib}" stripped_lib) + if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.19") + file(REAL_PATH "${stripped_lib}" stripped_lib) + endif () + endif () + list(APPEND fixed_libs ${stripped_lib}) + endforeach () + set(${_lib} ${fixed_libs} PARENT_SCOPE) +endfunction () + +set(BUILD_WITH_VIDEO FALSE) +IF (WITH_VIDEO) + ## +10 chaotic neutral hack: The SDL2_ttf CMake configuration include "-lfreetype" and + ## "-lharfbuzz", but, if you're on MacOS, you need to tell the linker where these libraries + ## are located... + set(ldirs) + foreach (lname ${FREETYPE_LIBRARIES} ${FREETYPE_LIBRARY} ${HARFBUZZ_LIBRARIES} ${HARFBUZZ_LIBRARY}) + get_filename_component(dirname "${lname}" DIRECTORY) + if (dirname) + string(STRIP ${dirname} dirname) + file(TO_CMAKE_PATH "${dirname}" dirname) + if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.19") + file(REAL_PATH "${dirname}" dirname) + endif () + list(APPEND ldirs ${dirname}) + endif() + endforeach () + get_property(ilink_dirs TARGET simh_video PROPERTY INTERFACE_LINK_DIRECTORIES) + list(APPEND ilink_dirs ${ldirs}) + set_property(TARGET simh_video PROPERTY INTERFACE_LINK_DIRECTORIES ${ilink_dirs}) + unset(ilink_dirs) + unset(ldirs) + + IF (SDL2_ttf_FOUND) + IF (WIN32 AND TARGET SDL2_ttf::SDL2_ttf-static) + target_link_libraries(simh_video INTERFACE SDL2_ttf::SDL2_ttf-static) + list(APPEND VIDEO_PKG_STATUS "SDL2_ttf static") + ELSEIF (TARGET SDL2_ttf::SDL2_ttf) + target_link_libraries(simh_video INTERFACE SDL2_ttf::SDL2_ttf) + list(APPEND VIDEO_PKG_STATUS "SDL2_ttf dynamic") + ELSEIF (TARGET PkgConfig::SDL2_ttf) + target_link_libraries(simh_video INTERFACE PkgConfig::SDL2_ttf) + list(APPEND VIDEO_PKG_STATUS "pkg-config SDL2_ttf") + ELSEIF (DEFINED SDL_ttf_LIBRARIES AND DEFINED SDL_ttf_INCLUDE_DIRS) + target_link_libraries(simh_video INTERFACE ${SDL_ttf_LIBRARIES}) + target_include_directories(simh_video INTERFACE ${SDL_ttf_INCLUDE_DIRS}) + list(APPEND VIDEO_PKG_STATUS "detected SDL2_ttf") + ELSE () + message(FATAL_ERROR "SDL2_ttf_FOUND set but no SDL2_ttf::SDL2_ttf import library or SDL_ttf_LIBRARIES/SDL_ttf_INCLUDE_DIRS? ") + ENDIF () + ENDIF (SDL2_ttf_FOUND) + + IF (SDL2_FOUND) + target_compile_definitions(simh_video INTERFACE USE_SIM_VIDEO HAVE_LIBSDL) + ## + ## Hopefully this hack can go away. Had to move the target_compile_definitions + ## over to add_simulator.cmake to accomodate the BESM6 SDL irregularity. + ## + ## (keep) if (CMAKE_HOST_APPLE) + ## (keep) ## NOTE: This shouldn't be just an Apple platform quirk; SDL_main should + ## (keep) ## be used by all platforms. + ## (keep) target_compile_definitions(simh_video INTERFACE SDL_MAIN_AVAILABLE) + ## (keep) endif () + + ## Link to SDL2main if defined for this platform. + target_link_libraries(simh_video INTERFACE $) + + IF (WIN32 AND TARGET SDL2::SDL2-static AND TARGET SDL2_ttf::SDL2_ttf-static) + ## Prefer the static version on Windows, but only if SDL2_ttf is also static. + target_link_libraries(simh_video INTERFACE SDL2::SDL2-static) + list(APPEND VIDEO_PKG_STATUS "SDL2 static") + ELSEIF (TARGET SDL2::SDL2) + fix_interface_libs(SDL2::SDL2) + target_link_libraries(simh_video INTERFACE SDL2::SDL2) + list(APPEND VIDEO_PKG_STATUS "SDL2 dynamic") + ELSEIF (TARGET PkgConfig::SDL2) + fix_interface_libs(PkgConfig::SDL2) + target_link_libraries(simh_video INTERFACE PkgConfig::SDL2) + list(APPEND VIDEO_PKG_STATUS "pkg-config SDL2") + ELSEIF (DEFINED SDL2_LIBRARIES AND DEFINED SDL2_INCLUDE_DIRS) + fix_libraries(SDL2_LIBRARIES) + target_link_libraries(simh_video INTERFACE ${SDL2_LIBRARIES}) + target_include_directories(simh_video INTERFACE ${SDL2_INCLUDE_DIRS}) + list(APPEND VIDEO_PKG_STATUS "detected SDL2") + ELSE () + message(FATAL_ERROR "SDL2_FOUND set but no SDL2::SDL2 import library or SDL2_LIBRARIES/SDL2_INCLUDE_DIRS?") + ENDIF () + ENDIF (SDL2_FOUND) + + IF (NOT USING_VCPKG AND FREETYPE_FOUND) + if (TARGET Freetype::Freetype) + target_link_libraries(simh_video INTERFACE freetype) + list(APPEND VIDEO_PKG_STATUS "Freetype::Freetype") + ELSEIF (TARGET PkgConfig::Freetype) + target_link_libraries(simh_video INTERFACE PkgConfig::Freetype) + list(APPEND VIDEO_PKG_STATUS "pkg-config Freetype") + ELSE () + target_link_libraries(simh_video INTERFACE ${FREETYPE_LIBRARIES}) + target_include_directories(simh_video INTERFACE ${FREETYPE_INCLUDE_DIRS}) + list(APPEND VIDEO_PKG_STATUS "detected Freetype") + ENDIF () + ENDIF () + + IF (PNG_FOUND) + target_compile_definitions(simh_video INTERFACE HAVE_LIBPNG) + + if (TARGET PNG::PNG) + target_link_libraries(simh_video INTERFACE PNG::PNG) + list(APPEND VIDEO_PKG_STATUS "interface PNG") + elseif (TARGET PkgConfig::PNG) + target_link_libraries(simh_video INTERFACE PkgConfig::PNG) + list(APPEND VIDEO_PKG_STATUS "pkg-config PNG") + else () + target_include_directories(simh_video INTERFACE ${PNG_INCLUDE_DIRS}) + target_link_libraries(simh_video INTERFACE ${PNG_LIBRARIES}) + list(APPEND VIDEO_PKG_STATUS "detected PNG") + endif () + ENDIF (PNG_FOUND) + + set(BUILD_WITH_VIDEO TRUE) +ELSE () + set(VIDEO_PKG_STATUS "video support disabled") +ENDIF() + +if (WITH_REGEX) + ## TEMP: Use PCRE until patches for PCRE2 are avaiable. + ## + ## 1. Prefer PCRE2 over PCRE (unless PREFER_PCRE is set) + ## 2. Prefer interface libraries before using detected find_package + ## variables. + IF (TARGET PkgConfig::PCRE) + target_link_libraries(simh_regexp INTERFACE PkgConfig::PCRE) + if (PREFER_PCRE) + target_compile_definitions(simh_regexp INTERFACE HAVE_PCRE_H) + set(PCRE_PKG_STATUS "pkg-config pcre") + else () + target_compile_definitions(simh_regexp INTERFACE HAVE_PCRE2_H) + if (WIN32) + ## Use static linkage (vice DLL) on Windows: + target_compile_definitions(simh_regexp INTERFACE PCRE2_STATIC) + endif () + set(PCRE_PKG_STATUS "pkg-config pcre2") + endif () + ELSEIF (TARGET unofficial::pcre::pcre) + ## vcpkg: + target_link_libraries(simh_regexp INTERFACE unofficial::pcre::pcre) + target_compile_definitions(simh_regexp INTERFACE HAVE_PCRE_H) + target_compile_definitions(simh_regexp INTERFACE PCRE_STATIC) + set(PCRE_PKG_STATUS "vcpkg pcre") + ELSEIF (NOT PREFER_PCRE AND PCRE2_FOUND) + target_compile_definitions(simh_regexp INTERFACE HAVE_PCRE2_H) + target_include_directories(simh_regexp INTERFACE ${PCRE2_INCLUDE_DIRS}) + if (NOT WIN32) + target_link_libraries(simh_regexp INTERFACE ${PCRE2_LIBRARY}) + else () + ## Use static linkage (vice DLL) on Windows: + target_compile_definitions(simh_regexp INTERFACE PCRE2_STATIC) + endif () + + set(PCRE_PKG_STATUS "detected pcre2") + ELSEIF (PCRE_FOUND) + target_compile_definitions(simh_regexp INTERFACE HAVE_PCRE_H) + target_include_directories(simh_regexp INTERFACE ${PCRE_INCLUDE_DIRS}) + target_link_libraries(simh_regexp INTERFACE ${PCRE_LIBRARY}) + if (WIN32) + target_compile_definitions(simh_regexp INTERFACE PCRE_STATIC) + endif () + set(PCRE_PKG_STATUS "detected pcre") + endif () +endif () + +if ((WITH_REGEX OR WITH_VIDEO) AND ZLIB_FOUND) + target_compile_definitions(simh_regexp INTERFACE HAVE_ZLIB) + target_compile_definitions(simh_video INTERFACE HAVE_ZLIB) + if (TARGET ZLIB::ZLIB) + target_link_libraries(simh_regexp INTERFACE ZLIB::ZLIB) + target_link_libraries(simh_video INTERFACE ZLIB::ZLIB) + set(ZLIB_PKG_STATUS "interface ZLIB") + elseif (TARGET PkgConfig::ZLIB) + target_link_libraries(simh_regexp INTERFACE PkgConfig::ZLIB) + target_link_libraries(simh_video INTERFACE PkgConfig::ZLIB) + set(ZLIB_PKG_STATUS "pkg-config ZLIB") + else () + target_include_directories(simh_regexp INTERFACE ${ZLIB_INCLUDE_DIRS}) + target_link_libraries(simh_regexp INTERFACE ${ZLIB_LIBRARIES}) + target_include_directories(simh_video INTERFACE ${ZLIB_INCLUDE_DIRS}) + target_link_libraries(simh_video INTERFACE ${ZLIB_LIBRARIES}) + set(ZLIB_PKG_STATUS "detected ZLIB") + endif () +endif () + + +if (WITH_NETWORK) + set(network_runtime USE_SHARED) + ## pcap is special: Headers only and dynamically loaded. + if (WITH_PCAP) + find_package(PCAP) + + if (NOT PCAP_FOUND) + list(APPEND NETWORK_PKG_STATUS "PCAP dynamic (unpacked)") + + message(STATUS "Downloading ${LIBPCAP_SOURCE_URL}") + message(STATUS "Destination ${CMAKE_BINARY_DIR}/libpcap") + execute_process( + COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/libpcap" + RESULT_VARIABLE LIBPCAP_MKDIR + ) + if (NOT (${LIBPCAP_MKDIR} EQUAL 0)) + message(FATAL_ERROR "Could not create ${CMAKE_CMAKE_BINARY_DIR}/libpcap") + endif (NOT (${LIBPCAP_MKDIR} EQUAL 0)) + + file(DOWNLOAD "${LIBPCAP_SOURCE_URL}" "${CMAKE_BINARY_DIR}/libpcap/libpcap.${LIBPCAP_ARCHIVE_TYPE}" + STATUS LIBPCAP_DOWNLOAD + ) + list(GET LIBPCAP_DOWNLOAD 0 LIBPCAP_DL_STATUS) + if (NOT (${LIBPCAP_DL_STATUS} EQUAL 0)) + list(GET LIBPCAP_DOWNLOAD 1 LIBPCAP_DL_ERROR) + message(FATAL_ERROR "Download failed: ${LIBPCAP_DL_ERROR}") + endif (NOT (${LIBPCAP_DL_STATUS} EQUAL 0)) + + message(STATUS "Extracting headers ${LIBPCAP_SOURCE_URL}") + execute_process( + COMMAND ${CMAKE_COMMAND} -E tar xvf "${CMAKE_BINARY_DIR}/libpcap/libpcap.${LIBPCAP_ARCHIVE_TYPE}" + "${LIBPCAP_PROJECT}-${LIBPCAP_ARCHIVE_NAME}-${LIBPCAP_RELEASE}/pcap.h" + "${LIBPCAP_PROJECT}-${LIBPCAP_ARCHIVE_NAME}-${LIBPCAP_RELEASE}/pcap/*.h" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/libpcap" + RESULT_VARIABLE LIBPCAP_EXTRACT + ) + if (NOT (${LIBPCAP_EXTRACT} EQUAL 0)) + message(FATAL_ERROR "Extract failed.") + endif (NOT (${LIBPCAP_EXTRACT} EQUAL 0)) + + message(STATUS "Copying headers from ${CMAKE_BINARY_DIR}/libpcap/${LIBPCAP_PROJECT}-${LIBPCAP_ARCHIVE_NAME}-${LIBPCAP_RELEASE}/pcap") + message(STATUS "Destination ${CMAKE_BINARY_DIR}/include/pcap") + execute_process( + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${LIBPCAP_PROJECT}-${LIBPCAP_ARCHIVE_NAME}-${LIBPCAP_RELEASE}/" + "${CMAKE_BINARY_DIR}/include/" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/libpcap" + RESULT_VARIABLE LIBPCAP_COPYDIR + ) + if (NOT (${LIBPCAP_COPYDIR} EQUAL 0)) + message(FATAL_ERROR "Copy failed.") + endif (NOT (${LIBPCAP_COPYDIR} EQUAL 0)) + + ## And try finding it again... + find_package(PCAP) + else () + list (APPEND NETWORK_PKG_STATUS "PCAP dynamic") + endif () + + + if (PCAP_FOUND) + set(network_runtime USE_SHARED) + foreach(hdr "${PCAP_INCLUDE_DIRS}") + file(STRINGS ${hdr}/pcap/pcap.h hdrcontent REGEX "pcap_compile *\\(.*const") + # message("hdrcontent: ${hdrcontent}") + list(LENGTH hdrcontent have_bpf_const) + if (${have_bpf_const} GREATER 0) + message(STATUS "pcap_compile requires BPF_CONST_STRING") + list(APPEND network_runtime BPF_CONST_STRING) + endif() + endforeach() + + target_include_directories(simh_network INTERFACE "${PCAP_INCLUDE_DIRS}") + target_compile_definitions(simh_network INTERFACE HAVE_PCAP_NETWORK) + endif () + endif () + + ## TAP/TUN devices + if (WITH_TAP) + target_compile_definitions(simh_network INTERFACE ${NETWORK_TUN_DEFS}) + endif (WITH_TAP) + + if (WITH_VDE AND VDE_FOUND) + if (TARGET PkgConfig::VDE) + target_compile_definitions(simh_network INTERFACE $) + target_include_directories(simh_network INTERFACE $) + target_link_libraries(simh_network INTERFACE PkgConfig::VDE) + list(APPEND NETWORK_PKG_STATUS "pkg-config VDE") + else () + target_include_directories(simh_network INTERFACE "${VDEPLUG_INCLUDE_DIRS}") + target_link_libraries(simh_network INTERFACE "${VDEPLUG_LIBRARY}") + list(APPEND NETWORK_PKG_STATUS "detected VDE") + endif () + + target_compile_definitions(simh_network INTERFACE HAVE_VDE_NETWORK) + endif () + + if (WITH_TAP) + if (HAVE_TAP_NETWORK) + target_compile_definitions(simh_network INTERFACE HAVE_TAP_NETWORK) + + if (HAVE_BSDTUNTAP) + target_compile_definitions(simh_network INTERFACE HAVE_BSDTUNTAP) + list(APPEND NETWORK_PKG_STATUS "BSD TUN/TAP") + else (HAVE_BSDTUNTAP) + list(APPEND NETWORK_PKG_STATUS "TAP") + endif (HAVE_BSDTUNTAP) + + endif (HAVE_TAP_NETWORK) + endif (WITH_TAP) + + if (WITH_SLIRP) + target_link_libraries(simh_network INTERFACE slirp) + list(APPEND NETWORK_PKG_STATUS "NAT(SLiRP)") + endif (WITH_SLIRP) + + ## Finally, set the network runtime + if (NOT network_runtime) + ## Default to USE_SHARED... USE_NETWORK is deprecated. + set(network_runtime USE_SHARED) + endif (NOT network_runtime) + + target_compile_definitions(simh_network INTERFACE ${network_runtime}) + + set(BUILD_WITH_NETWORK TRUE) +else (WITH_NETWORK) + set(NETWORK_STATUS "networking disabled") + set(NETWORK_PKG_STATUS "network disabled") + set(BUILD_WITH_NETWORK FALSE) +endif (WITH_NETWORK) diff --git a/cmake/dep-locate.cmake b/cmake/dep-locate.cmake new file mode 100644 index 00000000..052cbd8b --- /dev/null +++ b/cmake/dep-locate.cmake @@ -0,0 +1,361 @@ +##=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~= +## dep-locate.cmake +## +## Consolidated list of runtime dependencies for simh, probed/found via +## CMake's find_package() and pkg_check_modules() when 'pkgconfig' is +## available. +##=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~= + +##-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ +## Find packages: +##-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ + +if (WITH_REGEX) + if (PREFER_PCRE) + if (USING_VCPKG) + find_package(unofficial-pcre CONFIG) + else () + ## LEGACY strategy: + find_package(PCRE) + endif () + else () + find_package(PCRE2) + endif () +endif () + +if (WITH_REGEX OR WITH_VIDEO) + set(ZLIB_USE_STATIC_LIBS ON) + find_package(ZLIB) +endif () + +if (WITH_VIDEO) + if (NOT USING_VCPKG) + ## LEGACY strategy: + find_package(PNG) + find_package(Freetype) + find_package(SDL2 NAMES sdl2 SDL2) + find_package(SDL2_ttf NAMES sdl2_ttf SDL2_ttf) + else () + ## vcpkg strategy: + find_package(PNG REQUIRED) + find_package(SDL2 CONFIG) + find_package(SDL2_ttf CONFIG) + endif () +endif () + +if (WITH_NETWORK) + if (WITH_VDE) + find_package(VDE) + endif () + + ## pcap is special: Headers only and dynamically loaded. + if (WITH_PCAP) + find_package(PCAP) + endif (WITH_PCAP) +endif (WITH_NETWORK) + +if (NOT WIN32 OR MINGW) + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + if (WITH_REGEX) + if (PREFER_PCRE AND NOT PCRE_FOUND) + pkg_check_modules(PCRE IMPORTED_TARGET libpcre) + elseif (NOT PREFER_PCRE AND NOT PCRE2_FOUND) + pkg_check_modules(PCRE IMPORTED_TARGET libpcre2-8) + endif () + endif (WITH_REGEX) + + if (WITH_REGEX OR WITH_VIDEO) + if (NOT ZLIB_FOUND) + pkg_check_modules(ZLIB IMPORTED_TARGET zlib) + endif () + endif () + + if (WITH_VIDEO) + if (NOT PNG_FOUND) + pkg_check_modules(PNG IMPORTED_TARGET libpng16) + endif () + if (NOT SDL2_FOUND) + pkg_check_modules(SDL2 IMPORTED_TARGET sdl2) + if (NOT SDL2_FOUND) + pkg_check_modules(SDL2 IMPORTED_TARGET SDL2) + endif () + endif () + + if (NOT SDL2_ttf_FOUND) + pkg_check_modules(SDL2_ttf IMPORTED_TARGET SDL2_ttf) + if (NOT SDL2_ttf_FOUND) + pkg_check_modules(SDL2_ttf IMPORTED_TARGET sdl2_ttf) + endif () + endif () + endif (WITH_VIDEO) + + if (WITH_NETWORK) + if (WITH_VDE AND NOT VDE_FOUND) + pkg_check_modules(VDE IMPORTED_TARGET vdeplug) + endif () + endif (WITH_NETWORK) + endif () +endif () + +##-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ +## Add rules for the superbuild if dependencies need to be built: +##-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ + +if (NO_DEP_BUILD) + ## Not going to build dependencies... + return () +endif () + +include (ExternalProject) + +# Source URLs (to make it easy to update versions): +set(ZLIB_SOURCE_URL "https://github.com/madler/zlib/archive/v1.2.13.zip") +set(PCRE2_SOURCE_URL "https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.40/pcre2-10.40.zip") +## PCRE needs multiple URLs to chase a working SF mirror: +list(APPEND PCRE_SOURCE_URL + "https://sourceforge.net/projects/pcre/files/pcre/8.45/pcre-8.45.zip/download?use_mirror=cytranet" + "https://sourceforge.net/projects/pcre/files/pcre/8.45/pcre-8.45.zip/download?use_mirror=phoenixnap" + "https://sourceforge.net/projects/pcre/files/pcre/8.45/pcre-8.45.zip/download?use_mirror=versaweb" + "https://sourceforge.net/projects/pcre/files/pcre/8.45/pcre-8.45.zip/download?use_mirror=netactuate" + "https://sourceforge.net/projects/pcre/files/pcre/8.45/pcre-8.45.zip/download?use_mirror=cfhcable" + "https://sourceforge.net/projects/pcre/files/pcre/8.45/pcre-8.45.zip/download?use_mirror=freefr" + "https://sourceforge.net/projects/pcre/files/pcre/8.45/pcre-8.45.zip/download?use_mirror=master" +) +set(PNG_SOURCE_URL "https://github.com/glennrp/libpng/archive/refs/tags/v1.6.40.tar.gz") +## Freetype also needs multiple URLs to chase a working mirror: +list(APPEND FREETYPE_SOURCE_URL + "https://github.com/freetype/freetype/archive/refs/tags/VER-2-13-0.zip" + "https://sourceforge.net/projects/freetype/files/freetype2/2.13.1/ft2131.zip/download?use_mirror=cytranet" + "https://sourceforge.net/projects/freetype/files/freetype2/2.13.1/ft2131.zip/download?use_mirror=phoenixnap" + "https://sourceforge.net/projects/freetype/files/freetype2/2.13.1/ft2131.zip/download?use_mirror=versaweb" + "https://sourceforge.net/projects/freetype/files/freetype2/2.13.1/ft2131.zip/download?use_mirror=netactuate" + "https://sourceforge.net/projects/freetype/files/freetype2/2.13.1/ft2131.zip/download?use_mirror=cfhcable" + "https://sourceforge.net/projects/freetype/files/freetype2/2.13.1/ft2131.zip/download?use_mirror=freefr" + "https://sourceforge.net/projects/freetype/files/freetype2/2.13.1/ft2131.zip/download?use_mirror=master" + "https://download.savannah.gnu.org/releases/freetype/freetype-2.13.1.tar.xz" + "https://gitlab.freedesktop.org/freetype/freetype/-/archive/VER-2-13-0/freetype-VER-2-13-0.zip" +) +set(SDL2_SOURCE_URL "https://github.com/libsdl-org/SDL/archive/refs/tags/release-2.28.1.zip") +set(SDL2_TTF_SOURCE_URL "https://github.com/libsdl-org/SDL_ttf/archive/refs/tags/release-2.20.2.zip") + +## Need to build ZLIB for both PCRE and libpng16: +if ((WITH_REGEX OR WITH_VIDEO) AND NOT ZLIB_FOUND) + ExternalProject_Add(zlib-dep + URL ${ZLIB_SOURCE_URL} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + ## These patches come from vcpkg so that only the static libraries are built and + ## installed. If the patches don't apply cleanly (and there's a build error), that + ## means a version number got bumped and need to see what patches, if any, are + ## still applicable. + PATCH_COMMAND + git -c core.longpaths=true -c core.autocrlf=false --work-tree=. --git-dir=.git + apply + "${SIMH_DEP_PATCHES}/zlib/0001-Prevent-invalid-inclusions-when-HAVE_-is-set-to-0.patch" + "${SIMH_DEP_PATCHES}/zlib/0002-skip-building-examples.patch" + "${SIMH_DEP_PATCHES}/zlib/0003-build-static-or-shared-not-both.patch" + "${SIMH_DEP_PATCHES}/zlib/0004-android-and-mingw-fixes.patch" + --ignore-whitespace --whitespace=nowarn --verbose + ) + + BuildDepMatrix(zlib-dep zlib CMAKE_ARGS -DBUILD_SHARED_LIBS:Bool=${BUILD_SHARED_DEPS}) + + list(APPEND SIMH_BUILD_DEPS zlib) + list(APPEND SIMH_DEP_TARGETS zlib-dep) + message(STATUS "Building ZLIB from ${ZLIB_SOURCE_URL}.") + set(ZLIB_PKG_STATUS "ZLIB source build") +endif () + +IF (WITH_REGEX AND NOT (PCRE_FOUND OR PCRE2_FOUND OR TARGET unofficial::pcre::pcre)) + set(PCRE_DEPS) + IF (TARGET zlib-dep) + list(APPEND PCRE_DEPS zlib-dep) + ENDIF (TARGET zlib-dep) + + set(PCRE_CMAKE_ARGS -DBUILD_SHARED_LIBS:Bool=${BUILD_SHARED_DEPS}) + if (NOT PREFER_PCRE) + set(PCRE_URL ${PCRE2_SOURCE_URL}) + list(APPEND PCRE_CMAKE_ARGS + -DPCRE2_BUILD_PCREGREP:Bool=Off + -DPCRE2_SUPPORT_LIBEDIT:Bool=Off + -DPCRE2_SUPPORT_LIBREADLINE:Bool=Off + ) + + # IF(MSVC) + # list(APPEND PCRE_CMAKE_ARGS -DINSTALL_MSVC_PDB=On) + # ENDIF(MSVC) + + message(STATUS "Building PCRE2 from ${PCRE_URL}") + set(PCRE_PKG_STATUS "pcre2 source build") + ELSE () + set(PCRE_URL ${PCRE_SOURCE_URL}) + list(APPEND PCRE_CMAKE_ARGS + -DPCRE_BUILD_PCREGREP:Bool=Off + -DPCRE_SUPPORT_LIBEDIT:Bool=Off + -DPCRE_SUPPORT_LIBREADLINE:Bool=Off + ) + if (WIN32) + list(APPEND PCRE_CMAKE_ARGS + -DBUILD_SHARED_LIBS:Bool=Off + -DPCRE_STATIC_RUNTIME:Bool=On + ) + endif () + + message(STATUS "Building PCRE from ${PCRE_URL}") + set(PCRE_PKG_STATUS "pcre source build") + ENDIF () + + ExternalProject_Add(pcre-ext + URL + ${PCRE_URL} + DEPENDS + ${PCRE_DEPS} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + ) + + BuildDepMatrix(pcre-ext pcre CMAKE_ARGS ${PCRE_CMAKE_ARGS}) + + list(APPEND SIMH_BUILD_DEPS pcre) + list(APPEND SIMH_DEP_TARGETS pcre-ext) +ELSE () + set(PCRE_PKG_STATUS "regular expressions disabled") +ENDIF () + +set(BUILD_WITH_VIDEO FALSE) +IF (WITH_VIDEO) + IF (NOT PNG_FOUND) + set(PNG_DEPS) + if (NOT ZLIB_FOUND) + list(APPEND PNG_DEPS zlib-dep) + endif (NOT ZLIB_FOUND) + + ExternalProject_Add(png-dep + URL + ${PNG_SOURCE_URL} + DEPENDS + ${PNG_DEPS} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + ) + + ## Work around the GCC 8.1.0 SEH index regression. + set(PNG_CMAKE_BUILD_TYPE_RELEASE "Release") + if (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND + CMAKE_C_COMPILER_VERSION VERSION_EQUAL "8.1" AND + NOT CMAKE_BUILD_VERSION) + message(STATUS "PNG: Build using MinSizeRel CMAKE_BUILD_TYPE with GCC 8.1") + set(PNG_CMAKE_BUILD_TYPE_RELEASE "MinSizeRel") + endif() + + BuildDepMatrix(png-dep libpng + CMAKE_ARGS + -DPNG_SHARED:Bool=${BUILD_SHARED_DEPS} + -DPNG_STATUS:Bool=On + -DPNG_EXECUTABLES:Bool=Off + -DPNG_TESTS:Bool=Off + RELEASE_BUILD ${PNG_CMAKE_BUILD_TYPE_RELEASE} + ) + + list(APPEND SIMH_BUILD_DEPS "png") + list(APPEND SIMH_DEP_TARGETS "png-dep") + message(STATUS "Building PNG from ${PNG_SOURCE_URL}") + list(APPEND VIDEO_PKG_STATUS "PNG source build") + ENDIF (NOT PNG_FOUND) + + IF (NOT SDL2_FOUND) + ExternalProject_Add(sdl2-dep + URL ${SDL2_SOURCE_URL} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + ) + + BuildDepMatrix(sdl2-dep SDL2 CMAKE_ARGS "-DBUILD_SHARED_LIBS:Bool=${BUILD_SHARED_DEPS}") + + list(APPEND SIMH_BUILD_DEPS "SDL2") + list(APPEND SIMH_DEP_TARGETS "sdl2-dep") + message(STATUS "Building SDL2 from ${SDL2_SOURCE_URL}.") + list(APPEND VIDEO_PKG_STATUS "SDL2 source build") + ENDIF (NOT SDL2_FOUND) + + IF (NOT FREETYPE_FOUND) + set(FREETYPE_DEPS) + if (TARGET zlib-dep) + list(APPEND FREETYPE_DEPS zlib-dep) + endif () + if (TARGET png-dep) + list(APPEND FREETYPE_DEPS png-dep) + endif () + + ExternalProject_Add(freetype-dep + URL + ${FREETYPE_SOURCE_URL} + DEPENDS + ${FREETYPE_DEPS} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + ) + + BuildDepMatrix(freetype-dep Freetype + CMAKE_ARGS + "-DBUILD_SHARED_LIBS:Bool=${BUILD_SHARED_DEPS}" + "-DFT_DISABLE_BZIP2:Bool=TRUE" + "-DFT_DISABLE_HARFBUZZ:Bool=TRUE" + "-DFT_DISABLE_BROTLI:Bool=TRUE" + ) + + list(APPEND SIMH_BUILD_DEPS "Freetype") + list(APPEND SIMH_DEP_TARGETS freetype-dep) + message(STATUS "Building Freetype from ${FREETYPE_SOURCE_URL}.") + ENDIF () + + IF (NOT SDL2_ttf_FOUND) + set(SDL2_ttf_DEPS) + if (TARGET sdl2-dep) + list(APPEND SDL2_ttf_DEPS sdl2-dep) + endif (TARGET sdl2-dep) + if (TARGET freetype-dep) + list(APPEND SDL2_ttf_DEPS freetype-dep) + endif () + + ExternalProject_Add(sdl2-ttf-dep + URL + ${SDL2_TTF_SOURCE_URL} + DEPENDS + ${SDL2_ttf_DEPS} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + PATCH_COMMAND + git -c core.longpaths=true -c core.autocrlf=false --work-tree=. --git-dir=.git + apply + "${SIMH_DEP_PATCHES}/SDL_ttf/fix-pkgconfig.patch" + --ignore-whitespace --whitespace=nowarn --verbose + ) + + set(sdl2_ttf_cmake_args) + list(APPEND sdl2_ttf_cmake_args + "-DBUILD_SHARED_LIBS:Bool=${BUILD_SHARED_DEPS}" + "-DSDL2TTF_SAMPLES:Bool=Off" + "-DSDL2TTF_VENDORED:Bool=Off" + "-DSDL2TTF_HARFBUZZ:Bool=Off" + ) + + BuildDepMatrix(sdl2-ttf-dep SDL2_ttf CMAKE_ARGS ${sdl2_ttf_cmake_args}) + + list(APPEND SIMH_BUILD_DEPS "SDL2_ttf") + list(APPEND SIMH_DEP_TARGETS "sdl2-ttf-dep") + message(STATUS "Building SDL2_ttf from https://www.libsdl.org/release/SDL2_ttf-2.0.15.zip.") + list(APPEND VIDEO_PKG_STATUS "SDL2_ttf source build") + ENDIF (NOT SDL2_ttf_FOUND) + + set(BUILD_WITH_VIDEO TRUE) +ELSE () + set(VIDEO_PKG_STATUS "video support disabled") +ENDIF(WITH_VIDEO) diff --git a/cmake/diff-master.ps1 b/cmake/diff-master.ps1 new file mode 100644 index 00000000..0e57a1b9 --- /dev/null +++ b/cmake/diff-master.ps1 @@ -0,0 +1,14 @@ +## Make looking at divergence from simh/master easier... + +$excludes = @( + "/*CMakeLists.txt", + "/.gitignore", + "/Visual Studio Projects/", + "/build_*.bat", + "/cmake/", + "/PDP8/tests/diags/*.pal", + "/PDP8/tests/diags/*.txt", + "/appveyor.yml" +) | % { "`":!" + $_ + "`"" } + +git diff --ignore-space-at-eol simh/master HEAD -- ${excludes} diff --git a/cmake/file-link-copy.cmake b/cmake/file-link-copy.cmake new file mode 100644 index 00000000..f686499c --- /dev/null +++ b/cmake/file-link-copy.cmake @@ -0,0 +1,50 @@ +## File link or copy +## +## Written initially for the VAX to link vax to microvax3900, this +## evolved into a more general-purpose utility. + +if (NOT SRCFILE) + message(FATAL_ERROR "SRCFILE not defined") +endif () +if (NOT DSTFILE) + mesasge(FATAL_ERROR "DSTFILE not defined") +endif () +if (NOT WORKING_DIR) + message(FATAL_ERROR "WORKING_DIR not defined") +endif () + +if (NOT EXISTS ${WORKING_DIR}) + message(FATAL_ERROR "Working directory does not exist: ${WORKING_DIR}") +endif () + +file(TO_NATIVE_PATH "${WORKING_DIR}/${SRCFILE}" _source) +file(TO_NATIVE_PATH "${WORKING_DIR}/${DSTFILE}" _dest) + +if (EXISTS ${_dest}) + message("Removing destination ${_dest}") + file(REMOVE ${_dest}) + if (EXISTS ${_dest}) + message(FATAL_ERROR "Could not remove ${_dest}") + endif () +endif () + +execute_process( + COMMAND + ${CMAKE_COMMAND} -E create_symlink ${SRCFILE} ${DSTFILE} + WORKING_DIRECTORY + ${WORKING_DIR} + RESULT_VARIABLE + _file_symlink + ERROR_QUIET +) + +if (NOT _file_symlink EQUAL 0) + file(CREATE_LINK ${_source} ${_dest} COPY_ON_ERROR RESULT _result) + if (NOT _result EQUAL 0) + message(FATAL_ERROR "Could not link or copy ${_source} to ${_dest}") + else () + message(":::: Hard link/copy ${_source} -> ${_dest}") + endif () +else () + message(":::: Symlink ${SRCFILE} -> ${DSTFILE} in ${WORKING_DIR}") +endif() diff --git a/cmake/fpintrin.cmake b/cmake/fpintrin.cmake new file mode 100644 index 00000000..8ec2cbdc --- /dev/null +++ b/cmake/fpintrin.cmake @@ -0,0 +1,79 @@ +## Check for various GNU-specific floating point math flags +## +## Not entirely sure that they will make a huge difference to code +## generation in the simulators. + +set(EXTRA_TARGET_CFLAGS) + +set(CMAKE_REQUIRED_FLAGS "-msse") +check_c_source_compiles(" + #ifdef __MINGW32__ + #include <_mingw.h> + #ifdef __MINGW64_VERSION_MAJOR + #include + #else + #include + #endif + #else + #include + #endif + #ifndef __SSE__ + #error Assembler CPP flag not enabled + #endif + int main(int argc, char **argv) { }" HAVE_SSE) +if(HAVE_SSE) + list(APPEND EXTRA_TARGET_CFLAGS "-msse") +endif() +set(CMAKE_REQUIRED_FLAGS ${ORIG_CMAKE_REQUIRED_FLAGS}) + +set(CMAKE_REQUIRED_FLAGS "-msse2") +check_c_source_compiles(" + #ifdef __MINGW32__ + #include <_mingw.h> + #ifdef __MINGW64_VERSION_MAJOR + #include + #else + #include + #endif + #else + #include + #endif + #ifndef __SSE2__ + #error Assembler CPP flag not enabled + #endif + int main(int argc, char **argv) { }" HAVE_SSE2) +if(HAVE_SSE2) + list(APPEND EXTRA_TARGET_CFLAGS "-msse2") +endif() +set(CMAKE_REQUIRED_FLAGS ${ORIG_CMAKE_REQUIRED_FLAGS}) + +set(CMAKE_REQUIRED_FLAGS "-msse3") +check_c_source_compiles(" + #ifdef __MINGW32__ + #include <_mingw.h> + #ifdef __MINGW64_VERSION_MAJOR + #include + #else + #include + #endif + #else + #include + #endif + #ifndef __SSE3__ + #error Assembler CPP flag not enabled + #endif + int main(int argc, char **argv) { }" HAVE_SSE3) +if(HAVE_SSE3) + list(APPEND EXTRA_TARGET_CFLAGS "-msse3") +endif() +set(CMAKE_REQUIRED_FLAGS ${ORIG_CMAKE_REQUIRED_FLAGS}) + +if(SSE OR SSE2 OR SSE3) + if(USE_GCC) + check_c_compiler_flag(-mfpmath=387 HAVE_FP_387) + if(HAVE_FP_387) + list(APPEND EXTRA_TARGET_CFLAGS "-mfpmath=387") + endif() + endif() + set(HAVE_SSEMATH TRUE) +endif() diff --git a/cmake/generate.py b/cmake/generate.py new file mode 100644 index 00000000..1b068153 --- /dev/null +++ b/cmake/generate.py @@ -0,0 +1,200 @@ +## generate.py +## +## Generate the simulator CMakeLists.txt from the top-level makefile. +## +## This is the top-level driver: process options, search for the +## makefile, parse the makefile and walk its dependencies, and, +## finally, output the CMakeLists.txt(s) and simh-simulators.cmake. +## +## Author: B. Scott Michel +## ("scooter me fecit") + +import sys +import os.path +import argparse +import re + +GEN_SCRIPT_DIR = os.path.dirname(__file__) +GEN_SCRIPT_NAME = os.path.basename(__file__) + +import pprint + +import simgen.cmake_container as SCC +import simgen.parse_makefile as SPM +import simgen.packaging as SPKG + + +def process_makefile(makefile_dir, debug=0): + the_makefile = os.path.join(makefile_dir, "makefile") + print('{0}: Processing {1}'.format(GEN_SCRIPT_NAME, the_makefile)) + + (defs, rules, actions) = SPM.parse_makefile(the_makefile) + if debug >= 4: + pprint.pp(defs) + + all_rule = rules.get('all') or rules.get('ALL') + if all_rule is None: + print('{0}: "all" rule not found. Cannot proceed.'.format(GEN_SCRIPT_NAME)) + + simulators = SCC.CMakeBuildSystem() + for all_targ in SPM.shallow_expand_vars(all_rule, defs).split(): + print("{0}: all target {1}".format(GEN_SCRIPT_NAME, all_targ)) + walk_target_deps(all_targ, defs, rules, actions, simulators, debug=debug) + + experimental_rule = rules.get('experimental') + if experimental_rule is not None: + for experimental_targ in SPM.shallow_expand_vars(experimental_rule, defs).split(): + print("{0}: exp target {1}".format(GEN_SCRIPT_NAME, experimental_targ)) + walk_target_deps(experimental_targ, defs, rules, actions, simulators, debug=debug) + + simulators.collect_vars(defs, debug=debug) + return simulators + + +## Makefile target dependencies to filter out. +_ignored_deps = [ + '${SIM}', + '${BUILD_ROMS}' +] + +## Simulator compile/link action pattern +_compile_act_rx = re.compile(r"\$[({]CC[)}]\s*(.*)") +_test_name_rx = re.compile(r"\$@\s*\$\(call\s+find_test,\s*(.*),(.*)\)\s+\$") + +def walk_target_deps(target, defs, rules, actions, simulators, depth='', debug=0): + """ Recursively walk a target's dependencies, i.e., the right hand side of a make rule. + Descend into each dependency to find something that looks like a simulator's + source code list. Once source code list is found, extract simulator defines, includes, + source files and set flags. + """ + if debug >= 1: + print('{0}-- target: {1}'.format(depth, target)) + + target_deps = SPM.target_dep_list(target, rules, defs) + if debug >= 2: + print('{0}target deps: {1}'.format(depth, target_deps)) + + has_buildrom = any(filter(lambda dep: dep == '${BUILD_ROMS}', target_deps)) + if debug >= 1: + print('{0} has_buildrom {1}', has_buildrom) + + deps = [dep for dep in target_deps if dep not in _ignored_deps] + targ_actions = actions.get(target) + if targ_actions: + depth3 = depth + ' ' + if debug >= 2: + print('{0}deps {1}'.format(depth3, deps)) + + # Are the dependencies a source code list? + expanded_deps = [l for slist in [ SPM.shallow_expand_vars(dep, defs).split() for dep in deps ] for l in slist] + if debug >= 3: + print('{0}expanded_deps {1}'.format(depth3, expanded_deps)) + + if any(filter(lambda f: f.endswith('.c'), expanded_deps)): + if debug >= 1: + print('{0}sim sources {1}'.format(depth3, deps)) + if debug >= 2: + print('{0}targ_actions {1}'.format(depth3, targ_actions)) + + # The simulators' compile and test actions are very regular and easy to find: + compile_act = None + test_name = None + sim_dir = None + for act in targ_actions: + m_cact = _compile_act_rx.match(act) + m_test = _test_name_rx.match(act) + if m_cact: + compile_act = m_cact.group(1) + elif m_test: + (sim_dir, test_name) = m_test.group(1, 2) + + if debug >= 2: + print('{0}sim_dir {1}'.format(depth3, sim_dir)) + print('{0}compile_act {1}'.format(depth3, compile_act)) + print('{0}test_name {1}'.format(depth3, test_name)) + + if compile_act and test_name and sim_dir: + sim_name = target.replace("${BIN}", "").replace("${EXE}", "") + # Just in case there are vestiges of old-style make variables + sim_name = sim_name.replace("$(BIN)", "").replace("$(EXE)", "") + if debug >= 2: + print('{0}sim_name {1}'.format(depth3, sim_name)) + + simulators.extract(compile_act, test_name, sim_dir, sim_name, defs, has_buildrom, debug, depth+' ') + else: + # No actions associated with the dependency(ies), which means that the dependency(ies) + # are meta-targets. Continue to walk. + for dep in deps: + walk_target_deps(dep, defs, rules, actions, simulators, depth=depth+' ', debug=debug) + + +if __name__ == '__main__': + args = argparse.ArgumentParser(description="SIMH simulator CMakeLists.txt generator.") + args.add_argument('--debug', nargs='?', const=1, default=0, type=int, + help='Debug level (0-3, 0 == off)') + args.add_argument('--srcdir', default=None, + help='makefile source directory.') + args.add_argument('--skip-orphans', action='store_true', + help='Skip the check for packaging orphans') + + flags = vars(args.parse_args()) + + debug_level = flags.get('debug') + makefile_dir = flags.get('srcdir') + + found_makefile = True + if makefile_dir is None: + ## Find the makefile, which should be one directory up from this Python + ## module + makefile_dir = GEN_SCRIPT_DIR + print('{0}: Looking for makefile, starting in {1}'.format(GEN_SCRIPT_NAME, makefile_dir)) + the_makefile = '' + while makefile_dir: + the_makefile = os.path.join(makefile_dir, "makefile") + if os.path.exists(the_makefile): + break + else: + makefile_dir = os.path.dirname(makefile_dir) + print('{0}: Looking for makefile, trying {1}'.format(GEN_SCRIPT_NAME, makefile_dir)) + + if not the_makefile: + found_makefile = False + else: + the_makefile = os.path.join(makefile_dir, "makefile") + if not os.path.exists(the_makefile): + found_makefile = False + + if not found_makefile: + print('{0}: SIMH top-level makefile not found, relative to {1}'.format(GEN_SCRIPT_NAME, GEN_SCRIPT_DIR)) + sys.exit(1) + + sims = process_makefile(makefile_dir, debug=debug_level) + + ## Sanity check: Make sure that all of the simulators in SPKG.package_info have + ## been encountered + for simdir in sims.dirs.keys(): + if debug_level >= 2: + print('{}: simdir {}'.format(GEN_SCRIPT_NAME, simdir)) + for sim in sims.dirs[simdir].simulators.keys(): + SPKG.package_info[sim].encountered() + + if not flags.get('skip_orphans'): + print('{0}: Expecting to emit {1} simulators.'.format(GEN_SCRIPT_NAME, len(SPKG.package_info.keys()))) + + orphans = [ sim for sim, pkg_info in SPKG.package_info.items() if not pkg_info.was_processed() ] + if len(orphans) > 0: + print('{0}: Simulators not extracted from makefile:'.format(GEN_SCRIPT_NAME)) + for orphan in orphans: + print('{0}{1}'.format(' ' * 4, orphan)) + sys.exit(1) + else: + print('{0}: All simulators present and accounted for!'.format(GEN_SCRIPT_NAME)) + + if debug_level >= 1: + pp = pprint.PrettyPrinter() + pp.pprint(sims) + + ## Emit all of the individual CMakeLists.txt + sims.write_simulators(makefile_dir, debug=debug_level) + ## Emit the packaging data + SPKG.write_packaging(makefile_dir) diff --git a/cmake/git-commit-id.cmake b/cmake/git-commit-id.cmake new file mode 100644 index 00000000..2c994a6b --- /dev/null +++ b/cmake/git-commit-id.cmake @@ -0,0 +1,89 @@ +## git-commit-id.cmake +## +## Get the current Git commit hash code and commit time, update +## .git-commit-id and .git-commit-id.h + +set(GIT_COMMIT_ID ${GIT_COMMIT_DEST}/.git-commit-id) +set(GIT_COMMIT_ID_H ${GIT_COMMIT_DEST}/.git-commit-id.h) + +find_program(GIT_COMMAND git) +if (GIT_COMMAND) + execute_process(COMMAND ${GIT_COMMAND} "log" "-1" "--pretty=%H" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE HAVE_GIT_COMMIT_HASH + OUTPUT_VARIABLE SIMH_GIT_COMMIT_HASH) + + execute_process(COMMAND ${GIT_COMMAND} "log" "-1" "--pretty=%aI" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE HAVE_GIT_COMMIT_TIME + OUTPUT_VARIABLE SIMH_GIT_COMMIT_TIME) + + execute_process(COMMAND ${GIT_COMMAND} "update-index" "--refresh" "--" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE HAVE_UNCOMMITTED_CHANGES + OUTPUT_VARIABLE SIMH_UNCOMMITTED_CHANGES) +endif () + +if (GIT_COMMAND AND NOT (HAVE_GIT_COMMIT_HASH OR HAVE_GIT_COMMIT_TIME)) + string(STRIP ${SIMH_GIT_COMMIT_HASH} SIMH_GIT_COMMIT_HASH) + string(STRIP ${SIMH_GIT_COMMIT_TIME} SIMH_GIT_COMMIT_TIME) + string(REPLACE "T" " " SIMH_GIT_COMMIT_TIME ${SIMH_GIT_COMMIT_TIME}) + + if (HAVE_UNCOMMITTED_CHANGES) + ## message(STATUS "Git detected uncommitted changes.") + string(APPEND SIMH_GIT_COMMIT_HASH "+uncommitted-changes") + else () + message(STATUS "Clean working directory, no uncommitted changes.") + endif () + + set(WRITE_GIT_COMMIT_FILES True) + if (EXISTS ${GIT_COMMIT_ID}) + set(EXISTING_GIT_COMMIT_HASH) + set(EXISTING_GIT_COMMIT_TIME) + file(STRINGS ${GIT_COMMIT_ID} git_info) + foreach (inp IN LISTS git_info) + if (inp MATCHES "SIM_GIT_COMMIT_ID (.*)") + set(EXISTING_GIT_COMMIT_HASH ${CMAKE_MATCH_1}) + elseif (inp MATCHES "SIM_GIT_COMMIT_TIME (.*)") + set(EXISTING_GIT_COMMIT_TIME ${CMAKE_MATCH_1}) + endif () + endforeach() + if (EXISTING_GIT_COMMIT_HASH STREQUAL SIMH_GIT_COMMIT_HASH AND + EXISTING_GIT_COMMIT_TIME STREQUAL SIMH_GIT_COMMIT_TIME) + ## message(STATUS "GIT hash and time match, not writing files.") + set(WRITE_GIT_COMMIT_FILES False) + endif () + endif () + + if (WRITE_GIT_COMMIT_FILES) + message(STATUS "Updating GIT commit ID") + message(STATUS "SIM_GIT_COMMIT_ID: ${SIMH_GIT_COMMIT_HASH}") + message(STATUS "SIM_GIT_COMMIT_TIME: ${SIMH_GIT_COMMIT_TIME}") + + message(STATUS "Writing ${GIT_COMMIT_ID}") + file(WRITE ${GIT_COMMIT_ID} + "SIM_GIT_COMMIT_ID ${SIMH_GIT_COMMIT_HASH}\n" + "SIM_GIT_COMMIT_TIME ${SIMH_GIT_COMMIT_TIME}\n") + + message(STATUS "Writing ${GIT_COMMIT_ID_H}") + file(WRITE ${GIT_COMMIT_ID_H} + "#define SIM_GIT_COMMIT_ID ${SIMH_GIT_COMMIT_HASH}\n" + "#define SIM_GIT_COMMIT_TIME ${SIMH_GIT_COMMIT_TIME}\n") + ## else () + ## message(STATUS "No changes to ${GIT_COMMIT_ID}") + ## message(STATUS "No changes to ${GIT_COMMIT_ID_H}") + endif () +else () + message(STATUS "SIM_GIT_COMMIT_ID not set.") + message(STATUS "SIM_GIT_COMMIT_TIME not set.") + + if (NOT EXISTS ${GIT_COMMIT_ID_H}) + message(STATUS "Writing default ${GIT_COMMIT_ID_H}") + file(WRITE ${GIT_COMMIT_ID_H} + "#undef SIM_GIT_COMMIT_ID\n" + "#undef SIM_GIT_COMMIT_TIME\n" + ) + else () + message(STATUS "Leaving ${GIT_COMMIT_ID_H} intact") + endif () +endif() diff --git a/cmake/github_v141_xp.ps1 b/cmake/github_v141_xp.ps1 new file mode 100644 index 00000000..e448d3fd --- /dev/null +++ b/cmake/github_v141_xp.ps1 @@ -0,0 +1,153 @@ +<# +.SYNOPSIS +Install Visual Studio's 'v141_xp' XP toolkit on Github. + +.DESCRIPTION +Update the existing Github Visual Studio installation in-place. This script +will iterate through all VS installations identified by the `vswhere` utility. + +This script is Grey Magic. The `vs_installer` installer utility exits almost +immediately if you run it from the command line. `vs_installer`'s subprocesses +are reasonably well known, which allows this script to query the processes and +wait for them to terminate. + +GitHub Actions does something strange with stdout and stderr, so you won't get any +indication of failure or output that shows you that something is happening. + +The waiting code was adapted from the Chocolatey VS installer scripts (Wait-VSIstallerProcesses). + +Ref: https://github.com/jberezanski/ChocolateyPackages/blob/master/chocolatey-visualstudio.extension/extensions/Wait-VSInstallerProcesses.ps1 +#> + +$exitcode = $null +$vswhere = "${env:ProgramFiles} (x86)\Microsoft Visual Studio\Installer\vswhere" +$vsinstaller = "${env:ProgramFiles} (x86)\Microsoft Visual Studio\Installer\vs_installer.exe" +$vsInstallOut = "$env:TEMP\vsinstall-out.txt" +$vsInstallErr = "$env:TEMP\vsinstall-err.txt" + +& ${vswhere} -property installationPath | Foreach-Object -process { + # Save the installlation path for the current iteration + $installPath = $_ + + # Make sure that previously running installers have all exited. + Write-Debug ('Looking for previously running VS installer processes') + $lazyQuitterProcessNames = @('vs_installershell', 'vs_installerservice') + do + { + $lazyQuitterProcesses = Get-Process -Name $lazyQuitterProcessNames -ErrorAction SilentlyContinue | ` + Where-Object { $null -ne $_ -and -not $_.HasExited } + $lazyQuitterProcessCount = ($lazyQuitterProcesses | Measure-Object).Count + if ($lazyQuitterProcessCount -gt 0) + { + try + { + $lazyQuitterProcesses | Sort-Object -Property Name, Id | ForEach-Object { '[{0}] {1}' -f $_.Id, $_.Name } | Write-Debug + $lazyQuitterProcesses | Wait-Process -Timeout 10 -ErrorAction SilentlyContinue + } + finally + { + $lazyQuitterProcesses | ForEach-Object { $_.Dispose() } + $lazyQuitterProcesses = $null + } + } + } + while ($lazyQuitterProcessCount -gt 0) + + ## Now kick off our install: + ## + ## --quiet: Don't open/show UI + ## --force: Terminate any VS instances forcibly + ## --norestart: Delay reboot after install, if needed + ## --installWhileDownloading: Self-explanitory. + ## + ## Note: "--wait" doesn't. + $vsinstallParams = @( + "modify", + "--installPath", "$installPath", + "--add", "Microsoft.VisualStudio.Component.VC.v141.x86.x64", + "--add", "Microsoft.VisualStudio.Component.WinXP", + "--quiet", "--force", "--norestart", "--installWhileDownloading" + ) + + & $vsInstaller $vsinstallParams 2> $vsInstallErr > $vsInstallOut + + ## VS installer processes for which we want to wait because installation isn't complete. Yet. + $vsinstallerProcesses = @( 'vs_bootstrapper', 'vs_setup_bootstrapper', 'vs_installer', 'vs_installershell', ` + 'vs_installerservice', 'setup') + $vsInstallerProcessFilter = { $_.Name -ne 'setup' -or $_.Path -like '*\Microsoft Visual Studio\Installer*\setup.exe' } + do + { + Write-Debug ('Looking for running VS installer processes') + $vsInstallerRemaining = Get-Process -Name $vsinstallerProcesses -ErrorAction SilentlyContinue | ` + Where-Object { $null -ne $_ -and -not $_.HasExited } | ` + Where-Object $vsInstallerProcessFilter + $vsInstallerProcessCount = ($vsInstallerRemaining | Measure-Object).Count + if ($vsInstallerProcessCount -gt 0) + { + try + { + Write-Debug "Found $vsInstallerProcessCount running Visual Studio installer processes:" + $vsInstallerRemaining | Sort-Object -Property Name, Id | ForEach-Object { '[{0}] {1}' -f $_.Id, $_.Name } | Write-Debug + + foreach ($p in $vsInstallerProcesses) + { + [void] $p.Handle # make sure we get the exit code http://stackoverflow.com/a/23797762/266876 + } + + Write-Debug ('Waiting 60 seconds for process(es) to exit...') + $vsInstallerRemaining | Wait-Process -Timeout 60 -ErrorAction SilentlyContinue + + foreach ($proc in $vsInstallerProcesses) + { + if (-not $proc.HasExited) + { + continue + } + if ($null -eq $exitCode) + { + $exitCode = $proc.ExitCode + } + if ($proc.ExitCode -ne 0 -and $null -ne $proc.ExitCode) + { + Write-Debug "$($proc.Name) process $($proc.Id) exited with code $($proc.ExitCode)" + if ($exitCode -eq 0) + { + $exitCode = $proc.ExitCode + } + } + } + } + finally + { + $vsInstallerRemaining | ForEach-Object { $_.Dispose() } + $vsInstallerRemaining = $null + } + } + } + while (($vsInstallerStartup -and $vsInstallerStartCount -gt 0) -or $vsInstallerProcessCount -gt 0) +} + +if ((Test-Path $vsInstallOut -PathType Leaf) -and (Get-Item $vsInstallOut).length -gt 0kb) +{ + Write-Output "-- vsinstaller output:" + Get-Content $vsInstallOut +} +else +{ + Write-Debug "-- No vsinstaller output." +} + +if ((Test-Path $vsInstallErr -PathType Leaf) -and (Get-Item $vsInstallErr).length -gt 0kb) +{ + Write-Output "-- vsinstaller error output:" + Get-Content $vsInstallErr +} +else +{ + Write-Debug "-- No vsinstaller error/diag output." +} + +if ($null -ne $exitcode -and $exitcode -ne 0) +{ + throw "VS installer exited with non-zero exit status: $exitcode" +} diff --git a/cmake/installer-customizations/CPackSimhCustom.cmake.in b/cmake/installer-customizations/CPackSimhCustom.cmake.in new file mode 100644 index 00000000..ed83edf9 --- /dev/null +++ b/cmake/installer-customizations/CPackSimhCustom.cmake.in @@ -0,0 +1,3 @@ +## Additional variables needed to drive CPack: + +file(TO_NATIVE_PATH "@CMAKE_SOURCE_DIR@/cmake/installer-customizations/NSIS" SIMH_NSIS_INCLUDE_DIR) diff --git a/cmake/installer-customizations/NSIS.template.in b/cmake/installer-customizations/NSIS.template.in new file mode 100644 index 00000000..8324ab78 --- /dev/null +++ b/cmake/installer-customizations/NSIS.template.in @@ -0,0 +1,1018 @@ +; CPack install script designed for a nmake build + +;-------------------------------- +; You must define these values + + !define VERSION "@CPACK_PACKAGE_VERSION@" + !define PATCH "@CPACK_PACKAGE_VERSION_PATCH@" + !define INST_DIR "@CPACK_TEMPORARY_DIRECTORY@" + + !addincludedir "@SIMH_NSIS_INCLUDE_DIR@" + +;-------------------------------- +;Variables + + Var MUI_TEMP + Var STARTMENU_FOLDER + Var SV_ALLUSERS + Var START_MENU + Var DO_NOT_ADD_TO_PATH + Var ADD_TO_PATH_ALL_USERS + Var ADD_TO_PATH_CURRENT_USER + Var INSTALL_DESKTOP + Var IS_DEFAULT_INSTALLDIR +;-------------------------------- +;Include Modern UI + + !include "MUI.nsh" + + ;Default installation folder + InstallDir "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@" + +;-------------------------------- +;General + + ;Name and file + Name "@CPACK_NSIS_PACKAGE_NAME@" + OutFile "@CPACK_TOPLEVEL_DIRECTORY@/@CPACK_OUTPUT_FILE_NAME@" + + ;Set compression + SetCompressor @CPACK_NSIS_COMPRESSOR@ + + ; Execution level: none, access, admin, user + ; User execution level -- there's nothing in SIMH that has to install + ; in ProgramFiles or ProgramFiles (x86) + RequestExecutionLevel user + +@CPACK_NSIS_DEFINES@ +@CPACK_NSIS_MANIFEST_DPI_AWARE_CODE@ +@CPACK_NSIS_BRANDING_TEXT_CODE@ + + !include Sections.nsh + +;-------------------------------- +; Symbolic and hard file link support: + + !include "FileFunc.nsh" + !include "FileLinks.nsh" + +;--- Component support macros: --- +; The code for the add/remove functionality is from: +; https://nsis.sourceforge.io/Add/Remove_Functionality +; It has been modified slightly and extended to provide +; inter-component dependencies. +Var AR_SecFlags +Var AR_RegFlags +@CPACK_NSIS_SECTION_SELECTED_VARS@ + +; Loads the "selected" flag for the section named SecName into the +; variable VarName. +!macro LoadSectionSelectedIntoVar SecName VarName + SectionGetFlags ${${SecName}} $${VarName} + IntOp $${VarName} $${VarName} & ${SF_SELECTED} ;Turn off all other bits +!macroend + +; Loads the value of a variable... can we get around this? +!macro LoadVar VarName + IntOp $R0 0 + $${VarName} +!macroend + +; Sets the value of a variable +!macro StoreVar VarName IntValue + IntOp $${VarName} 0 + ${IntValue} +!macroend + +!macro InitSection SecName + ; This macro reads component installed flag from the registry and + ;changes checked state of the section on the components page. + ;Input: section index constant name specified in Section command. + + ClearErrors + ;Reading component status from registry + ReadRegDWORD $AR_RegFlags HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\Components\${SecName}" "Installed" + IfErrors "default_${SecName}" + ;Status will stay default if registry value not found + ;(component was never installed) + IntOp $AR_RegFlags $AR_RegFlags & ${SF_SELECTED} ;Turn off all other bits + SectionGetFlags ${${SecName}} $AR_SecFlags ;Reading default section flags + IntOp $AR_SecFlags $AR_SecFlags & 0xFFFE ;Turn lowest (enabled) bit off + IntOp $AR_SecFlags $AR_RegFlags | $AR_SecFlags ;Change lowest bit + + ; Note whether this component was installed before + !insertmacro StoreVar ${SecName}_was_installed $AR_RegFlags + IntOp $R0 $AR_RegFlags & $AR_RegFlags + + ;Writing modified flags + SectionSetFlags ${${SecName}} $AR_SecFlags + + "default_${SecName}:" + !insertmacro LoadSectionSelectedIntoVar ${SecName} ${SecName}_selected +!macroend + +!macro FinishSection SecName + ; This macro reads section flag set by user and removes the section + ;if it is not selected. + ;Then it writes component installed flag to registry + ;Input: section index constant name specified in Section command. + + SectionGetFlags ${${SecName}} $AR_SecFlags ;Reading section flags + ;Checking lowest bit: + IntOp $AR_SecFlags $AR_SecFlags & ${SF_SELECTED} + IntCmp $AR_SecFlags 1 "leave_${SecName}" + ;Section is not selected: + ;Calling Section uninstall macro and writing zero installed flag + !insertmacro "Remove_${${SecName}}" + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\Components\${SecName}" \ + "Installed" 0 + Goto "exit_${SecName}" + + "leave_${SecName}:" + ;Section is selected: + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\Components\${SecName}" \ + "Installed" 1 + + "exit_${SecName}:" +!macroend + +!macro RemoveSection_CPack SecName + ; This macro is used to call section's Remove_... macro + ;from the uninstaller. + ;Input: section index constant name specified in Section command. + + !insertmacro "Remove_${${SecName}}" +!macroend + +; Determine whether the selection of SecName changed +!macro MaybeSelectionChanged SecName + !insertmacro LoadVar ${SecName}_selected + SectionGetFlags ${${SecName}} $R1 + IntOp $R1 $R1 & ${SF_SELECTED} ;Turn off all other bits + + ; See if the status has changed: + IntCmp $R0 $R1 "${SecName}_unchanged" + !insertmacro LoadSectionSelectedIntoVar ${SecName} ${SecName}_selected + + IntCmp $R1 ${SF_SELECTED} "${SecName}_was_selected" + !insertmacro "Deselect_required_by_${SecName}" + goto "${SecName}_unchanged" + + "${SecName}_was_selected:" + !insertmacro "Select_${SecName}_depends" + + "${SecName}_unchanged:" +!macroend +;--- End of Add/Remove macros --- + +;-------------------------------- +;Interface Settings + + !define MUI_HEADERIMAGE + !define MUI_ABORTWARNING + +;---------------------------------------- +; based upon a script of "Written by KiCHiK 2003-01-18 05:57:02" +;---------------------------------------- +!verbose 3 +!include "WinMessages.NSH" +!verbose 4 +;==================================================== +; get_NT_environment +; Returns: the selected environment +; Output : head of the stack +;==================================================== +!macro select_NT_profile UN +Function ${UN}select_NT_profile + StrCmp $ADD_TO_PATH_ALL_USERS "1" 0 environment_single + DetailPrint "Selected environment for all users" + Push "all" + Return + environment_single: + DetailPrint "Selected environment for current user only." + Push "current" + Return +FunctionEnd +!macroend +!insertmacro select_NT_profile "" +!insertmacro select_NT_profile "un." +;---------------------------------------------------- +!define NT_current_env 'HKCU "Environment"' +!define NT_all_env 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' + +!ifndef WriteEnvStr_RegKey + !ifdef ALL_USERS + !define WriteEnvStr_RegKey \ + 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' + !else + !define WriteEnvStr_RegKey 'HKCU "Environment"' + !endif +!endif + +; AddToPath - Adds the given dir to the search path. +; Input - head of the stack +; Note - Win9x systems requires reboot + +Function AddToPath + Exch $0 + Push $1 + Push $2 + Push $3 + + # don't add if the path doesn't exist + IfFileExists "$0\*.*" "" AddToPath_done + + ReadEnvStr $1 PATH + ; if the path is too long for a NSIS variable NSIS will return a 0 + ; length string. If we find that, then warn and skip any path + ; modification as it will trash the existing path. + StrLen $2 $1 + IntCmp $2 0 CheckPathLength_ShowPathWarning CheckPathLength_Done CheckPathLength_Done + CheckPathLength_ShowPathWarning: + Messagebox MB_OK|MB_ICONEXCLAMATION "Warning! PATH too long installer unable to modify PATH!" + Goto AddToPath_done + CheckPathLength_Done: + Push "$1;" + Push "$0;" + Call StrStr + Pop $2 + StrCmp $2 "" "" AddToPath_done + Push "$1;" + Push "$0\;" + Call StrStr + Pop $2 + StrCmp $2 "" "" AddToPath_done + GetFullPathName /SHORT $3 $0 + Push "$1;" + Push "$3;" + Call StrStr + Pop $2 + StrCmp $2 "" "" AddToPath_done + Push "$1;" + Push "$3\;" + Call StrStr + Pop $2 + StrCmp $2 "" "" AddToPath_done + + Call IsNT + Pop $1 + StrCmp $1 1 AddToPath_NT + ; Not on NT + StrCpy $1 $WINDIR 2 + FileOpen $1 "$1\autoexec.bat" a + FileSeek $1 -1 END + FileReadByte $1 $2 + IntCmp $2 26 0 +2 +2 # DOS EOF + FileSeek $1 -1 END # write over EOF + FileWrite $1 "$\r$\nSET PATH=%PATH%;$3$\r$\n" + FileClose $1 + SetRebootFlag true + Goto AddToPath_done + + AddToPath_NT: + StrCmp $ADD_TO_PATH_ALL_USERS "1" ReadAllKey + ReadRegStr $1 ${NT_current_env} "PATH" + Goto DoTrim + ReadAllKey: + ReadRegStr $1 ${NT_all_env} "PATH" + DoTrim: + StrCmp $1 "" AddToPath_NTdoIt + Push $1 + Call Trim + Pop $1 + StrCpy $0 "$1;$0" + AddToPath_NTdoIt: + StrCmp $ADD_TO_PATH_ALL_USERS "1" WriteAllKey + WriteRegExpandStr ${NT_current_env} "PATH" $0 + Goto DoSend + WriteAllKey: + WriteRegExpandStr ${NT_all_env} "PATH" $0 + DoSend: + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + + AddToPath_done: + Pop $3 + Pop $2 + Pop $1 + Pop $0 +FunctionEnd + + +; RemoveFromPath - Remove a given dir from the path +; Input: head of the stack + +Function un.RemoveFromPath + Exch $0 + Push $1 + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + + IntFmt $6 "%c" 26 # DOS EOF + + Call un.IsNT + Pop $1 + StrCmp $1 1 unRemoveFromPath_NT + ; Not on NT + StrCpy $1 $WINDIR 2 + FileOpen $1 "$1\autoexec.bat" r + GetTempFileName $4 + FileOpen $2 $4 w + GetFullPathName /SHORT $0 $0 + StrCpy $0 "SET PATH=%PATH%;$0" + Goto unRemoveFromPath_dosLoop + + unRemoveFromPath_dosLoop: + FileRead $1 $3 + StrCpy $5 $3 1 -1 # read last char + StrCmp $5 $6 0 +2 # if DOS EOF + StrCpy $3 $3 -1 # remove DOS EOF so we can compare + StrCmp $3 "$0$\r$\n" unRemoveFromPath_dosLoopRemoveLine + StrCmp $3 "$0$\n" unRemoveFromPath_dosLoopRemoveLine + StrCmp $3 "$0" unRemoveFromPath_dosLoopRemoveLine + StrCmp $3 "" unRemoveFromPath_dosLoopEnd + FileWrite $2 $3 + Goto unRemoveFromPath_dosLoop + unRemoveFromPath_dosLoopRemoveLine: + SetRebootFlag true + Goto unRemoveFromPath_dosLoop + + unRemoveFromPath_dosLoopEnd: + FileClose $2 + FileClose $1 + StrCpy $1 $WINDIR 2 + Delete "$1\autoexec.bat" + CopyFiles /SILENT $4 "$1\autoexec.bat" + Delete $4 + Goto unRemoveFromPath_done + + unRemoveFromPath_NT: + StrCmp $ADD_TO_PATH_ALL_USERS "1" unReadAllKey + ReadRegStr $1 ${NT_current_env} "PATH" + Goto unDoTrim + unReadAllKey: + ReadRegStr $1 ${NT_all_env} "PATH" + unDoTrim: + StrCpy $5 $1 1 -1 # copy last char + StrCmp $5 ";" +2 # if last char != ; + StrCpy $1 "$1;" # append ; + Push $1 + Push "$0;" + Call un.StrStr ; Find `$0;` in $1 + Pop $2 ; pos of our dir + StrCmp $2 "" unRemoveFromPath_done + ; else, it is in path + # $0 - path to add + # $1 - path var + StrLen $3 "$0;" + StrLen $4 $2 + StrCpy $5 $1 -$4 # $5 is now the part before the path to remove + StrCpy $6 $2 "" $3 # $6 is now the part after the path to remove + StrCpy $3 $5$6 + + StrCpy $5 $3 1 -1 # copy last char + StrCmp $5 ";" 0 +2 # if last char == ; + StrCpy $3 $3 -1 # remove last char + + StrCmp $ADD_TO_PATH_ALL_USERS "1" unWriteAllKey + WriteRegExpandStr ${NT_current_env} "PATH" $3 + Goto unDoSend + unWriteAllKey: + WriteRegExpandStr ${NT_all_env} "PATH" $3 + unDoSend: + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + + unRemoveFromPath_done: + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 +FunctionEnd + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Uninstall stuff +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +########################################### +# Utility Functions # +########################################### + +;==================================================== +; IsNT - Returns 1 if the current system is NT, 0 +; otherwise. +; Output: head of the stack +;==================================================== +; IsNT +; no input +; output, top of the stack = 1 if NT or 0 if not +; +; Usage: +; Call IsNT +; Pop $R0 +; ($R0 at this point is 1 or 0) + +!macro IsNT un +Function ${un}IsNT + Push $0 + ReadRegStr $0 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" CurrentVersion + StrCmp $0 "" 0 IsNT_yes + ; we are not NT. + Pop $0 + Push 0 + Return + + IsNT_yes: + ; NT!!! + Pop $0 + Push 1 +FunctionEnd +!macroend +!insertmacro IsNT "" +!insertmacro IsNT "un." + +; StrStr +; input, top of stack = string to search for +; top of stack-1 = string to search in +; output, top of stack (replaces with the portion of the string remaining) +; modifies no other variables. +; +; Usage: +; Push "this is a long ass string" +; Push "ass" +; Call StrStr +; Pop $R0 +; ($R0 at this point is "ass string") + +!macro StrStr un +Function ${un}StrStr +Exch $R1 ; st=haystack,old$R1, $R1=needle + Exch ; st=old$R1,haystack + Exch $R2 ; st=old$R1,old$R2, $R2=haystack + Push $R3 + Push $R4 + Push $R5 + StrLen $R3 $R1 + StrCpy $R4 0 + ; $R1=needle + ; $R2=haystack + ; $R3=len(needle) + ; $R4=cnt + ; $R5=tmp + loop: + StrCpy $R5 $R2 $R3 $R4 + StrCmp $R5 $R1 done + StrCmp $R5 "" done + IntOp $R4 $R4 + 1 + Goto loop +done: + StrCpy $R1 $R2 "" $R4 + Pop $R5 + Pop $R4 + Pop $R3 + Pop $R2 + Exch $R1 +FunctionEnd +!macroend +!insertmacro StrStr "" +!insertmacro StrStr "un." + +Function Trim ; Added by Pelaca + Exch $R1 + Push $R2 +Loop: + StrCpy $R2 "$R1" 1 -1 + StrCmp "$R2" " " RTrim + StrCmp "$R2" "$\n" RTrim + StrCmp "$R2" "$\r" RTrim + StrCmp "$R2" ";" RTrim + GoTo Done +RTrim: + StrCpy $R1 "$R1" -1 + Goto Loop +Done: + Pop $R2 + Exch $R1 +FunctionEnd + +Function ConditionalAddToRegistry + Pop $0 + Pop $1 + StrCmp "$0" "" ConditionalAddToRegistry_EmptyString + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" \ + "$1" "$0" + ;MessageBox MB_OK "Set Registry: '$1' to '$0'" + DetailPrint "Set install registry entry: '$1' to '$0'" + ConditionalAddToRegistry_EmptyString: +FunctionEnd + +;-------------------------------- + +!ifdef CPACK_USES_DOWNLOAD +Function DownloadFile + IfFileExists $INSTDIR\* +2 + CreateDirectory $INSTDIR + Pop $0 + + ; Skip if already downloaded + IfFileExists $INSTDIR\$0 0 +2 + Return + + StrCpy $1 "@CPACK_DOWNLOAD_SITE@" + + try_again: + NSISdl::download "$1/$0" "$INSTDIR\$0" + + Pop $1 + StrCmp $1 "success" success + StrCmp $1 "Cancelled" cancel + MessageBox MB_OK "Download failed: $1" + cancel: + Return + success: +FunctionEnd +!endif + +;-------------------------------- +; Define some macro setting for the gui +@CPACK_NSIS_INSTALLER_MUI_ICON_CODE@ +@CPACK_NSIS_INSTALLER_ICON_CODE@ +@CPACK_NSIS_INSTALLER_MUI_WELCOMEFINISH_CODE@ +@CPACK_NSIS_INSTALLER_MUI_UNWELCOMEFINISH_CODE@ +@CPACK_NSIS_INSTALLER_MUI_FINISHPAGE_RUN_CODE@ + +;-------------------------------- +;Pages + @CPACK_NSIS_INSTALLER_WELCOME_TITLE_CODE@ + @CPACK_NSIS_INSTALLER_WELCOME_TITLE_3LINES_CODE@ + !insertmacro MUI_PAGE_WELCOME + + @CPACK_NSIS_LICENSE_PAGE@ + Page custom InstallOptionsPage + !insertmacro MUI_PAGE_DIRECTORY + + ;Start Menu Folder Page Configuration + !define MUI_STARTMENUPAGE_REGISTRY_ROOT "SHCTX" + !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder" + !insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER + + @CPACK_NSIS_PAGE_COMPONENTS@ + + !insertmacro MUI_PAGE_INSTFILES + @CPACK_NSIS_INSTALLER_FINISH_TITLE_CODE@ + @CPACK_NSIS_INSTALLER_FINISH_TITLE_3LINES_CODE@ + !insertmacro MUI_PAGE_FINISH + + !insertmacro MUI_UNPAGE_CONFIRM + !insertmacro MUI_UNPAGE_INSTFILES + +;-------------------------------- +;Languages + + !insertmacro MUI_LANGUAGE "English" ;first language is the default language + !insertmacro MUI_LANGUAGE "Afrikaans" + !insertmacro MUI_LANGUAGE "Albanian" + !insertmacro MUI_LANGUAGE "Arabic" + !insertmacro MUI_LANGUAGE "Asturian" + !insertmacro MUI_LANGUAGE "Basque" + !insertmacro MUI_LANGUAGE "Belarusian" + !insertmacro MUI_LANGUAGE "Bosnian" + !insertmacro MUI_LANGUAGE "Breton" + !insertmacro MUI_LANGUAGE "Bulgarian" + !insertmacro MUI_LANGUAGE "Catalan" + !insertmacro MUI_LANGUAGE "Corsican" + !insertmacro MUI_LANGUAGE "Croatian" + !insertmacro MUI_LANGUAGE "Czech" + !insertmacro MUI_LANGUAGE "Danish" + !insertmacro MUI_LANGUAGE "Dutch" + !insertmacro MUI_LANGUAGE "Esperanto" + !insertmacro MUI_LANGUAGE "Estonian" + !insertmacro MUI_LANGUAGE "Farsi" + !insertmacro MUI_LANGUAGE "Finnish" + !insertmacro MUI_LANGUAGE "French" + !insertmacro MUI_LANGUAGE "Galician" + !insertmacro MUI_LANGUAGE "German" + !insertmacro MUI_LANGUAGE "Greek" + !insertmacro MUI_LANGUAGE "Hebrew" + !insertmacro MUI_LANGUAGE "Hungarian" + !insertmacro MUI_LANGUAGE "Icelandic" + !insertmacro MUI_LANGUAGE "Indonesian" + !insertmacro MUI_LANGUAGE "Irish" + !insertmacro MUI_LANGUAGE "Italian" + !insertmacro MUI_LANGUAGE "Japanese" + !insertmacro MUI_LANGUAGE "Korean" + !insertmacro MUI_LANGUAGE "Kurdish" + !insertmacro MUI_LANGUAGE "Latvian" + !insertmacro MUI_LANGUAGE "Lithuanian" + !insertmacro MUI_LANGUAGE "Luxembourgish" + !insertmacro MUI_LANGUAGE "Macedonian" + !insertmacro MUI_LANGUAGE "Malay" + !insertmacro MUI_LANGUAGE "Mongolian" + !insertmacro MUI_LANGUAGE "Norwegian" + !insertmacro MUI_LANGUAGE "NorwegianNynorsk" + !insertmacro MUI_LANGUAGE "Pashto" + !insertmacro MUI_LANGUAGE "Polish" + !insertmacro MUI_LANGUAGE "Portuguese" + !insertmacro MUI_LANGUAGE "PortugueseBR" + !insertmacro MUI_LANGUAGE "Romanian" + !insertmacro MUI_LANGUAGE "Russian" + !insertmacro MUI_LANGUAGE "ScotsGaelic" + !insertmacro MUI_LANGUAGE "Serbian" + !insertmacro MUI_LANGUAGE "SerbianLatin" + !insertmacro MUI_LANGUAGE "SimpChinese" + !insertmacro MUI_LANGUAGE "Slovak" + !insertmacro MUI_LANGUAGE "Slovenian" + !insertmacro MUI_LANGUAGE "Spanish" + !insertmacro MUI_LANGUAGE "SpanishInternational" + !insertmacro MUI_LANGUAGE "Swedish" + !insertmacro MUI_LANGUAGE "Tatar" + !insertmacro MUI_LANGUAGE "Thai" + !insertmacro MUI_LANGUAGE "TradChinese" + !insertmacro MUI_LANGUAGE "Turkish" + !insertmacro MUI_LANGUAGE "Ukrainian" + !insertmacro MUI_LANGUAGE "Uzbek" + !insertmacro MUI_LANGUAGE "Vietnamese" + !insertmacro MUI_LANGUAGE "Welsh" + +;-------------------------------- +;Reserve Files + + ;These files should be inserted before other files in the data block + ;Keep these lines before any File command + ;Only for solid compression (by default, solid compression is enabled for BZIP2 and LZMA) + + ReserveFile "NSIS.InstallOptions.ini" + !insertmacro MUI_RESERVEFILE_INSTALLOPTIONS + + ; for UserInfo::GetName and UserInfo::GetAccountType + ReserveFile /plugin 'UserInfo.dll' + +;-------------------------------- +; Installation types +@CPACK_NSIS_INSTALLATION_TYPES@ + +;-------------------------------- +; Component sections +@CPACK_NSIS_COMPONENT_SECTIONS@ +@CPACK_NSIS_INSTALLER_MUI_COMPONENTS_DESC@ +;-------------------------------- +;Installer Sections + +Section "-Core installation" + ;Use the entire tree produced by the INSTALL target. Keep the + ;list of directories here in sync with the RMDir commands below. + SetOutPath "$INSTDIR" + @CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS@ + @CPACK_NSIS_FULL_INSTALL@ + + ;Store installation folder + WriteRegStr SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "" $INSTDIR + + ;Create uninstaller + WriteUninstaller "$INSTDIR\@CPACK_NSIS_UNINSTALL_NAME@.exe" + Push "DisplayName" + Push "@CPACK_NSIS_DISPLAY_NAME@" + Call ConditionalAddToRegistry + Push "DisplayVersion" + Push "@CPACK_PACKAGE_VERSION@" + Call ConditionalAddToRegistry + Push "Publisher" + Push "@CPACK_PACKAGE_VENDOR@" + Call ConditionalAddToRegistry + Push "UninstallString" + Push "$\"$INSTDIR\@CPACK_NSIS_UNINSTALL_NAME@.exe$\"" + Call ConditionalAddToRegistry + Push "NoRepair" + Push "1" + Call ConditionalAddToRegistry + + !ifdef CPACK_NSIS_ADD_REMOVE + ;Create add/remove functionality + Push "ModifyPath" + Push "$INSTDIR\AddRemove.exe" + Call ConditionalAddToRegistry + !else + Push "NoModify" + Push "1" + Call ConditionalAddToRegistry + !endif + + ; Optional registration + Push "DisplayIcon" + Push "$INSTDIR\@CPACK_NSIS_INSTALLED_ICON_NAME@" + Call ConditionalAddToRegistry + Push "HelpLink" + Push "@CPACK_NSIS_HELP_LINK@" + Call ConditionalAddToRegistry + Push "URLInfoAbout" + Push "@CPACK_NSIS_URL_INFO_ABOUT@" + Call ConditionalAddToRegistry + Push "Contact" + Push "@CPACK_NSIS_CONTACT@" + Call ConditionalAddToRegistry + !insertmacro MUI_INSTALLOPTIONS_READ $INSTALL_DESKTOP "NSIS.InstallOptions.ini" "Field 5" "State" + !insertmacro MUI_STARTMENU_WRITE_BEGIN Application + + ;Create shortcuts + CreateDirectory "$SMPROGRAMS\$STARTMENU_FOLDER" +@CPACK_NSIS_CREATE_ICONS@ +@CPACK_NSIS_CREATE_ICONS_EXTRA@ + CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Uninstall.lnk" "$INSTDIR\@CPACK_NSIS_UNINSTALL_NAME@.exe" + + ;Read a value from an InstallOptions INI file + !insertmacro MUI_INSTALLOPTIONS_READ $DO_NOT_ADD_TO_PATH "NSIS.InstallOptions.ini" "Field 2" "State" + !insertmacro MUI_INSTALLOPTIONS_READ $ADD_TO_PATH_ALL_USERS "NSIS.InstallOptions.ini" "Field 3" "State" + !insertmacro MUI_INSTALLOPTIONS_READ $ADD_TO_PATH_CURRENT_USER "NSIS.InstallOptions.ini" "Field 4" "State" + + ; Write special uninstall registry entries + Push "StartMenu" + Push "$STARTMENU_FOLDER" + Call ConditionalAddToRegistry + Push "DoNotAddToPath" + Push "$DO_NOT_ADD_TO_PATH" + Call ConditionalAddToRegistry + Push "AddToPathAllUsers" + Push "$ADD_TO_PATH_ALL_USERS" + Call ConditionalAddToRegistry + Push "AddToPathCurrentUser" + Push "$ADD_TO_PATH_CURRENT_USER" + Call ConditionalAddToRegistry + Push "InstallToDesktop" + Push "$INSTALL_DESKTOP" + Call ConditionalAddToRegistry + + !insertmacro MUI_STARTMENU_WRITE_END + +@CPACK_NSIS_EXTRA_INSTALL_COMMANDS@ + +SectionEnd + +Section "-Add to path" + Push $INSTDIR\bin + StrCmp "@CPACK_NSIS_MODIFY_PATH@" "ON" 0 doNotAddToPath + StrCmp $DO_NOT_ADD_TO_PATH "1" doNotAddToPath 0 + Call AddToPath + doNotAddToPath: +SectionEnd + +;-------------------------------- +; Create custom pages +Function InstallOptionsPage + !insertmacro MUI_HEADER_TEXT "Install Options" "Choose options for installing @CPACK_NSIS_PACKAGE_NAME@" + !insertmacro MUI_INSTALLOPTIONS_DISPLAY "NSIS.InstallOptions.ini" + +FunctionEnd + +;-------------------------------- +; determine admin versus local install +Function un.onInit + + ClearErrors + UserInfo::GetName + IfErrors noLM + Pop $0 + UserInfo::GetAccountType + Pop $1 + StrCmp $1 "Admin" 0 +3 + SetShellVarContext all + ;MessageBox MB_OK 'User "$0" is in the Admin group' + Goto done + StrCmp $1 "Power" 0 +3 + SetShellVarContext all + ;MessageBox MB_OK 'User "$0" is in the Power Users group' + Goto done + + noLM: + ;Get installation folder from registry if available + + done: + +FunctionEnd + +;--- Add/Remove callback functions: --- +!macro SectionList MacroName + ;This macro used to perform operation on multiple sections. + ;List all of your components in following manner here. +@CPACK_NSIS_COMPONENT_SECTION_LIST@ +!macroend + +Section -FinishComponents + ;Removes unselected components and writes component status to registry + !insertmacro SectionList "FinishSection" + +!ifdef CPACK_NSIS_ADD_REMOVE + ; Get the name of the installer executable + System::Call 'kernel32::GetModuleFileNameA(i 0, t .R0, i 1024) i r1' + StrCpy $R3 $R0 + + ; Strip off the last 13 characters, to see if we have AddRemove.exe + StrLen $R1 $R0 + IntOp $R1 $R0 - 13 + StrCpy $R2 $R0 13 $R1 + StrCmp $R2 "AddRemove.exe" addremove_installed + + ; We're not running AddRemove.exe, so install it + CopyFiles $R3 $INSTDIR\AddRemove.exe + + addremove_installed: +!endif +SectionEnd +;--- End of Add/Remove callback functions --- + +;-------------------------------- +; Component dependencies +Function .onSelChange + !insertmacro SectionList MaybeSelectionChanged +FunctionEnd + +;-------------------------------- +;Uninstaller Section + +Section "Uninstall" + ReadRegStr $START_MENU SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "StartMenu" + ;MessageBox MB_OK "Start menu is in: $START_MENU" + ReadRegStr $DO_NOT_ADD_TO_PATH SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "DoNotAddToPath" + ReadRegStr $ADD_TO_PATH_ALL_USERS SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "AddToPathAllUsers" + ReadRegStr $ADD_TO_PATH_CURRENT_USER SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "AddToPathCurrentUser" + ;MessageBox MB_OK "Add to path: $DO_NOT_ADD_TO_PATH all users: $ADD_TO_PATH_ALL_USERS" + ReadRegStr $INSTALL_DESKTOP SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "InstallToDesktop" + ;MessageBox MB_OK "Install to desktop: $INSTALL_DESKTOP " + +@CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS@ + + ;Remove files we installed. + ;Keep the list of directories here in sync with the File commands above. +@CPACK_NSIS_DELETE_FILES@ +@CPACK_NSIS_DELETE_DIRECTORIES@ + +!ifdef CPACK_NSIS_ADD_REMOVE + ;Remove the add/remove program + Delete "$INSTDIR\AddRemove.exe" +!endif + + ;Remove the uninstaller itself. + Delete "$INSTDIR\@CPACK_NSIS_UNINSTALL_NAME@.exe" + DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + + ;Remove the installation directory if it is empty. + RMDir "$INSTDIR" + + ; Remove the registry entries. + DeleteRegKey SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + + ; Removes all optional components + !insertmacro SectionList "RemoveSection_CPack" + + !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP + + Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk" +@CPACK_NSIS_DELETE_ICONS@ +@CPACK_NSIS_DELETE_ICONS_EXTRA@ + + ;Delete empty start menu parent directories + StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP" + + startMenuDeleteLoop: + ClearErrors + RMDir $MUI_TEMP + GetFullPathName $MUI_TEMP "$MUI_TEMP\.." + + IfErrors startMenuDeleteLoopDone + + StrCmp "$MUI_TEMP" "$SMPROGRAMS" startMenuDeleteLoopDone startMenuDeleteLoop + startMenuDeleteLoopDone: + + ; If the user changed the shortcut, then uninstall may not work. This should + ; try to fix it. + StrCpy $MUI_TEMP "$START_MENU" + Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk" +@CPACK_NSIS_DELETE_ICONS_EXTRA@ + + ;Delete empty start menu parent directories + StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP" + + secondStartMenuDeleteLoop: + ClearErrors + RMDir $MUI_TEMP + GetFullPathName $MUI_TEMP "$MUI_TEMP\.." + + IfErrors secondStartMenuDeleteLoopDone + + StrCmp "$MUI_TEMP" "$SMPROGRAMS" secondStartMenuDeleteLoopDone secondStartMenuDeleteLoop + secondStartMenuDeleteLoopDone: + + DeleteRegKey /ifempty SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + + Push $INSTDIR\bin + StrCmp $DO_NOT_ADD_TO_PATH_ "1" doNotRemoveFromPath 0 + Call un.RemoveFromPath + doNotRemoveFromPath: +SectionEnd + +;-------------------------------- +; determine admin versus local install +; Is install for "AllUsers" or "JustMe"? +; Default to "JustMe" - set to "AllUsers" if admin or on Win9x +; This function is used for the very first "custom page" of the installer. +; This custom page does not show up visibly, but it executes prior to the +; first visible page and sets up $INSTDIR properly... +; Choose different default installation folder based on SV_ALLUSERS... +; "Program Files" for AllUsers, "My Documents" for JustMe... + +Function .onInit + StrCmp "@CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL@" "ON" 0 inst + + ReadRegStr $0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "UninstallString" + StrCmp $0 "" inst + + MessageBox MB_YESNOCANCEL|MB_ICONEXCLAMATION \ + "@CPACK_NSIS_PACKAGE_NAME@ is already installed. $\n$\nDo you want to uninstall the old version before installing the new one?" \ + /SD IDYES IDYES uninst IDNO inst + Abort + +;Run the uninstaller +uninst: + ClearErrors + # $0 should _always_ be quoted, however older versions of CMake did not + # do this. We'll conditionally remove the begin/end quotes. + # Remove first char if quote + StrCpy $2 $0 1 0 # copy first char + StrCmp $2 "$\"" 0 +2 # if char is quote + StrCpy $0 $0 "" 1 # remove first char + # Remove last char if quote + StrCpy $2 $0 1 -1 # copy last char + StrCmp $2 "$\"" 0 +2 # if char is quote + StrCpy $0 $0 -1 # remove last char + + StrLen $2 "\@CPACK_NSIS_UNINSTALL_NAME@.exe" + StrCpy $3 $0 -$2 # remove "\@CPACK_NSIS_UNINSTALL_NAME@.exe" from UninstallString to get path + ExecWait '"$0" /S _?=$3' ;Do not copy the uninstaller to a temp file + + IfErrors uninst_failed inst +uninst_failed: + MessageBox MB_OK|MB_ICONSTOP "Uninstall failed." + Abort + + +inst: + ; Reads components status for registry + !insertmacro SectionList "InitSection" + + ; check to see if /D has been used to change + ; the install directory by comparing it to the + ; install directory that is expected to be the + ; default + StrCpy $IS_DEFAULT_INSTALLDIR 0 + StrCmp "$INSTDIR" "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@" 0 +2 + StrCpy $IS_DEFAULT_INSTALLDIR 1 + + StrCpy $SV_ALLUSERS "JustMe" + ; if default install dir then change the default + ; if it is installed for JustMe + StrCmp "$IS_DEFAULT_INSTALLDIR" "1" 0 +2 + StrCpy $INSTDIR "$DOCUMENTS\@CPACK_PACKAGE_INSTALL_DIRECTORY@" + + ClearErrors + UserInfo::GetName + IfErrors noLM + Pop $0 + UserInfo::GetAccountType + Pop $1 + StrCmp $1 "Admin" 0 +4 + SetShellVarContext all + ;MessageBox MB_OK 'User "$0" is in the Admin group' + StrCpy $SV_ALLUSERS "AllUsers" + Goto done + StrCmp $1 "Power" 0 +4 + SetShellVarContext all + ;MessageBox MB_OK 'User "$0" is in the Power Users group' + StrCpy $SV_ALLUSERS "AllUsers" + Goto done + + noLM: + StrCpy $SV_ALLUSERS "AllUsers" + ;Get installation folder from registry if available + + done: + StrCmp $SV_ALLUSERS "AllUsers" 0 +3 + StrCmp "$IS_DEFAULT_INSTALLDIR" "1" 0 +2 + StrCpy $INSTDIR "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@" + + StrCmp "@CPACK_NSIS_MODIFY_PATH@" "ON" 0 noOptionsPage + !insertmacro MUI_INSTALLOPTIONS_EXTRACT "NSIS.InstallOptions.ini" + + noOptionsPage: +FunctionEnd + +Function .onInstSuccess + MessageBox MB_OK "Installed successfully!" + +FunctionEnd \ No newline at end of file diff --git a/cmake/installer-customizations/NSIS/FileLinks.nsh b/cmake/installer-customizations/NSIS/FileLinks.nsh new file mode 100644 index 00000000..9ca72a60 --- /dev/null +++ b/cmake/installer-customizations/NSIS/FileLinks.nsh @@ -0,0 +1,198 @@ +; misc +!define CreateParentFolder "!insertmacro CreateParentFolder" + +!macro CreateParentFolder Path + Push $1 + ${GetParent} "${Path}" $1 + CreateDirectory "$1" + Pop $1 +!macroend + +; info +!define IsLink "!insertmacro IsLink" +!define IsSoftLink "!insertmacro IsSoftLink" +!define IsHardLink "!insertmacro IsHardLink" + +Function IsSoftLink + Exch $0 + ${GetFileAttributes} "$0" "REPARSE_POINT" $0 + + ${If} $0 != "1" + StrCpy $0 "0" + ${EndIf} + Exch $0 +FunctionEnd + +!macro IsSoftLink Path outVar + Push "${Path}" + Call IsSoftLink + Pop ${outVar} +!macroend + +Function IsHardLink + Exch $1 + System::Call "kernel32::CreateFileW(w `$1`, i 0x40000000, i 0, i 0, i 3, i 0, i 0) i .r0" + + ${If} $0 = "-1" + StrCpy $0 "0" + goto is_hard_link_end + ${EndIf} + + System::Call "*(&i256 0) i. r1" + System::Call "kernel32::GetFileInformationByHandle(i r0, i r1) i .s" + System::Call "kernel32::CloseHandle(i r0) i.r0" + Pop $0 + + ${If} $0 == "0" + goto is_hard_link_end + ${EndIf} + + System::Call "*$1(&i40 0, &i4 .r0)" + + ${If} $0 != "0" + IntOp $0 $0 - 1 + ${EndIf} + + is_hard_link_end: + Pop $1 +FunctionEnd + +!macro IsHardLink Path outVar + Push $0 + Push "${Path}" + Call IsHardLink + StrCpy ${outVar} $0 + Pop $0 +!macroend + +!macro IsLink Path outVar + ${IsSoftLink} "${Path}" ${outVar} + + ${If} ${outVar} == 0 + ${IsHardLink} "${Path}" ${outVar} + ${EndIf} +!macroend + +; files +!define CreateHardLink "!insertmacro CreateHardLink" +!define CreateSymbolicLinkFile "!insertmacro CreateSymbolicLinkFile" +!define CreateLinkFile "!insertmacro CreateLinkFile" +!define DeleteLinkFile "!insertmacro DeleteLinkFile" + +!macro CreateSymbolicLinkFile Junction Target outVar + ${CreateParentFolder} "${Junction}" + System::Call "kernel32::CreateSymbolicLinkW(w `${Junction}`, w `${Target}`, i 0) i .s" + Pop ${outVar} + + ${If} ${outVar} == "error" + StrCpy ${outVar} "0" + ${EndIf} +!macroend + +!macro CreateHardLink Junction Target outVar + ${CreateParentFolder} "${Junction}" + System::Call "kernel32::CreateHardLinkW(w `${Junction}`, w `${Target}`, i 0) i .s" + Pop ${outVar} +!macroend + +!macro CreateLinkFile Junction Target outVar + ${CreateSymbolicLinkFile} "${Junction}" "${Target}" ${outVar} + + ${If} ${outVar} == 0 + ${CreateHardLink} "${Junction}" "${Target}" ${outVar} + ${EndIf} +!macroend + +!macro DeleteLinkFile Path outVar + ${IsLink} "${Path}" ${outVar} + + ${If} ${outVar} != 0 + SetFileAttributes "${Path}" "NORMAL" + System::Call "kernel32::DeleteFileW(w `${Path}`) i.s" + Pop ${outVar} + ${EndIf} +!macroend + +; folders +!define CreateJunction "!insertmacro CreateJunction" +!define CreateSymbolicLinkFolder "!insertmacro CreateSymbolicLinkFolder" +!define CreateLinkFolder "!insertmacro CreateLinkFolder" +!define DeleteLinkFolder "!insertmacro DeleteLinkFolder" + +Function CreateJunction + Exch $4 + Exch + Exch $5 + Push $1 + Push $2 + Push $3 + Push $6 + CreateDirectory "$5" + System::Call "kernel32::CreateFileW(w `$5`, i 0x40000000, i 0, i 0, i 3, i 0x02200000, i 0) i .r6" + + ${If} $0 = "-1" + StrCpy $0 "0" + RMDir "$5" + goto create_junction_end + ${EndIf} + + CreateDirectory "$4" ; Windows XP requires that the destination exists + StrCpy $4 "\??\$4" + StrLen $0 $4 + IntOp $0 $0 * 2 + IntOp $1 $0 + 2 + IntOp $2 $1 + 10 + IntOp $3 $1 + 18 + System::Call "*(i 0xA0000003, &i4 $2, &i2 0, &i2 $0, &i2 $1, &i2 0, &w$1 `$4`, &i2 0)i.r2" + System::Call "kernel32::DeviceIoControl(i r6, i 0x900A4, i r2, i r3, i 0, i 0, *i r4r4, i 0) i.r0" + System::Call "kernel32::CloseHandle(i r6) i.r1" + + ${If} $0 == "0" + RMDir "$5" + ${EndIf} + + create_junction_end: + Pop $6 + Pop $3 + Pop $2 + Pop $1 + Pop $5 + Pop $4 +FunctionEnd + +!macro CreateJunction Junction Target outVar + Push $0 + Push "${Junction}" + Push "${Target}" + Call CreateJunction + StrCpy ${outVar} $0 + Pop $0 +!macroend + +!macro CreateSymbolicLinkFolder Junction Target outVar + ${CreateParentFolder} "${Junction}" + System::Call "kernel32::CreateSymbolicLinkW(w `${Junction}`, w `${Target}`, i 1) i .s" + Pop ${outVar} + + ${If} ${outVar} == "error" + StrCpy ${outVar} "0" + ${EndIf} +!macroend + +!macro CreateLinkFolder Junction Target outVar + ${CreateSymbolicLinkFolder} "${Junction}" "${Target}" ${outVar} + + ${If} ${outVar} == 0 + ${CreateJunction} "${Junction}" "${Target}" ${outVar} + ${EndIf} +!macroend + +!macro DeleteLinkFolder Path outVar + ${IsSoftLink} "${Path}" ${outVar} + + ${If} ${outVar} != 0 + SetFileAttributes "${Path}" "NORMAL" + System::Call "kernel32::RemoveDirectoryW(w `${Path}`) i.s" + Pop ${outVar} + ${EndIf} +!macroend \ No newline at end of file diff --git a/cmake/os-features.cmake b/cmake/os-features.cmake new file mode 100644 index 00000000..e6db4569 --- /dev/null +++ b/cmake/os-features.cmake @@ -0,0 +1,207 @@ +## Various and sundry operating system features. +## +## Author: B. Scott Michel +## "scooter me fecit" + +include(CheckSymbolExists) +include(CMakePushCheckState) + +include(pthreads-dep) + +set(NEED_LIBRT FALSE) + +add_library(os_features INTERFACE) + +## Editline support? +find_package(EDITLINE) +if (TARGET Editline::Editline) + target_link_libraries(os_features INTERFACE Editline::Editline) +endif () + +if (WITH_ASYNC) + ## semaphores and sem_timedwait support (OS feature): + check_include_file(semaphore.h semaphore_h_found) + if (semaphore_h_found) + cmake_push_check_state() + + get_property(zz_thread_defs TARGET thread_lib PROPERTY INTERFACE_COMPILE_DEFINITIONS) + get_property(zz_thread_incs TARGET thread_lib PROPERTY INTERFACE_INCLUDE_DIRECTORIES) + get_property(zz_thread_lopts TARGET thread_lib PROPERTY INTERFACE_LINK_OPTIONS) + get_property(zz_thread_libs TARGET thread_lib PROPERTY INTERFACE_LINK_LIBRARIES) + + list(APPEND CMAKE_REQUIRE_DEFINITIONS ${zz_thread_defs}) + list(APPEND CMAKE_REQUIRED_INCLUDES ${zz_thread_incs}) + list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${zz_thread_lopts}) + list(APPEND CMAKE_REQUIRED_LIBRARIES ${zz_thread_libs}) + + check_symbol_exists(sem_timedwait semaphore.h have_sem_timedwait) + + if (NOT have_sem_timedwait) + ## Maybe it's in librt, like shm_open (and more likely, it's not.) + list(APPEND CMAKE_REQUIRED_LIBRARIES rt) + check_symbol_exists(sem_timedwait semaphore.h have_sem_timedwait_rt) + if (have_sem_timedwait_rt) + set(NEED_LIBRT TRUE) + endif (have_sem_timedwait_rt) + endif (NOT have_sem_timedwait) + + cmake_pop_check_state() + + if (have_sem_timedwait OR have_sem_timedwait_rt) + target_compile_definitions(os_features INTERFACE HAVE_SEMAPHORE) + endif () + endif (semaphore_h_found) +endif (WITH_ASYNC) + +## Note: We could use this to enforce better type safety with file I/O. +## +## _LARGEFILE64_SOURCE and _FILE_OFFSET_BITS for Linux +## check_type_size(off_t SIZE_OFF_T) +## if (SIZE_OFF_T) +## target_compile_definitions(os_features INTERFACE SIZE_OFF_T=${SIZE_OFF_T}) +## endif () +## +## check_type_size(off64_t SIZE_OFF64_T) +## if (NOT SIZE_OFF64_T) +## set(xxx_CMAKE_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS}) +## list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE=1) +## check_type_size(off64_t SIZE_OFF64_T) +## set(xxx_CMAKE_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS}) +## +## if (SIZE_OFF64_T) +## target_compile_definitions(os_features INTERFACE _FILE_OFFSET_BITS=64 _LARGEFILE64_SOURCE=1) +## endif () +## endif() +## +## if (SIZE_OFF64_T) +## target_compile_definitions(os_features INTERFACE SIZE_OFF64_T=${SIZE_OFF64_T}) +## endif () + +if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID MATCHES ".*Clang") + target_compile_definitions(os_features INTERFACE _GNU_SOURCE) +endif () + +## +check_include_file(sys/ioctl.h have_sys_ioctl_h) +if (have_sys_ioctl_h) + target_compile_definitions(os_features INTERFACE HAVE_SYS_IOCTL) +endif (have_sys_ioctl_h) + +## +check_include_file(linux/cdrom.h have_linux_cdrom_h) +if (have_linux_cdrom_h) + target_compile_definitions(os_features INTERFACE HAVE_LINUX_CDROM) +endif (have_linux_cdrom_h) + +## +check_include_file(utime.h have_utime_h) +if (have_utime_h) + target_compile_definitions(os_features INTERFACE HAVE_UTIME) +endif (have_utime_h) + +## +check_include_file(glob.h have_glob_h) +if (have_glob_h) + target_compile_definitions(os_features INTERFACE HAVE_GLOB) +else () + ## + check_include_file(fnmatch.h have_fnmatch_h) + if (have_fnmatch_h) + target_compile_definitions(os_features INTERFACE HAVE_FNMATCH) + endif (have_fnmatch_h) +endif (have_glob_h) + +## and shm_open +check_include_file(sys/mman.h have_sys_mman_h) +if (have_sys_mman_h) + cmake_push_check_state() + + check_symbol_exists(shm_open sys/mman.h have_shm_open) + + if (NOT have_shm_open OR NEED_LIBRT) + ## Linux: shm_open is in the rt library? + set(CMAKE_REQUIRED_LIBRARIES rt) + check_symbol_exists(shm_open sys/mman.h have_shm_open_lrt) + endif (NOT have_shm_open OR NEED_LIBRT) + + if (have_shm_open OR have_shm_open_lrt) + target_compile_definitions(os_features INTERFACE HAVE_SHM_OPEN) + endif (have_shm_open OR have_shm_open_lrt) + if (have_shm_open_lrt) + set(NEED_LIBRT TRUE) + endif (have_shm_open_lrt) + + cmake_pop_check_state() +endif (have_sys_mman_h) + +IF (NEED_LIBRT) + target_link_libraries(os_features INTERFACE rt) +ENDIF (NEED_LIBRT) + +check_include_file(dlfcn.h have_dlfcn_h) +if (have_dlfcn_h) + cmake_push_check_state() + + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_DL_LIBS}) + check_symbol_exists(dlopen dlfcn.h have_dlopen) + + if (have_dlopen) + target_link_libraries(os_features INTERFACE ${CMAKE_DL_LIBS}) + + set(dlext ${CMAKE_SHARED_LIBRARY_SUFFIX}) + string(REPLACE "." "" dlext "${dlext}") + target_compile_definitions(os_features INTERFACE SIM_HAVE_DLOPEN=${dlext}) + endif (have_dlopen) + + cmake_pop_check_state() +endif (have_dlfcn_h) + +if (NOT MSVC AND NOT (WIN32 AND CMAKE_C_COMPILER_ID MATCHES ".*Clang")) + # Need the math library on non-Windows platforms + target_link_libraries(os_features INTERFACE m) +endif () + +set(HAVE_TAP_NETWORK False) +set(HAVE_BSDTUNTAP False) + +if (WITH_NETWORK) + ## TAP/TUN devices + if (WITH_TAP) + check_include_file(linux/if_tun.h if_tun_found) + + if (NOT if_tun_found) + check_include_file(net/if_tun.h net_if_tun_found) + if (net_if_tun_found OR EXISTS /Library/Extensions/tap.kext) + set(HAVE_BSDTUNTAP True) + endif (net_if_tun_found OR EXISTS /Library/Extensions/tap.kext) + endif (NOT if_tun_found) + + if (if_tun_found OR net_if_tun_found) + set(HAVE_TAP_NETWORK True) + endif (if_tun_found OR net_if_tun_found) + endif (WITH_TAP) +endif (WITH_NETWORK) + +## Windows: winmm (for ms timer functions), socket functions (even when networking is +## disabled. Also squelch the deprecation warnings (these warnings can be enabled +## via the -DWINAPI_DEPRECATION:Bool=On flag at configure time.) +if (WIN32) + target_link_libraries(os_features INTERFACE ws2_32 wsock32 winmm) + target_compile_definitions(os_features INTERFACE HAVE_WINMM) + if (NOT WINAPI_DEPRECATION) + target_compile_definitions(os_features INTERFACE + _WINSOCK_DEPRECATED_NO_WARNINGS + _CRT_NONSTDC_NO_WARNINGS + _CRT_SECURE_NO_WARNINGS + ) + endif () +endif () + +## Cygwin also wants winmm. Note: Untested but should work. +if (CYGWIN) + check_library_exists(winmm timeGetTime "" HAS_WINMM) + if (HAS_WINMM) + target_link_libraries(os_features INTERFACE ws2_32 wsock32 winmm) + target_compile_definitions(os_features INTERFACE HAVE_WINMM) + endif () +endif () diff --git a/cmake/patches/SDL_ttf/fix-pkgconfig.patch b/cmake/patches/SDL_ttf/fix-pkgconfig.patch new file mode 100644 index 00000000..7b8aa546 --- /dev/null +++ b/cmake/patches/SDL_ttf/fix-pkgconfig.patch @@ -0,0 +1,22 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 4ea903d..35be59d 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -325,7 +325,7 @@ if(SDL2TTF_INSTALL) + COMPONENT devel + ) + +- if(SDL2TTF_BUILD_SHARED_LIBS) ++ if(1) + # Only create a .pc file for a shared SDL2_ttf + set(prefix "${CMAKE_INSTALL_PREFIX}") + set(exec_prefix "\${prefix}") +@@ -353,7 +353,7 @@ if(SDL2TTF_INSTALL) + \"${CMAKE_CURRENT_BINARY_DIR}/SDL2_ttf.pc\") + file(INSTALL DESTINATION \"\${CMAKE_INSTALL_PREFIX}/${PC_DESTDIR}\" + TYPE FILE +- FILES \"${CMAKE_CURRENT_BINARY_DIR}/SDL2_ttf.pc\")" CONFIG Release) ++ FILES \"${CMAKE_CURRENT_BINARY_DIR}/SDL2_ttf.pc\")") + endif() + + if(SDL2TTF_BUILD_SHARED_LIBS AND (APPLE OR (UNIX AND NOT ANDROID))) diff --git a/cmake/patches/zlib/0001-Prevent-invalid-inclusions-when-HAVE_-is-set-to-0.patch b/cmake/patches/zlib/0001-Prevent-invalid-inclusions-when-HAVE_-is-set-to-0.patch new file mode 100644 index 00000000..8fe2b2f5 --- /dev/null +++ b/cmake/patches/zlib/0001-Prevent-invalid-inclusions-when-HAVE_-is-set-to-0.patch @@ -0,0 +1,53 @@ +diff --git a/zconf.h.cmakein b/zconf.h.cmakein +index a7f24cc..a1b359b 100644 +--- a/zconf.h.cmakein ++++ b/zconf.h.cmakein +@@ -434,11 +434,19 @@ typedef uLong FAR uLongf; + #endif + + #ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */ +-# define Z_HAVE_UNISTD_H ++# if ~(~HAVE_UNISTD_H + 0) == 0 && ~(~HAVE_UNISTD_H + 1) == 1 ++# define Z_HAVE_UNISTD_H ++# elif HAVE_UNISTD_H != 0 ++# define Z_HAVE_UNISTD_H ++# endif + #endif + + #ifdef HAVE_STDARG_H /* may be set to #if 1 by ./configure */ +-# define Z_HAVE_STDARG_H ++# if ~(~HAVE_STDARG_H + 0) == 0 && ~(~HAVE_STDARG_H + 1) == 1 ++# define Z_HAVE_STDARG_H ++# elif HAVE_STDARG_H != 0 ++# define Z_HAVE_STDARG_H ++# endif + #endif + + #ifdef STDC +diff --git a/zconf.h.in b/zconf.h.in +index 5e1d68a..32f53c8 100644 +--- a/zconf.h.in ++++ b/zconf.h.in +@@ -432,11 +432,19 @@ typedef uLong FAR uLongf; + #endif + + #ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */ +-# define Z_HAVE_UNISTD_H ++# if ~(~HAVE_UNISTD_H + 0) == 0 && ~(~HAVE_UNISTD_H + 1) == 1 ++# define Z_HAVE_UNISTD_H ++# elif HAVE_UNISTD_H != 0 ++# define Z_HAVE_UNISTD_H ++# endif + #endif + + #ifdef HAVE_STDARG_H /* may be set to #if 1 by ./configure */ +-# define Z_HAVE_STDARG_H ++# if ~(~HAVE_STDARG_H + 0) == 0 && ~(~HAVE_STDARG_H + 1) == 1 ++# define Z_HAVE_STDARG_H ++# elif HAVE_STDARG_H != 0 ++# define Z_HAVE_STDARG_H ++# endif + #endif + + #ifdef STDC + diff --git a/cmake/patches/zlib/0002-skip-building-examples.patch b/cmake/patches/zlib/0002-skip-building-examples.patch new file mode 100644 index 00000000..8183f2ab --- /dev/null +++ b/cmake/patches/zlib/0002-skip-building-examples.patch @@ -0,0 +1,17 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index b412dc7..f46c8e6 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -194,6 +194,7 @@ endif() + # Example binaries + #============================================================================ + ++if (0) + add_executable(example test/example.c) + target_link_libraries(example zlib) + add_test(example example) +@@ -211,3 +212,4 @@ if(HAVE_OFF64_T) + target_link_libraries(minigzip64 zlib) + set_target_properties(minigzip64 PROPERTIES COMPILE_FLAGS "-D_FILE_OFFSET_BITS=64") + endif() ++endif() diff --git a/cmake/patches/zlib/0003-build-static-or-shared-not-both.patch b/cmake/patches/zlib/0003-build-static-or-shared-not-both.patch new file mode 100644 index 00000000..c9f2ecf1 --- /dev/null +++ b/cmake/patches/zlib/0003-build-static-or-shared-not-both.patch @@ -0,0 +1,53 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index f46c8e6..6fa5575 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -121,9 +121,11 @@ set(ZLIB_SRCS + ) + + if(NOT MINGW) ++ if(BUILD_SHARED_LIBS) + set(ZLIB_DLL_SRCS + win32/zlib1.rc # If present will override custom build rule below. + ) ++ endif() + endif() + + # parse the full version number from zlib.h and include in ZLIB_FULL_VERSION +@@ -144,13 +146,16 @@ if(MINGW) + -I ${CMAKE_CURRENT_BINARY_DIR} + -o ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj + -i ${CMAKE_CURRENT_SOURCE_DIR}/win32/zlib1.rc) ++ if(BUILD_SHARED_LIBS) + set(ZLIB_DLL_SRCS ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj) ++ endif() + endif(MINGW) + +-add_library(zlib SHARED ${ZLIB_SRCS} ${ZLIB_DLL_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS}) +-add_library(zlibstatic STATIC ${ZLIB_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS}) ++add_library(zlib ${ZLIB_SRCS} ${ZLIB_ASMS} ${ZLIB_DLL_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS}) ++if (BUILD_SHARED_LIBS) + set_target_properties(zlib PROPERTIES DEFINE_SYMBOL ZLIB_DLL) + set_target_properties(zlib PROPERTIES SOVERSION 1) ++endif() + + if(NOT CYGWIN) + # This property causes shared libraries on Linux to have the full version +@@ -165,7 +170,7 @@ endif() + + if(UNIX) + # On unix-like platforms the library is almost always called libz +- set_target_properties(zlib zlibstatic PROPERTIES OUTPUT_NAME z) ++ set_target_properties(zlib PROPERTIES OUTPUT_NAME z) + if(NOT APPLE) + set_target_properties(zlib PROPERTIES LINK_FLAGS "-Wl,--version-script,\"${CMAKE_CURRENT_SOURCE_DIR}/zlib.map\"") + endif() +@@ -175,7 +180,7 @@ elseif(BUILD_SHARED_LIBS AND WIN32) + endif() + + if(NOT SKIP_INSTALL_LIBRARIES AND NOT SKIP_INSTALL_ALL ) +- install(TARGETS zlib zlibstatic ++ install(TARGETS zlib + RUNTIME DESTINATION "${INSTALL_BIN_DIR}" + ARCHIVE DESTINATION "${INSTALL_LIB_DIR}" + LIBRARY DESTINATION "${INSTALL_LIB_DIR}" ) diff --git a/cmake/patches/zlib/0004-android-and-mingw-fixes.patch b/cmake/patches/zlib/0004-android-and-mingw-fixes.patch new file mode 100644 index 00000000..e93173f2 --- /dev/null +++ b/cmake/patches/zlib/0004-android-and-mingw-fixes.patch @@ -0,0 +1,31 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 6fa5575..7c345db 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -56,7 +56,7 @@ endif() + # + check_include_file(unistd.h Z_HAVE_UNISTD_H) + +-if(MSVC) ++if(WIN32) + set(CMAKE_DEBUG_POSTFIX "d") + add_definitions(-D_CRT_SECURE_NO_DEPRECATE) + add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE) +@@ -133,7 +133,7 @@ file(READ ${CMAKE_CURRENT_SOURCE_DIR}/zlib.h _zlib_h_contents) + string(REGEX REPLACE ".*#define[ \t]+ZLIB_VERSION[ \t]+\"([-0-9A-Za-z.]+)\".*" + "\\1" ZLIB_FULL_VERSION ${_zlib_h_contents}) + +-if(MINGW) ++if(MINGW AND NOT ANDROID) + # This gets us DLL resource information when compiling on MinGW. + if(NOT CMAKE_RC_COMPILER) + set(CMAKE_RC_COMPILER windres.exe) +@@ -149,7 +149,7 @@ if(MINGW) + if(BUILD_SHARED_LIBS) + set(ZLIB_DLL_SRCS ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj) + endif() +-endif(MINGW) ++endif(MINGW AND NOT ANDROID) + + add_library(zlib ${ZLIB_SRCS} ${ZLIB_ASMS} ${ZLIB_DLL_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS}) + if (BUILD_SHARED_LIBS) diff --git a/cmake/platform-quirks.cmake b/cmake/platform-quirks.cmake new file mode 100644 index 00000000..d3dbe598 --- /dev/null +++ b/cmake/platform-quirks.cmake @@ -0,0 +1,266 @@ +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of The Authors shall not be +# used in advertising or otherwise to promote the sale, use or other dealings +# in this Software without prior written authorization from the Authors. + +## platform_quirks.cmake +## +## This is the place where the CMake build handles various platform quirks, +## such as architecture-specific prefixes (Linux, Windows) and MacOS +## HomeBrew +## +## Author: B. Scott Michel +# "scooter me fecit" + + +set(EXTRA_TARGET_CFLAGS) +set(EXTRA_TARGET_CFLAGS) + +# For 64-bit builds (and this is especially true for MSVC), set the library +# architecture. +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + ## Strongly encourage (i.e., force) CMake to look in the x64 architecture + ## directories: + if (MSVC OR MINGW) + # set(CMAKE_C_LIBRARY_ARCHITECTURE "x64") + # set(CMAKE_LIBRARY_ARCHITECTURE "x64") + elseif (${CMAKE_HOST_SYSTEM_NAME} MATCHES "Linux") + ## Linux has architecture-specific subdirectories where CMake needs to + ## search for headers. Currently, we know about x64 and ARM architecture + ## variants. + foreach (arch "x86_64-linux-gnu" "aarch64-linux-gnu" "arm-linux-gnueabihf") + if (EXISTS "/usr/lib/${arch}") + message(STATUS "CMAKE_LIBRARY_ARCHITECTURE set to ${arch}") + set(CMAKE_C_LIBRARY_ARCHITECTURE "${arch}") + set(CMAKE_LIBRARY_ARCHITECTURE "${arch}") + endif() + endforeach() + endif () +endif() + +if (WIN32) + ## At some point, bring this back in to deal with MS ISO C99 deprecation. + ## Right now, it's not in the code base and the warnings are squelched. + ## + ## (keep): if (MSVC_VERSION GREATER_EQUAL 1920) + ## (keep): add_compile_definitions(USE_ISO_C99_NAMES) + ## (keep): endif () + + if (MSVC) + ## Flags enabled in the SIMH VS solution (diff redution): + ## + ## /EHsc: Standard C++ exception handling, extern "C" functions never + ## throw exceptions. + ## /FC: Output full path name of source in diagnostics + ## /GF: String pooling + ## /GL: Whole program optimization + ## /Gy: Enable function-level linking + ## /Oi: Emit intrinsic functions + ## /Ot: Favor fast code + ## /Oy: Suppress generating a stack frame (??? why?) + add_compile_options("$<$:/EHsc;/GF;/Gy;/Oi;/Ot;/Oy;/Zi>") + add_compile_options("$<$:/EHsc;/FC>") + + if (RELEASE_LTO) + ## /LTCG: Link-Time Code Generation. Pair with /GL at compile time. + add_compile_options("$<$:/GL>") + add_link_options("$<$:/LTCG>") + message(STATUS "Adding LTO to Release compiler and linker flags") + endif () + + ## Set the MSVC runtime. Note CMP0091 policy is set to new early on in + ## the top-level CMakeLists.txt + if (BUILD_SHARED_DEPS) + set(use_rtdll "$<$") + else () + set(use_rtdll "") + endif () + + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>${use_rtll}") + + ## Disable automagic add for _MBCS: + add_definitions(-D_SBCS) + + if (CMAKE_VERSION VERSION_LESS "3.23") + ## -5 Evil hack to ensure that find_package() can match against an empty + ## prefix and not trigger the "CMAKE_FIND_LIBRARY_PREFIXES not set" bug. + list(APPEND CMAKE_FIND_LIBRARY_PREFIXES "lib" "|") + endif () + + list(APPEND EXTRA_TARGET_CFLAGS + "$<$:$<$:/W4>>" + "$<$:/W3>" + ) + + ## Uncomment this line if you end up with /NODEFAULTLIB warninigs. You will also + ## need to build with the '--verbose' flag and check the values of "/M*" flags + ## (typically you should see /MT or /MTd for the static runtime libraries.) + ## + # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /verbose:lib") + + if (WARNINGS_FATAL) + message(STATUS "WARNINGS_FATAL: Compiler warnings are errors!! (/WX)") + list(APPEND EXTRA_TARGET_CFLAGS "/WX") + endif () + endif () +elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + # The MSVC solution builds as 32-bit, but none of the *nix platforms do. + # + # If 32-bit compiles have to be added back, uncomment the following 2 lines: + # + # add_compile_options("-m32") + # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -m32") +endif () + + +if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID MATCHES ".*Clang") + # include(fpintrin) + + # Turn on warnings about strict overflow/potential overflows. + ## LIST(APPEND EXTRA_TARGET_CFLAGS "-Wall" "-fno-inline" "-fstrict-overflow" "-Wstrict-overflow=3") + LIST(APPEND EXTRA_TARGET_CFLAGS + "-U__STRICT_ANSI__" + "$<$:$<$:-Wall>>" + ## Only add if WARNINGS_FATAL set; has undesirable consequences with LTO. + "$<$:-Wall>" + ) + + # 07 NOV 2022: Apparently, -O3 is kosher now. + # + # 'O3' optimization and strict overflow cause all kinds of simulator issues, especially inside + # the VAX simulators. Reduce optimization and ensure strict overflow is turned off. + + if (CMAKE_C_COMPILER_ID STREQUAL "GNU") + set(update_o2 TRUE) + if (NOT MINGW) + if (RELEASE_LTO AND (NOT DEFINED CMAKE_BUILD_TYPE OR CMAKE_BUILD_TYPE STREQUAL "Release")) + check_c_compiler_flag("-flto" GCC_LTO_FLAG) + if (GCC_LTO_FLAG) + message(STATUS "Adding LTO to Release compiler and linker flags") + set(lto_flag "$<$:-flto>") + list(APPEND EXTRA_TARGET_CFLAGS "${lto_flag}") + list(APPEND EXTRA_TARGET_LFLAGS "${lto_flag}") + set(update_o2 FALSE) + else () + message(STATUS "Compiler does not support Link Time Optimization.") + endif () + else () + message(STATUS "Link Time Optimization NOT ENABLED.") + endif () + elseif (MINGW) + message(STATUS "MinGW: Link Time Optimization BROKEN, not added to Release flags") + endif () + + if (update_o2) + message(STATUS "Replacing '-O3' with '-O2'") + string(REGEX REPLACE "-O3" "-O2" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") + string(REGEX REPLACE "-O3" "-O2" CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL}") + endif () + + if (WARNINGS_FATAL` OR RELEASE_LTO) + check_c_compiler_flag("-Werror" GCC_W_ERROR_FLAG) + if (GCC_W_ERROR_FLAG) + if (WARNINGS_FATAL) + message(STATUS "WARNINGS_FATAL: Compiler warnings are errors!! (-Werror)") + list(APPEND EXTRA_TARGET_CFLAGS "-Werror") + endif () + if (RELEASE_LTO) + message(STATUS "WARNINGS_FATAL: Link-time optimization warnings are errors!! (-Werror)") + list(APPEND EXTRA_TARGET_LFLAGS "-Werror") + endif () + endif () + endif () + + message(STATUS "Adding GNU-specific optimizations to CMAKE_C_FLAGS_RELEASE") + list(APPEND opt_flags "-finline-functions" "-fgcse-after-reload" "-fpredictive-commoning" + "-fipa-cp-clone" "-fno-unsafe-loop-optimizations" "-fno-strict-overflow") + elseif (CMAKE_C_COMPILER_ID MATCHES ".*Clang") + message(STATUS "Adding Clang-specific optimizations to CMAKE_C_FLAGS_RELEASE") + list(APPEND opt_flags "-fno-strict-overflow") + endif() + + foreach (opt_flag ${opt_flags}) + message(STATUS " ${opt_flag}") + string(REGEX REPLACE "${opt_flag}[ \t\r\n]*" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") + string(APPEND CMAKE_C_FLAGS_RELEASE " ${opt_flag}") + string(REGEX REPLACE "${opt_flag}[ \t\r\n]*" "" CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL}") + string(APPEND CMAKE_C_FLAGS_MINSIZEREL " ${opt_flag}") + endforeach () +else () + message(STATUS "Not changing CMAKE_C_FLAGS_RELEASE on ${CMAKE_C_COMPILER_ID}") +endif () + + +if (CMAKE_HOST_APPLE) + ## Look for app bundles and frameworks after looking for Unix-style packages: + set(CMAKE_FIND_FRAMEWORK "LAST") + set(CMAKE_FIND_APPBUNDLE "LAST") + + if (EXISTS "/usr/local/Cellar" OR EXISTS "/opt/homebrew/Cellar") + ## Smells like HomeBrew. Bulk add the includes and library subdirectories + message(STATUS "Adding HomeBrew paths to library and include search") + set(hb_topdir "/usr/local/Cellar") + if (EXISTS "/opt/homebrew/Cellar") + set(hb_topdir "/opt/homebrew/Cellar") + endif() + + file(GLOB hb_lib_candidates LIST_DIRECTORIES TRUE "${hb_topdir}/*/*/lib") + file(GLOB hb_include_candidates LIST_DIRECTORIES TRUE "${hb_topdir}/*/*/include") + + # message("@@ lib candidates ${hb_lib_candidates}") + # message("@@ inc candidates ${hb_include_candidates}") + + set(hb_libs "") + foreach (hb_path ${hb_lib_candidates}) + if (IS_DIRECTORY "${hb_path}") + # message("@@ consider ${hb_path}") + list(APPEND hb_libs "${hb_path}") + endif() + endforeach() + + set(hb_includes "") + foreach (hb_path ${hb_include_candidates}) + if (IS_DIRECTORY "${hb_path}") + # message("@@ consider ${hb_path}") + list(APPEND hb_includes "${hb_path}") + endif() + endforeach() + + # message("hb_libs ${hb_libs}") + # message("hb_includes ${hb_includes}") + + list(PREPEND CMAKE_LIBRARY_PATH ${hb_libs}) + list(PREPEND CMAKE_INCLUDE_PATH ${hb_includes}) + + unset(hb_lib_candidates) + unset(hb_include_candidates) + unset(hb_includes) + unset(hb_libs) + unset(hb_path) + elseif(EXISTS /opt/local/bin/port) + # MacPorts + list(PREPEND CMAKE_LIBRARY_PATH /opt/local/lib) + list(PREPEND CMAKE_INCLUDE_PATH /opt/local/include) + endif() + + ## Universal binaries? + if (MAC_UNIVERSAL) + set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64") + endif () +endif() diff --git a/cmake/pthreads-dep.cmake b/cmake/pthreads-dep.cmake new file mode 100644 index 00000000..5e5c9981 --- /dev/null +++ b/cmake/pthreads-dep.cmake @@ -0,0 +1,76 @@ +#~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~= +# Manage the pthreads dependency +# +# (a) Try to locate the system's installed pthreads library, which is very +# platform dependent (MSVC -> Pthreads4w, MinGW -> pthreads, *nix -> pthreads.) +# (b) MSVC: Build Pthreads4w as a dependent +#~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~= + +add_library(thread_lib INTERFACE) +set(AIO_FLAGS) + +if (WITH_ASYNC) + include(ExternalProject) + + if (MSVC OR (WIN32 AND CMAKE_C_COMPILER_ID MATCHES ".*Clang")) + # Pthreads4w: pthreads for windows. + if (USING_VCPKG) + find_package(PThreads4W REQUIRED) + target_link_libraries(thread_lib INTERFACE PThreads4W::PThreads4W) + set(THREADING_PKG_STATUS "vcpkg PThreads4W") + else () + find_package(PTW) + + if (PTW_FOUND) + target_compile_definitions(thread_lib INTERFACE PTW32_STATIC_LIB) + target_include_directories(thread_lib INTERFACE ${PTW_INCLUDE_DIRS}) + target_link_libraries(thread_lib INTERFACE ${PTW_C_LIBRARY}) + + set(THREADING_PKG_STATUS "detected PTW/PThreads4W") + else () + ## Would really like to build from the original jwinarske repo, but it + ## ends up installing in ${CMAKE_INSTALL_PREFIX}/> prefix. + ## Which completely breaks how CMake Find*.cmake works. + ## + ## set(PTHREADS4W_URL "https://github.com/jwinarske/pthreads4w") + ## set(PTHREADS4W_URL "https://github.com/bscottm/pthreads4w") + set(PTHREADS4W_URL "https://github.com/bscottm/pthreads4w/archive/refs/tags/version-3.1.0-release.zip") + + ExternalProject_Add(pthreads4w-ext + URL ${PTHREADS4W_URL} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + ) + + BuildDepMatrix(pthreads4w-ext pthreads4w + # CMAKE_ARGS + # -DDIST_ROOT=${SIMH_DEP_TOPDIR} + ) + + list(APPEND SIMH_BUILD_DEPS pthreads4w) + list(APPEND SIMH_DEP_TARGETS pthreads4w-ext) + message(STATUS "Building Pthreads4w from ${PTHREADS4W_URL}") + set(THREADING_PKG_STATUS "pthreads4w source build") + endif () + endif () + else () + # Let CMake determine which threading library ought be used. + set(THREADS_PREFER_PTHREAD_FLAG On) + find_package(Threads) + if (THREADS_FOUND) + target_link_libraries(thread_lib INTERFACE Threads::Threads) + endif (THREADS_FOUND) + + set(THREADING_PKG_STATUS "Platform-detected threading support") + endif () + + if (THREADS_FOUND OR PTW_FOUND OR PThreads4W_FOUND) + set(AIO_FLAGS USE_READER_THREAD SIM_ASYNCH_IO) + else () + set(AIO_FLAGS DONT_USE_READER_THREAD) + endif () +else() + target_compile_definitions(thread_lib INTERFACE DONT_USE_READER_THREAD) + set(THREADING_PKG_STATUS "asynchronous I/O disabled.") +endif() diff --git a/cmake/simgen/basic_simulator.py b/cmake/simgen/basic_simulator.py new file mode 100644 index 00000000..d720cb09 --- /dev/null +++ b/cmake/simgen/basic_simulator.py @@ -0,0 +1,321 @@ +import pprint + +import simgen.parse_makefile as SPM +import simgen.utils as SU +import simgen.packaging as SPKG + +class SIMHBasicSimulator: + """ + """ + def __init__(self, sim_name, dir_macro, test_name, buildrom): + self.sim_name = sim_name + self.dir_macro = dir_macro + self.test_name = test_name + self.int64 = False + self.full64 = False + self.buildrom = buildrom + ## self.has_display -> True if there is a specific display used by the simulator. + self.has_display = False + ## self.uses_video -> True if USE_SIM_VIDEO appears in the simulator's preprocessor defn's. + self.uses_video = False + ## self.besm6_sdl_hack -> Only set/used by the BESM6 simulator. + self.besm6_sdl_hack = False + ## self.uses_aio -> True if the simulator uses AIO + self.uses_aio = False + self.sources = [] + self.defines = [] + self.includes = [] + + def add_source(self, src): + if src not in self.sources: + self.sources.append(src) + + def add_include(self, incl): + if incl not in self.includes: + self.includes.append(incl) + + def add_define(self, define): + if define not in self.defines: + self.defines.append(define) + + def scan_for_flags(self, defs): + """Scan for USE_INT64/USE_ADDR64 in the simulator's defines and set the + 'int64' and 'full64' instance variables. If found, these defines are + removed. Also look for any of the "DISPLAY" make macros, and, if found, + set the 'video' instance variable. + """ + use_int64 = 'USE_INT64' in self.defines + use_addr64 = 'USE_ADDR64' in self.defines + if use_int64 or use_addr64: + self.int64 = use_int64 and not use_addr64 + self.full64 = use_int64 and use_addr64 + for defn in ['USE_INT64', 'USE_ADDR64']: + try: + self.defines.remove(defn) + except: + pass + + ## Video support: + + self.has_display = any(map(lambda s: 'DISPLAY' in SPM.shallow_expand_vars(s, defs), self.sources)) + if self.has_display: + for src in ['${DISPLAYL}', '$(DISPLAYL)']: + try: + self.sources.remove(src) + except: + pass + + self.uses_video = 'USE_SIM_VIDEO' in self.defines or self.has_display + + ## AIO support: + self.uses_aio = 'SIM_ASYNCH_IO' in self.defines + if self.uses_aio: + for defn in ['SIM_ASYNCH_IO', 'USE_READER_THREAD']: + try: + self.defines.remove(defn) + except: + pass + + def cleanup_defines(self): + """Remove command line defines that aren't needed (because the CMake interface libraries + already define them.) + """ + for define in ['USE_SIM_CARD', 'USE_SIM_VIDEO', 'USE_NETWORK', 'USE_SHARED']: + try: + self.defines.remove(define) + except: + pass + + def get_source_vars(self): + srcvars = set() + for src in self.sources: + srcvars = srcvars.union(set(SPM.extract_variables(src))) + return srcvars + + def get_include_vars(self): + incvars = set() + for inc in self.includes: + incvars = incvars.union(set(SPM.extract_variables(inc))) + return incvars + + def write_section(self, stream, section, indent, test_label='default', additional_text=[], + section_name=None, section_srcs=None, section_incs=None): + indent4 = ' ' * (indent + 4) + indent8 = ' ' * (indent + 8) + + pkg_info = SPKG.package_info.get(section_name) + install_flag = pkg_info.install_flag if pkg_info is not None else None + pkg_family = pkg_info.family.component_name if pkg_info is not None else None + + stream.write(' ' * indent + '{}({}\n'.format(section, section_name)) + stream.write(' ' * (indent + 4) + 'SOURCES\n') + stream.write('\n'.join(map(lambda src: indent8 + src, section_srcs))) + if len(self.includes) > 0: + stream.write('\n' + indent4 + 'INCLUDES\n') + stream.write('\n'.join([ indent8 + inc for inc in section_incs])) + if len(self.defines) > 0: + stream.write('\n' + indent4 + 'DEFINES\n') + stream.write('\n'.join(map(lambda dfn: indent8 + dfn, self.defines))) + if self.int64: + stream.write('\n' + indent4 + 'FEATURE_INT64') + if self.full64: + stream.write('\n' + indent4 + 'FEATURE_FULL64') + if self.uses_video: + stream.write('\n' + indent4 + "FEATURE_VIDEO") + if self.has_display: + stream.write('\n' + indent4 + "FEATURE_DISPLAY") + if self.besm6_sdl_hack: + stream.write('\n' + indent4 + "BESM6_SDL_HACK") + if self.uses_aio: + stream.write('\n' + indent4 + "USES_AIO") + if self.buildrom: + stream.write('\n' + indent4 + "BUILDROMS") + stream.write('\n' + indent4 + "LABEL " + test_label) + if install_flag: + if pkg_family: + stream.write('\n' + indent4 + "PKG_FAMILY " + pkg_family) + else: + stream.write('\n' + indent4 + "NO_INSTALL") + stream.write('\n' + '\n'.join(additional_text)) + stream.write(')\n') + + def write_simulator(self, stream, indent, test_label='default'): + ## When writing an individual CMakeList.txt, we can take advantage of the CMAKE_CURRENT_SOURCE_DIR + ## as a replacement for the SIMH directory macro. + srcs = [ src.replace(self.dir_macro + '/', '') for src in self.sources] + incs = [ inc if inc != self.dir_macro else '${CMAKE_CURRENT_SOURCE_DIR}' for inc in self.includes] + + indent4 = ' ' * (indent + 4) + + addl_text = [ + indent4 + "TEST " + self.test_name, + ] + + self.write_section(stream, 'add_simulator', indent, test_label, additional_text=addl_text, + section_name=self.sim_name, section_srcs=srcs, section_incs=incs) + + # Default: Don't generate a unit test CMakeFiles.txt. Yet. + def write_unit_test(self, stream, indent, test_label='default'): + pass + + def __repr__(self): + return '{0}({1},{2},{3},{4})'.format(self.__class__.__name__, self.sim_name.__repr__(), + self.sources.__repr__(), self.includes.__repr__(), self.defines.__repr__()) + + +class BESM6Simulator(SIMHBasicSimulator): + """The (fine Communist) BESM6 simulator needs some extra code + in the CMakeLists.txt to detect a suitable font that supports + Cyrillic. + """ + def __init__(self, sim_name, dir_macro, test_name, buildrom): + super().__init__(sim_name, dir_macro, test_name, buildrom) + + def scan_for_flags(self, defs): + super().scan_for_flags(defs) + + def write_simulator(self, stream, indent, test_label='besm6'): + ## Fixups... :-) + for macro in ['FONTFILE=$(FONTFILE)', 'FONTFILE=${FONTFILE}']: + try: + self.defines.remove(macro) + except: + pass + + ## Add the search for a font file. + stream.write('\n'.join([ + 'set(besm6_font)', + 'set(cand_fonts', + ' "DejaVuSans.ttf"', + ' "LucidaSansRegular.ttf"', + ' "FreeSans.ttf"', + ' "AppleGothic.ttf"', + ' "tahoma.ttf")', + 'set(cand_fontdirs', + ' "/usr/share/fonts"', + ' "/usr/lib/jvm"', + ' "/Library/Fonts"', + ' "/System/Library/Fonts"', + ' "/System/Library/Frameworks/JavaVM.framework/Versions"', + ' "$ENV{WINDIR}/Fonts")', + '', + 'foreach (fdir ${cand_fontdirs})', + ' foreach (font ${cand_fonts})', + ' if (EXISTS ${fdir}/${font})', + ' get_filename_component(fontfile ${fdir}/${font} ABSOLUTE)', + ' list(APPEND besm6_font ${fontfile})', + ' endif ()', + '', + ' file(GLOB besm6_font_cand_1 LIST_DIRECTORIES FALSE "${fdir}/*/${font}")', + ' file(GLOB besm6_font_cand_2 LIST_DIRECTORIES FALSE "${fdir}/*/*/${font}")', + ' file(GLOB besm6_font_cand_3 LIST_DIRECTORIES FALSE "${fdir}/*/*/*/${font}")', + ' list(APPEND besm6_font ${besm6_font_cand_1} ${besm6_font_cand_2} ${besm6_font_cand_3})', + ' endforeach()', + 'endforeach()', + '', + 'if (besm6_font)', + ' set(besm6_found_fonts "BESM6: Fonts found")', + ' foreach(bfont ${besm6_font})', + ' string(APPEND besm6_found_fonts "\n .. ${bfont}")', + ' endforeach ()', + ' message(STATUS ${besm6_found_fonts})', + ' unset(besm6_found_fonts)', + ' list(GET besm6_font 0 besm6_font)', + ' message(STATUS "BESM6: Using ${besm6_font}")', + 'else ()', + ' set(besm6_no_fonts "BESM6: No applicable Cyrillic fonts found.")', + ' string(APPEND besm6_no_fonts "\n Font names tried:")', + ' foreach (font ${cand_fonts})', + ' string(APPEND besm6_no_fonts "\n .. ${font}")', + ' endforeach ()', + ' string(APPEND besm6_no_fonts "\n\n Looked in:")', + ' foreach (fdir ${cand_fontdirs})', + ' string(APPEND besm6_no_fonts "\n .. ${fdir}")', + ' endforeach()', + ' string(APPEND besm6_no_fonts "\n\nBESM6: Not building with panel display.")', + ' message(STATUS ${besm6_no_fonts})', + ' unset(besm6_no_fonts)', + 'endif ()', + '', + 'if (NOT (besm6_font AND WITH_VIDEO))\n'])) + super().write_simulator(stream, indent + 4, test_label) + stream.write('else ()\n') + self.defines.append("FONTFILE=${besm6_font}") + self.has_display = True + self.uses_video = True + self.besm6_sdl_hack = True + super().write_simulator(stream, indent + 4, test_label) + stream.write('\n'.join([ + 'endif()', + 'unset(cand_fonts)', + 'unset(cand_fontdirs)\n'])) + +class IBM650Simulator(SIMHBasicSimulator): + '''The IBM650 simulator creates relatively deep stacks, which will fail on Windows. + Adjust target simulator link flags to provide a 8M stack, similar to Linux. + ''' + def __init__(self, sim_name, dir_macro, test_name, buildrom): + super().__init__(sim_name, dir_macro, test_name, buildrom) + self.stack_size = 8 * 1024 * 1024 + + def write_simulator(self, stream, indent, test_label='ibm650'): + super().write_simulator(stream, indent, test_label) + stream.write('\n') + ## Link i650 with a 8M stack on windows + stream.write('\n'.join([ + 'if (WIN32)', + ' if (MSVC)', + ' set(I650_STACK_FLAG "/STACK:{0}")'.format(self.stack_size), + ' else ()', + ' set(I650_STACK_FLAG "-Wl,--stack,{0}")'.format(self.stack_size), + ' endif ()', + ' if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.13")', + ' target_link_options({0} PUBLIC "${{I650_STACK_FLAG}}")'.format(self.sim_name), + ' else ()', + ' set_property(TARGET {0} LINK_FLAGS " ${{I650_STACK_FLAG}}")'.format(self.sim_name), + ' endif ()', + 'endif()' + ])) + +class IBM1130Simulator(SIMHBasicSimulator): + '''The IBM650 simulator creates relatively deep stacks, which will fail on Windows. + Adjust target simulator link flags to provide a 8M stack, similar to Linux. + ''' + def __init__(self, sim_name, dir_macro, test_name, buildrom): + super().__init__(sim_name, dir_macro, test_name, buildrom) + + def write_simulator(self, stream, indent, test_label='ibm650'): + super().write_simulator(stream, indent, test_label) + stream.write('\n'.join([ + '', + 'if (WIN32)', + ' target_compile_definitions(ibm1130 PRIVATE GUI_SUPPORT)', + ' ## missing source in IBM1130?' + ' ## target_sources(ibm1130 PRIVATE ibm1130.c)', + 'endif()' + ])) + + +if '_dispatch' in pprint.PrettyPrinter.__dict__: + def sim_pprinter(pprinter, sim, stream, indent, allowance, context, level): + cls = sim.__class__ + stream.write(cls.__name__ + '(') + indent += len(cls.__name__) + 1 + pprinter._format(sim.sim_name, stream, indent, allowance + 2, context, level) + stream.write(',') + pprinter._format(sim.dir_macro, stream, indent, allowance + 2, context, level) + stream.write(',') + pprinter._format(sim.int64, stream, indent, allowance + 2, context, level) + stream.write(',') + pprinter._format(sim.full64, stream, indent, allowance + 2, context, level) + stream.write(',') + pprinter._format(sim.has_display, stream, indent, allowance + 2, context, level) + stream.write(',') + pprinter._format(sim.sources, stream, indent, allowance + 2, context, level) + stream.write(',\n' + ' ' * indent) + pprinter._format(sim.defines, stream, indent, allowance + 2, context, level) + stream.write(',\n' + ' ' * indent) + pprinter._format(sim.includes, stream, indent, allowance + 2, context, level) + stream.write(')') + + pprint.PrettyPrinter._dispatch[SIMHBasicSimulator.__repr__] = sim_pprinter diff --git a/cmake/simgen/cmake_container.py b/cmake/simgen/cmake_container.py new file mode 100644 index 00000000..07d3439f --- /dev/null +++ b/cmake/simgen/cmake_container.py @@ -0,0 +1,411 @@ +## cmake_container.py +## +## A container for a collection of SIMH simulators +## +## + +import sys +import os +import re +import pprint +import functools + +import simgen.parse_makefile as SPM +import simgen.sim_collection as SC +import simgen.utils as SU + +## Corresponding special variable uses in the makefile: +_special_sources = frozenset(['${DISPLAYL}', '$(DISPLAYL)']) + +## Banner header for individual CMakeLists.txt files: +_individual_header = [ + '##', + '## This is an automagically generated file. Do NOT EDIT.', + '## Any changes you make will be overwritten!!', + '##', + '## Make changes to the SIMH top-level makefile and then run the', + '## "cmake/generate.py" script to regenerate these files.', + '##', + '## cd cmake; python -m generate --help', + '##', + '## ' + '-' * 60 + '\n' + ] + +## Banner header for individual unit test CMakeLists.txt files: +_unit_test_header = [ + '##', + '## This is an automagically generated file. Do NOT EDIT.', + '## Any changes you make will be overwritten!!', + '##', + '## If you need to make changes, modify write_unit_test()', + '## method in the the underlying Python class in ', + '## cmake/simgen/basic_simulator.py, then execute the', + '## "cmake/generate.py" script to regenerate these files.', + '##', + '## cd cmake; python -m generate --help', + '##', + '## ' + '-' * 60 + '\n' + ] +## Unset the display variables when not building with video. A +10 +## kludge. +_unset_display_vars = ''' +if (NOT WITH_VIDEO) + ### Hack: Unset these variables so that they don't expand if + ### not building with video: + set(DISPLAY340 "") + set(DISPLAYIII "") + set(DISPLAYNG "") + set(DISPLAYVT "") +endif () +''' + +class CMakeBuildSystem: + """A container for collections of SIMH simulators and automagic + CMakeLists.txt driver. + + This is the top-level container that stores collections of SIMH simulators + in the 'SIM' + """ + + ## Compile command line elements that are ignored. + _ignore_compile_elems = frozenset(['${LDFLAGS}', '$(LDFLAGS)', + '${CC_OUTSPEC}', '$(CC_OUTSPEC)', + '${SIM}', '$(SIM)', + '-o', '$@', + '${NETWORK_OPT}', '$(NETWORK_OPT)', + '${SCSI}', '$(SCSI)', + '${DISPLAY_OPT}', '$(DISPLAY_OPT)', + '${VIDEO_CCDEFS}', '$(VIDEO_CCDEFS)', + '${VIDEO_LDFLAGS}', '$(VIDEO_LDFLAGS)', + '${BESM6_PANEL_OPT}', '$(BESM6_PANEL_OPT)']) + + def __init__(self): + # "Special" variables that we look for in source code lists and which we'll + # emit into the CMakeLists.txt files. + self.vars = SC.ignored_display_macros.copy() + # Subdirectory -> SimCollection mapping + self.dirs = {} + + + def extract(self, compile_action, test_name, sim_dir, sim_name, defs, buildrom, debug=0, depth=''): + """Extract sources, defines, includes and flags from the simulator's + compile action in the makefile. + """ + sim_dir_path = SPM.expand_vars(sim_dir, defs).replace('./', '') + simcoll = self.dirs.get(sim_dir_path) + if simcoll is None: + simcoll = SC.SimCollection(sim_dir) + self.dirs[sim_dir_path] = simcoll + + sim = simcoll.get_simulator(sim_name, sim_dir, sim_dir_path, test_name, buildrom) + + # Remove compile command line elements and do one level of variable expansion, split the resulting + # string into a list. + all_comps = [] + for comp in [ SPM.normalize_variables(act) for act in compile_action.split() if not self.compile_elems_to_ignore(act) ]: + all_comps.extend(comp.split()) + + if debug >= 3: + print('{0}all_comps after filtering:'.format(depth)) + pprint.pp(all_comps) + + # Iterate through the final compile component list and extract source files, includes + # and defines. + # + # Deferred: Looking for options that set the i64, z64, video library options. + while all_comps: + comp = all_comps[0] + # print(':: comp {0}'.format(comp)) + if self.is_source(comp): + # Source file... + ## sim.add_source(comp.replace(sim_dir + '/', '')) + sim.add_source(comp) + elif comp.startswith('-I'): + all_comps = self.process_flag(all_comps, defs, sim.add_include, depth) + elif comp.startswith('-D'): + all_comps = self.process_flag(all_comps, defs, sim.add_define, depth) + elif comp.startswith('-L') or comp.startswith('-l'): + ## It's a library path or library. Skip. + pass + else: + # Solitary variable expansion? + m = SPM._var_rx.match(comp) + if m: + varname = m.group(1) + if varname not in SC._special_vars: + if not self.is_source_macro(comp, defs): + expand = [ SPM.normalize_variables(elem) for elem in SPM.shallow_expand_vars(comp, defs).split() + if not self.source_elems_to_ignore(elem) ] + SU.emit_debug(debug, 3, '{0}var expanded {1} -> {2}'.format(depth, comp, expand)) + all_comps[1:] = expand + all_comps[1:] + else: + ## Source macro + self.collect_source_macros(comp, defs, varname, simcoll, sim, debug, depth) + else: + sim.add_source(comp) + else: + # Nope. + print('{0}unknown component: {1}'.format(depth, comp)) + all_comps = all_comps[1:] + + sim.scan_for_flags(defs) + sim.cleanup_defines() + + if debug >= 2: + pprint.pprint(sim) + + + def compile_elems_to_ignore(self, elem): + return (elem in self._ignore_compile_elems or elem.endswith('_LDFLAGS')) + + def source_elems_to_ignore(self, elem): + return self.compile_elems_to_ignore(elem) or elem in _special_sources + + + def is_source_macro(self, var, defs): + """Is the macro/variable a list of sources? + """ + expanded = SPM.expand_vars(var, defs).split() + # print('is_source_macro {}'.format(expanded)) + return all(map(lambda src: self.is_source(src), expanded)) + + + def is_source(self, thing): + return thing.endswith('.c') + + + def process_flag(self, comps, defs, process_func, depth): + if len(comps[0]) > 2: + # "-Ddef" + val = comps[0][2:] + else: + # "-D def" + val = comps[1] + comps = comps[1:] + m = SPM._var_rx.match(val) + if m: + var = m.group(1) + ## Gracefully deal with undefined variables (ATT3B2M400B2D is a good example) + if var in defs: + if var not in self.vars: + self.vars[var] = defs[var] + else: + print('{0}undefined make macro: {1}'.format(depth, var)) + + process_func(val) + return comps + + + def collect_vars(self, defs, debug=0): + """Add indirectly referenced macros and variables, adding them to the defines dictionary. + + Indirectly referenced macros and variables are macros and variables embedded in existing + variables, source macros and include lists. For example, SIMHD is an indirect reference + in "KA10D = ${SIMHD}/ka10" because KA10D might never have been expanded by 'extract()'. + """ + + def scan_var(varset, var): + tmp = var + return varset.union(set(SPM.extract_variables(tmp))) + + def replace_simhd(l, v): + l.append(v.replace('SIMHD', 'CMAKE_SOURCE_DIR')) + return l + + simvars = set() + for v in self.vars.values(): + if isinstance(v, list): + simvars = functools.reduce(scan_var, v, simvars) + else: + simvars = scan_var(simvars, v) + + for dir in self.dirs.keys(): + simvars = simvars.union(self.dirs[dir].get_simulator_vars(debug)) + + if debug >= 2: + print('Collected simvars:') + pprint.pprint(simvars) + + for var in simvars: + if var not in self.vars: + if var in defs: + self.vars[var] = defs[var] + else: + print('{0}: variable not defined.'.format(var)) + + ## Replace SIMHD with CMAKE_SOURCE_DIR + for k, v in self.vars.items(): + if isinstance(v, list): + v = functools.reduce(replace_simhd, v, []) + else: + v = v.replace('SIMHD', 'CMAKE_SOURCE_DIR') + self.vars[k] = v + + def collect_source_macros(self, comp, defs, varname, simcoll, sim, debug=0, depth=''): + def inner_sources(srcmacro): + for v in srcmacro: + if not self.source_elems_to_ignore(v): + m = SPM._var_rx.match(v) + if m is not None: + vname = m.group(1) + vardef = defs.get(vname) + if vardef is not None: + ## Convert the macro variable into a list + vardef = [ SPM.normalize_variables(v) \ + for v in vardef.split() if not self.source_elems_to_ignore(v) ] + SU.emit_debug(debug, 3, '{0}source macro: {1} -> {2}'.format(depth, vname, vardef)) + simcoll.add_source_macro(vname, vardef, sim) + ## Continue into the macro variable's definitions + inner_sources(vardef) + + vardef = defs.get(varname) + if vardef is not None: + vardef = [ SPM.normalize_variables(v) \ + for v in vardef.split() if not self.source_elems_to_ignore(v) ] + SU.emit_debug(debug, 3, '{0}source macro: {1} -> {2}'.format(depth, varname, vardef)) + simcoll.add_source_macro(varname, vardef, sim) + inner_sources(vardef) + SU.emit_debug(debug, 3, '{0}source added: {1}'.format(depth, comp)) + sim.add_source(comp) + else: + print('{0}undefined make macro: {1}'.format(depth, varname)) + + def write_vars(self, stream): + def collect_vars(varlist, var): + varlist.extend(SPM.extract_variables(var)) + return varlist + + varnames = list(self.vars.keys()) + namewidth = max(map(lambda s: len(s), varnames)) + # vardeps maps the parent variable to its dependents, e.g., + # INTELSYSD -> [ISYS8010D, ...] + vardeps = dict() + # alldeps is the set of all parent and dependents, which will be + # deleted from a copy of self.vars. The copy has variables that + # don't depend on anything (except for CMAKE_SOURCE_DIR, but we + # know that's defined by CMake.) + alldeps = set() + for var in varnames: + if isinstance(self.vars[var], list): + mvars = functools.reduce(collect_vars, self.vars[var], []) + else: + mvars = SPM.extract_variables(self.vars[var]) + mvars = [mvar for mvar in mvars if mvar != "CMAKE_SOURCE_DIR"] + if mvars: + alldeps.add(var) + for mvar in mvars: + if mvar not in vardeps: + vardeps[mvar] = [] + vardeps[mvar].append(var) + alldeps.add(mvar) + + nodeps = self.vars.copy() + ## SIMHD will never be used. + if 'SIMHD' in nodeps: + del nodeps['SIMHD'] + for dep in alldeps: + del nodeps[dep] + + varnames = list(nodeps.keys()) + varnames.sort() + for var in varnames: + self.emit_value(var, self.vars[var], stream, namewidth) + + ## Now to emit the dependencies + depnames = list(vardeps.keys()) + depnames.sort() + for dep in depnames: + self.write_dep(dep, vardeps, alldeps, namewidth, stream) + + ## stream.write('\n## ' + '-' * 40 + '\n') + + def write_dep(self, dep, vardeps, alldeps, width, stream): + # Not the most efficient, but it works + alldeps.discard(dep) + for parent in [ v for v in vardeps.keys() if dep in vardeps[v]]: + if dep in parent.values(): + self.write_dep(parent, vardeps, alldeps, width, stream) + + stream.write('\n') + self.emit_value(dep, self.vars[dep], stream, width) + + children = vardeps[dep] + children.sort() + for child in children: + if child in alldeps: + self.emit_value(child, self.vars[child], stream, width) + alldeps -= set(children) + + def emit_value(self, var, value, stream, width=0): + if isinstance(value, list): + stream.write('set({:{width}} {})\n'.format(var, ' '.join(map(lambda s: '"' + s + '"', value)), + width=width)) + else: + stream.write('set({:{width}} "{}")\n'.format(var, value, width=width)) + + def write_simulators(self, toplevel_dir, debug=0): + dirnames = list(self.dirs.keys()) + dirnames.sort() + + for subdir in dirnames: + simcoll = self.dirs[subdir] + test_label = subdir + # Group tests under subdirectories together + has_slash = test_label.find('/') + if has_slash < 0: + has_slash = test_label.find('\\') + if has_slash >= 0: + test_label = test_label[:has_slash] + ## Write out individual CMakeLists.txt: + subdir_cmake = os.path.join(toplevel_dir, subdir, 'CMakeLists.txt') + print('==== writing to {0}'.format(subdir_cmake)) + with open(subdir_cmake, "w", newline='\r\n') as stream2: + plural = '' if len(self.dirs[subdir]) == 1 else 's' + stream2.write('## {} simulator{plural}\n'.format(subdir, plural=plural)) + stream2.write('\n'.join(_individual_header)) + stream2.write('\n') + stream2.write('if (HAVE_UNITY_FRAMEWORK AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/unit-tests/CMakeLists.txt")\n') + stream2.write(' add_subdirectory(unit-tests)\n') + stream2.write('endif ()') + stream2.write('\n') + simcoll.write_simulators(stream2, debug=debug, test_label=test_label) + + ## Write out unit tests, if the unit test subdirectory exists: + subdir_units_dir = os.path.join(toplevel_dir, subdir, 'unit-tests') + subdir_units = os.path.join(subdir_units_dir, 'CMakeLists.txt') + if os.path.exists(subdir_units): + with open(subdir_units, "w") as stream2: + plural = '' if len(self.dirs[subdir]) == 1 else 's' + stream2.write('## {} simulator{plural}\n'.format(subdir, plural=plural)) + stream2.write('\n'.join(_unit_test_header)) + stream2.write('\n') + simcoll.write_unit_tests(stream2, debug, subdir) + + simh_subdirs = os.path.join(toplevel_dir, 'cmake', 'simh-simulators.cmake') + print("==== writing {0}".format(simh_subdirs)) + with open(simh_subdirs, "w") as stream2: + stream2.write('\n'.join(_individual_header)) + self.write_vars(stream2) + stream2.write('\n## ' + '-' * 40 + '\n') + stream2.write(_unset_display_vars) + stream2.write('\n## ' + '-' * 40 + '\n\n') + stmts = [ 'add_subdirectory(' + dir + ')' for dir in dirnames ] + stream2.write('\n'.join(stmts)) + stream2.write('\n') + + ## Representation when printed + def __repr__(self): + return '{0}({1}, {2})'.format(self.__class__.__name__, self.dirs.__repr__(), self.vars.__repr__()) + + +if '_dispatch' in pprint.PrettyPrinter.__dict__: + def cmake_pprinter(pprinter, cmake, stream, indent, allowance, context, level): + cls = cmake.__class__ + stream.write(cls.__name__ + '(') + indent += len(cls.__name__) + 1 + pprinter._format(cmake.dirs, stream, indent, allowance + 2, context, level) + stream.write(',\n' + ' ' * indent) + pprinter._format(cmake.vars, stream, indent, allowance + 2, context, level) + stream.write(')') + + pprint.PrettyPrinter._dispatch[CMakeBuildSystem.__repr__] = cmake_pprinter diff --git a/cmake/simgen/packaging.py b/cmake/simgen/packaging.py new file mode 100644 index 00000000..76950dd8 --- /dev/null +++ b/cmake/simgen/packaging.py @@ -0,0 +1,115 @@ +import os +import functools + +## Initialize package_info to an empty dictionary here so +## that it's visible to write_packaging(). +package_info = {} + + +class SIMHPackaging: + def __init__(self, family, install_flag = True) -> None: + self.family = family + self.processed = False + self.install_flag = install_flag + + def was_processed(self) -> bool: + return self.processed == True + + def encountered(self) -> None: + self.processed = True + +class PkgFamily: + def __init__(self, component_name, display_name, description) -> None: + self.component_name = component_name + self.display_name = display_name + self.description = description + + def write_component_info(self, stream, indent) -> None: + pkg_description = self.description + if pkg_description[-1] != '.': + pkg_description += '.' + sims = [] + for sim, pkg in package_info.items(): + if pkg.family is self and pkg.was_processed(): + sims.append(sim) + + if len(sims) > 0: + sims.sort() + pkg_description += " Simulators: " + ', '.join(sims) + indent0 = ' ' * indent + indent4 = ' ' * (indent + 4) + stream.write(indent0 + "cpack_add_component(" + self.component_name + "\n") + stream.write(indent4 + "DISPLAY_NAME \"" + self.display_name + "\"\n") + stream.write(indent4 + "DESCRIPTION \"" + pkg_description + "\"\n") + stream.write(indent0 + ")\n") + + def __lt__(self, obj): + return self.component_name < obj.component_name + def __eq__(self, obj): + return self.component_name == obj.component_name + def __gt__(self, obj): + return self.component_name > obj.component_name + def __hash__(self): + return hash(self.component_name) + +def write_packaging(toplevel_dir) -> None: + families = set([sim.family for sim in package_info.values()]) + pkging_file = os.path.join(toplevel_dir, 'cmake', 'simh-packaging.cmake') + print("==== writing {0}".format(pkging_file)) + with open(pkging_file, "w") as stream: + ## Runtime support family: + stream.write("""## The default runtime support component/family: +cpack_add_component(runtime_support + DISPLAY_NAME "Runtime support" + DESCRIPTION "Required SIMH runtime support (documentation, shared libraries)" + REQUIRED +) + +## Basic documentation for SIMH +install(FILES doc/simh.doc TYPE DOC COMPONENT runtime_support) + +""") + + ## Simulators: + for family in sorted(families): + family.write_component_info(stream, 0) + + +default_family = PkgFamily("default_family", "Default SIMH simulator family.", + """The SIMH simulator collection of historical processors and computing systems that do not belong to +any other simulated system family""" +) + +pdp10_family = PkgFamily("pdp10_family", "DEC PDP-10 collection", + """DEC PDP-10 architecture simulators and variants.""" +) + +b5500_family = PkgFamily("b5500_family", "Burroughs 5500", + """The Burroughs 5500 system simulator""") + +ibm_family = PkgFamily("ibm_family", "IBM", + """IBM system simulators""" +) + +ict_family = PkgFamily("ict_family", "ICT", + """International Computers and Tabulators simulators""") + +gould_family = PkgFamily("gould_family", "Gould simulators", + """Gould Systems simulators""" +) + +package_info["b5500"] = SIMHPackaging(b5500_family) +package_info["i701"] = SIMHPackaging(ibm_family) +package_info["i7010"] = SIMHPackaging(ibm_family) +package_info["i704"] = SIMHPackaging(ibm_family) +package_info["i7070"] = SIMHPackaging(ibm_family) +package_info["i7080"] = SIMHPackaging(ibm_family) +package_info["i7090"] = SIMHPackaging(ibm_family) +package_info["ibm360"] = SIMHPackaging(ibm_family) +package_info["icl1900"] = SIMHPackaging(ict_family) +package_info["pdp10-ka"] = SIMHPackaging(pdp10_family) +package_info["pdp10-ki"] = SIMHPackaging(pdp10_family) +package_info["pdp10-kl"] = SIMHPackaging(pdp10_family) +package_info["pdp10-ks"] = SIMHPackaging(pdp10_family) +package_info["pdp6"] = SIMHPackaging(pdp10_family) +package_info["sel32"] = SIMHPackaging(gould_family) diff --git a/cmake/simgen/parse_makefile.py b/cmake/simgen/parse_makefile.py new file mode 100644 index 00000000..c935f120 --- /dev/null +++ b/cmake/simgen/parse_makefile.py @@ -0,0 +1,194 @@ +"""Makefile parsing and variable expansion. + +Read and collect variable, rule and action information from a [Mm]akefile. +This isn't a precise collection; for example, it does not respect GNU Makefile +directives such as 'ifeq' and 'ifneq'. +""" + +import re + +# Regexes needed for parsing Makefile (and similar syntaxes, +# like old-style Setup files). +_variable_rx = re.compile(r"\s*([A-Za-z][\w_-]+)\s*=\s*(.*)") +_rule_rx = re.compile(r"(((\$[({])*\w[\w_-]+[)}]*)+)\s*:\s*(.*)") + +# Regex that recognizes variables. Group 1 is the variable's name. +_var_pattern = r"[A-Za-z][\w_-]*" +_var_rx = re.compile(r"^\$[{(](" + _var_pattern + r")[)}]$") +_var_rx2 = re.compile(r"\$[{(](" + _var_pattern + r")[)}]") +_norm_var_rx = re.compile(r"\$[(](" + _var_pattern + r")[)]") + +def parse_makefile(fn, g_vars=None, g_rules=None, g_actions=None): + """Parse a Makefile-style file. + + Collects all of the variable definitions, rules and actions associated with rules. + + """ + ## Python 3.11 and onward dropped distuitls, so import our local copy. + from simgen.text_file import TextFile + + fp = TextFile(fn, strip_comments=1, skip_blanks=1, join_lines=1, errors="surrogateescape") + + if g_vars is None: + g_vars = {} + if g_rules is None: + g_rules = {} + if g_actions is None: + g_actions = {} + done = {} + rules = {} + actions = {} + + line = fp.readline() + while line is not None: + vmatch = _variable_rx.match(line) + rmatch = _rule_rx.match(line) + if vmatch: + n, v = vmatch.group(1, 2) + v = v.strip() + + try: + v = int(v) + except ValueError: + # insert literal `$' + done[n] = v.replace('$$', '$') + else: + done[n] = v + + line = fp.readline() + elif rmatch: + ## make allows "$(VAR)" and "${VAR}" to be used interchangably, so there's a + ## possibility that the sim developer used both forms in the target, e.g.: + ## + ## foosim: $(BIN)foosim$(EXE) + ## + ## ${BIN}foosim${EXE}: stuff that foosim depends on... + + n, v = rmatch.group(1, 4) + rules[normalize_variables(n)] = normalize_variables(v) + + ## Collect the actions: + collected = [] + line = fp.readline() + while line is not None: + m = _variable_rx.match(line) or _rule_rx.match(line) + if m is None: + collected.append(line.lstrip()) + line = fp.readline() + else: + break + actions[n] = collected + else: + line = fp.readline() + + fp.close() + + # strip spurious spaces + for k, v in done.items(): + if isinstance(v, str): + done[k] = v.strip().replace('\t', ' ') + + # save the results in the global dictionary + g_vars.update(done) + g_rules.update(rules) + g_actions.update(actions) + return (g_vars, g_rules, g_actions) + + +def target_dep_list(target, rules, defs): + return (rules.get(target) or '').split() + +def expand_vars(s, defs): + """Expand Makefile-style variables -- "${foo}" or "$(foo)" -- in + 'string' according to 'defs' (a dictionary mapping variable names to + values). Variables not present in 'defs' are silently expanded to the + empty string. + + Returns a variable-expanded version of 's'. + """ + + # This algorithm does multiple expansion, so if defs['foo'] contains + # "${bar}", it will expand ${foo} to ${bar}, and then expand + # ${bar}... and so forth. This is fine as long as 'defs' comes from + # 'parse_makefile()', which takes care of such expansions eagerly, + # according to make's variable expansion semantics. + + while True: + m = _var_rx2.search(s) + if m: + (beg, end) = m.span() + s = s[0:beg] + (defs.get(m.group(1)) or '') + s[end:] + else: + break + return s + + +def shallow_expand_vars(s, defs): + """Expand Makefile-style variables -- "${foo}" or "$(foo)" -- in + 'string' according to 'defs' (a dictionary mapping variable names to + values). Variables not present in 'defs' are silently expanded to the + empty string. + + Returns a variable-expanded version of 's'. + """ + + # This algorithm does multiple expansion, so if defs['foo'] contains + # "${bar}", it will expand ${foo} to ${bar}, and then expand + # ${bar}... and so forth. This is fine as long as 'defs' comes from + # 'parse_makefile()', which takes care of such expansions eagerly, + # according to make's variable expansion semantics. + + m = _var_rx2.search(s) + if m: + (beg, end) = m.span() + return s[0:beg] + (defs.get(m.group(1)) or '') + shallow_expand_vars(s[end:], defs) + + return s + + +def extract_variables(varstr): + """Extracct all variable references, e.g., "${foo}" or "$(foo)" + from a string. + """ + retval = [] + tmp = varstr + while True: + m = _var_rx2.search(tmp) + if m: + retval.append(m[1]) + tmp = tmp[m.end():] + else: + break + return retval + + +def normalize_variables(varstr): + """Convert '$(var)' to '${var}' -- normalizes all variables to a consistent + form. + """ + retval = "" + tmp = varstr + while tmp: + m = _norm_var_rx.search(tmp) + if m: + retval += tmp[:m.start()] + "${" + m[1] + "}" + tmp = tmp[m.end():] + else: + retval += tmp + tmp = "" + return retval + + +def test_rule_rx(): + result = _rule_rx.match('${BIN}frontpaneltest${EXE} : frontpanel/FrontPanelTest.c sim_sock.c sim_frontpanel.c') + print('{0}: {1}'.format('${BIN}frontpaneltest${EXE}...', result)) + print(result.groups()) + + +def test_normalize_variables(): + result = normalize_variables('foo: bar baz') + print('{0}: {1}'.format('foo:...', result)) + result = normalize_variables('$(var): dep1 dep2') + print('{0}: {1}'.format('$(var)...', result)) + result = normalize_variables('$(var): dep1 ${var2} dep2 $(var3)') + print('{0}: {1}'.format('$(var)...', result)) diff --git a/cmake/simgen/pdp10_simulator.py b/cmake/simgen/pdp10_simulator.py new file mode 100644 index 00000000..3a8ebf73 --- /dev/null +++ b/cmake/simgen/pdp10_simulator.py @@ -0,0 +1,45 @@ +## KA10 simulator: Add the PANDA_LIGHTS and the PIDP10 options for frontpanel +## code. + +import simgen.basic_simulator as SBS + +class KA10Simulator(SBS.SIMHBasicSimulator): + def __init__(self, sim_name, dir_macro, test_name, buildrom): + super().__init__(sim_name, dir_macro, test_name, buildrom) + + def write_simulator(self, stream, indent, test_label='ka10'): + ## Keep the CMake options separate, just in case they are needed for more + ## than just the KA10 (and add a class variable so they are only written + ## once.) + stream.write(''' +option(PANDA_LIGHTS + "Enable (=1)/disable (=0) KA-10/KI-11 simulator\'s Panda display. (def: disabled)" + FALSE) +option(PIDP10 + "Enable (=1)/disable (=0) PIDP10 display options (def: disabled)" + FALSE) + +### Ensure that the options are mutually exclusive: +if (PANDA_LIGHTS AND PIDP10) + message(FATAL_ERROR "PANDA_LIGHTS and PIDP10 options are mutually exclusive. Choose one.") +endif () + + +''') + ## Emit the simulator: + super().write_simulator(stream, indent, test_label) + ## Update the display sources for PANDA_LIGHTS or PIDP10: + stream.write('\n'.join([ + '', + 'if (PANDA_LIGHTS)', + ' target_sources({0} PUBLIC {1}/kx10_lights.c)'.format(self.sim_name, self.dir_macro), + ' target_compile_definitions({0} PUBLIC PANDA_LIGHTS)'.format(self.sim_name), + ' target_link_libraries({0} PUBLIC usb-1.0)'.format(self.sim_name), + 'endif ()', + 'if (PIDP10)', + ' target_sources({0} PUBLIC {1}/ka10_pipanel.c)'.format(self.sim_name, self.dir_macro), + ' target_compile_definitions({0} PUBLIC PIDP10=1)'.format(self.sim_name), + 'endif ()' + ])) + stream.write('\n') + diff --git a/cmake/simgen/sim_collection.py b/cmake/simgen/sim_collection.py new file mode 100644 index 00000000..06bd3b5a --- /dev/null +++ b/cmake/simgen/sim_collection.py @@ -0,0 +1,159 @@ +import pprint + +import simgen.parse_makefile as SPM +import simgen.basic_simulator as SBS +import simgen.pdp10_simulator as PDP10 +import simgen.vax_simulators as VAXen +import simgen.utils as SU + +## Special variables that should __not__ expand into their definitions: +_special_vars = frozenset(['DISPLAYL', + 'DISPLAYVT', + 'DISPLAY340', + 'DISPLAYNG', + 'DISPLAYIII']) + +## Map simulator name to its class, for special cases +_special_simulators = { + "besm6": SBS.BESM6Simulator, + "i650": SBS.IBM650Simulator, + "ibm1130": SBS.IBM1130Simulator, + "pdp10-ka": PDP10.KA10Simulator, + "vax": VAXen.VAXSimulator, + "vax730": VAXen.BasicVAXSimulator +} + +ignored_display_macros = { + 'DISPLAYVT': ['${DISPLAYD}/vt11.c'], + 'DISPLAY340': ['${DISPLAYD}/type340.c'], + 'DISPLAYNG': ['${DISPLAYD}/ng.c'], + 'DISPLAYIII': ['${DISPLAYD}/iii.c'] +} + +def get_simulator_ctor(name): + """Return the class object for special case simulators, otherwise + return the base 'SIMHBasicSimulator' + """ + return _special_simulators.get(name) or SBS.SIMHBasicSimulator + + +class SimCollection: + """A collection of simulators. + """ + def __init__(self, dir_macro): + self.source_macros = {} + self.macro_uses = {} + self.simulators = {} + + def get_simulator(self, name, dir_macro, _dir_path, test_name, buildrom): + sim = self.simulators.get(name) + if sim is None: + sim = (get_simulator_ctor(name))(name, dir_macro, test_name, buildrom) + self.simulators[name] = sim + return sim + + def add_source_macro(self, macro, macro_def, sim): + if macro not in self.source_macros: + self.source_macros[macro] = macro_def + + used = self.macro_uses.get(macro) + if used is None: + self.macro_uses[macro] = [] + used = self.macro_uses[macro] + used.append(sim) + + def get_simulator_vars(self, debug=0): + simvars = set() + ignored = set(self.source_macros.keys()) + for macval in self.source_macros.values(): + ## This could be replaced by a functools.reduce() + for val in macval: + simvars = simvars.union(set(SPM.extract_variables(val))) + + for sim in self.simulators.values(): + simvars = simvars.union(sim.get_source_vars().union(sim.get_include_vars())) + + simvars = simvars.difference(ignored).difference(_special_vars) + SU.emit_debug(debug, 2, 'simvars {0}'.format(simvars)) + return simvars + + def write_simulators(self, stream, debug=0, test_label='default'): + ## Emit source macros + dontexpand = set([smac for smac, uses in self.macro_uses.items() if smac not in ignored_display_macros and len(uses) > 1]) + SU.emit_debug(debug, 2, "{0}: dontexpand {1}".format(self.__class__.__name__, dontexpand)) + + if len(dontexpand) > 0: + smac_sorted = list(dontexpand) + smac_sorted.sort() + for smac in smac_sorted: + stream.write('\n\n') + stream.write('set({0}\n'.format(smac)) + stream.write('\n'.join([' ' * 4 + f for f in self.source_macros[smac]])) + stream.write(')') + stream.write('\n\n') + + ## Emit the simulators + simnames = list(self.simulators.keys()) + simnames.sort() + SU.emit_debug(debug, 2, "{0}: Writing {1}".format(self.__class__.__name__, simnames)) + for simname in simnames: + sim = self.simulators[simname] + + ## Patch up the simulator source lists, expanding macros that aren't + ## in the macro sources: + sim.sources = self.expand_sources(sim.sources, dontexpand, debug) + + stream.write('\n') + sim.write_simulator(stream, 0, test_label) + + def write_unit_tests(self, stream, debug=0, test_label='default'): + dontexpand = set([smac for smac, uses in self.macro_uses.items() if len(uses) > 1]) + + simnames = list(self.simulators.keys()) + simnames.sort() + SU.emit_debug(debug, 2, "{0}: Writing {1}".format(self.__class__.__name__, simnames)) + for simname in simnames: + sim = self.simulators[simname] + + ## Patch up the simulator source lists, expanding macros that aren't + ## in the macro sources: + sim.sources = self.expand_sources(sim.sources, dontexpand, debug) + sim.write_unit_test(stream, 0, test_label) + + def expand_sources(self, srcs, dontexpand, debug=0): + updated_srcs = [] + for src in srcs: + SU.emit_debug(debug, 2, "{0}: Source {1}".format(self.__class__.__name__, src)) + m = SPM._var_rx.match(src) + if m and m[1] not in dontexpand.union(_special_vars): + SU.emit_debug(debug, 2, "{0}: Expanding {1}".format(self.__class__.__name__, m[1])) + varexp = self.source_macros.get(m[1]) + if varexp is not None: + updated_srcs.extend(self.source_macros[m[1]]) + else: + print('!! Could not expand {0}'.format(m[1])) + else: + updated_srcs.append(src) + + if updated_srcs == srcs: + return srcs + else: + return self.expand_sources(updated_srcs, dontexpand, debug) + + def __len__(self): + return len(self.simulators) + +if '_dispatch' in pprint.PrettyPrinter.__dict__: + def simcoll_pprinter(pprinter, simcoll, stream, indent, allowance, context, level): + cls = simcoll.__class__ + stream.write(cls.__name__ + '(') + indent += len(cls.__name__) + 1 + pprinter._format(simcoll.source_macros, stream, indent, allowance + 2, context, level) + stream.write(',\n' + ' ' * indent) + uses_dict = dict([(sim, len(uses)) for (sim, uses) in simcoll.macro_uses.items()]) + pprinter._format(uses_dict, stream, indent, allowance + 2, context, level) + stream.write(',\n' + ' ' * indent) + pprinter._format(simcoll.simulators, stream, indent, allowance + 2, context, level) + stream.write(')') + + pprint.PrettyPrinter._dispatch[SimCollection.__repr__] = simcoll_pprinter diff --git a/cmake/simgen/text_file.py b/cmake/simgen/text_file.py new file mode 100644 index 00000000..93abad38 --- /dev/null +++ b/cmake/simgen/text_file.py @@ -0,0 +1,286 @@ +"""text_file + +provides the TextFile class, which gives an interface to text files +that (optionally) takes care of stripping comments, ignoring blank +lines, and joining lines with backslashes.""" + +import sys, io + + +class TextFile: + """Provides a file-like object that takes care of all the things you + commonly want to do when processing a text file that has some + line-by-line syntax: strip comments (as long as "#" is your + comment character), skip blank lines, join adjacent lines by + escaping the newline (ie. backslash at end of line), strip + leading and/or trailing whitespace. All of these are optional + and independently controllable. + + Provides a 'warn()' method so you can generate warning messages that + report physical line number, even if the logical line in question + spans multiple physical lines. Also provides 'unreadline()' for + implementing line-at-a-time lookahead. + + Constructor is called as: + + TextFile (filename=None, file=None, **options) + + It bombs (RuntimeError) if both 'filename' and 'file' are None; + 'filename' should be a string, and 'file' a file object (or + something that provides 'readline()' and 'close()' methods). It is + recommended that you supply at least 'filename', so that TextFile + can include it in warning messages. If 'file' is not supplied, + TextFile creates its own using 'io.open()'. + + The options are all boolean, and affect the value returned by + 'readline()': + strip_comments [default: true] + strip from "#" to end-of-line, as well as any whitespace + leading up to the "#" -- unless it is escaped by a backslash + lstrip_ws [default: false] + strip leading whitespace from each line before returning it + rstrip_ws [default: true] + strip trailing whitespace (including line terminator!) from + each line before returning it + skip_blanks [default: true} + skip lines that are empty *after* stripping comments and + whitespace. (If both lstrip_ws and rstrip_ws are false, + then some lines may consist of solely whitespace: these will + *not* be skipped, even if 'skip_blanks' is true.) + join_lines [default: false] + if a backslash is the last non-newline character on a line + after stripping comments and whitespace, join the following line + to it to form one "logical line"; if N consecutive lines end + with a backslash, then N+1 physical lines will be joined to + form one logical line. + collapse_join [default: false] + strip leading whitespace from lines that are joined to their + predecessor; only matters if (join_lines and not lstrip_ws) + errors [default: 'strict'] + error handler used to decode the file content + + Note that since 'rstrip_ws' can strip the trailing newline, the + semantics of 'readline()' must differ from those of the builtin file + object's 'readline()' method! In particular, 'readline()' returns + None for end-of-file: an empty string might just be a blank line (or + an all-whitespace line), if 'rstrip_ws' is true but 'skip_blanks' is + not.""" + + default_options = { 'strip_comments': 1, + 'skip_blanks': 1, + 'lstrip_ws': 0, + 'rstrip_ws': 1, + 'join_lines': 0, + 'collapse_join': 0, + 'errors': 'strict', + } + + def __init__(self, filename=None, file=None, **options): + """Construct a new TextFile object. At least one of 'filename' + (a string) and 'file' (a file-like object) must be supplied. + They keyword argument options are described above and affect + the values returned by 'readline()'.""" + if filename is None and file is None: + raise RuntimeError("you must supply either or both of 'filename' and 'file'") + + # set values for all options -- either from client option hash + # or fallback to default_options + for opt in self.default_options.keys(): + if opt in options: + setattr(self, opt, options[opt]) + else: + setattr(self, opt, self.default_options[opt]) + + # sanity check client option hash + for opt in options.keys(): + if opt not in self.default_options: + raise KeyError("invalid TextFile option '%s'" % opt) + + if file is None: + self.open(filename) + else: + self.filename = filename + self.file = file + self.current_line = 0 # assuming that file is at BOF! + + # 'linebuf' is a stack of lines that will be emptied before we + # actually read from the file; it's only populated by an + # 'unreadline()' operation + self.linebuf = [] + + def open(self, filename): + """Open a new file named 'filename'. This overrides both the + 'filename' and 'file' arguments to the constructor.""" + self.filename = filename + self.file = io.open(self.filename, 'r', errors=self.errors) + self.current_line = 0 + + def close(self): + """Close the current file and forget everything we know about it + (filename, current line number).""" + file = self.file + self.file = None + self.filename = None + self.current_line = None + file.close() + + def gen_error(self, msg, line=None): + outmsg = [] + if line is None: + line = self.current_line + outmsg.append(self.filename + ", ") + if isinstance(line, (list, tuple)): + outmsg.append("lines %d-%d: " % tuple(line)) + else: + outmsg.append("line %d: " % line) + outmsg.append(str(msg)) + return "".join(outmsg) + + def error(self, msg, line=None): + raise ValueError("error: " + self.gen_error(msg, line)) + + def warn(self, msg, line=None): + """Print (to stderr) a warning message tied to the current logical + line in the current file. If the current logical line in the + file spans multiple physical lines, the warning refers to the + whole range, eg. "lines 3-5". If 'line' supplied, it overrides + the current line number; it may be a list or tuple to indicate a + range of physical lines, or an integer for a single physical + line.""" + sys.stderr.write("warning: " + self.gen_error(msg, line) + "\n") + + def readline(self): + """Read and return a single logical line from the current file (or + from an internal buffer if lines have previously been "unread" + with 'unreadline()'). If the 'join_lines' option is true, this + may involve reading multiple physical lines concatenated into a + single string. Updates the current line number, so calling + 'warn()' after 'readline()' emits a warning about the physical + line(s) just read. Returns None on end-of-file, since the empty + string can occur if 'rstrip_ws' is true but 'strip_blanks' is + not.""" + # If any "unread" lines waiting in 'linebuf', return the top + # one. (We don't actually buffer read-ahead data -- lines only + # get put in 'linebuf' if the client explicitly does an + # 'unreadline()'. + if self.linebuf: + line = self.linebuf[-1] + del self.linebuf[-1] + return line + + buildup_line = '' + + while True: + # read the line, make it None if EOF + line = self.file.readline() + if line == '': + line = None + + if self.strip_comments and line: + + # Look for the first "#" in the line. If none, never + # mind. If we find one and it's the first character, or + # is not preceded by "\", then it starts a comment -- + # strip the comment, strip whitespace before it, and + # carry on. Otherwise, it's just an escaped "#", so + # unescape it (and any other escaped "#"'s that might be + # lurking in there) and otherwise leave the line alone. + + pos = line.find("#") + if pos == -1: # no "#" -- no comments + pass + + # It's definitely a comment -- either "#" is the first + # character, or it's elsewhere and unescaped. + elif pos == 0 or line[pos-1] != "\\": + # Have to preserve the trailing newline, because it's + # the job of a later step (rstrip_ws) to remove it -- + # and if rstrip_ws is false, we'd better preserve it! + # (NB. this means that if the final line is all comment + # and has no trailing newline, we will think that it's + # EOF; I think that's OK.) + eol = (line[-1] == '\n') and '\n' or '' + line = line[0:pos] + eol + + # If all that's left is whitespace, then skip line + # *now*, before we try to join it to 'buildup_line' -- + # that way constructs like + # hello \\ + # # comment that should be ignored + # there + # result in "hello there". + if line.strip() == "": + continue + else: # it's an escaped "#" + line = line.replace("\\#", "#") + + # did previous line end with a backslash? then accumulate + if self.join_lines and buildup_line: + # oops: end of file + if line is None: + self.warn("continuation line immediately precedes " + "end-of-file") + return buildup_line + + if self.collapse_join: + line = line.lstrip() + line = buildup_line + line + + # careful: pay attention to line number when incrementing it + if isinstance(self.current_line, list): + self.current_line[1] = self.current_line[1] + 1 + else: + self.current_line = [self.current_line, + self.current_line + 1] + # just an ordinary line, read it as usual + else: + if line is None: # eof + return None + + # still have to be careful about incrementing the line number! + if isinstance(self.current_line, list): + self.current_line = self.current_line[1] + 1 + else: + self.current_line = self.current_line + 1 + + # strip whitespace however the client wants (leading and + # trailing, or one or the other, or neither) + if self.lstrip_ws and self.rstrip_ws: + line = line.strip() + elif self.lstrip_ws: + line = line.lstrip() + elif self.rstrip_ws: + line = line.rstrip() + + # blank line (whether we rstrip'ed or not)? skip to next line + # if appropriate + if (line == '' or line == '\n') and self.skip_blanks: + continue + + if self.join_lines: + if line[-1] == '\\': + buildup_line = line[:-1] + continue + + if line[-2:] == '\\\n': + buildup_line = line[0:-2] + '\n' + continue + + # well, I guess there's some actual content there: return it + return line + + def readlines(self): + """Read and return the list of all logical lines remaining in the + current file.""" + lines = [] + while True: + line = self.readline() + if line is None: + return lines + lines.append(line) + + def unreadline(self, line): + """Push 'line' (a string) onto an internal buffer that will be + checked by future 'readline()' calls. Handy for implementing + a parser with line-at-a-time lookahead.""" + self.linebuf.append(line) diff --git a/cmake/simgen/utils.py b/cmake/simgen/utils.py new file mode 100644 index 00000000..ddfa7bc0 --- /dev/null +++ b/cmake/simgen/utils.py @@ -0,0 +1,9 @@ +import re + + +def emit_debug(dval, level, msg): + if dval >= level: + print(msg) + +def do_debug(dval, level, act): + pass diff --git a/cmake/simgen/vax_simulators.py b/cmake/simgen/vax_simulators.py new file mode 100644 index 00000000..3b758627 --- /dev/null +++ b/cmake/simgen/vax_simulators.py @@ -0,0 +1,53 @@ +## VAX simulators require extra magic -- notably, 'microvax3900${EXE}' needs +## to be symlinked, hardlinked or copied (in that order) to 'vax${EXE}'. + +import simgen.basic_simulator as SBS + +class BasicVAXSimulator(SBS.SIMHBasicSimulator): + """ + """ + def __init__(self, sim_name, dir_macro, test_name, buildrom): + super().__init__(sim_name, dir_macro, test_name, buildrom) + + def write_unit_test(self, stream, indent, individual=False, test_label='default'): + stream.write('\n') + self.write_section(stream, 'add_unit_test', indent, individual=False, test_label=test_label, + section_name='vax_cc_{}'.format(self.sim_name), + section_srcs=['vax_cc.c'], + section_incs=self.includes) + +class VAXSimulator(BasicVAXSimulator): + """ + """ + def __init__(self, sim_name, dir_macro, test_name, buildrom): + super().__init__(sim_name, dir_macro, test_name, buildrom) + + def write_simulator(self, stream, indent, test_label='VAX'): + super().write_simulator(stream, indent, test_label) + stream.write(''' +set(vax_binary_dir ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) +if (CMAKE_CONFIGURATION_TYPES) + string(APPEND vax_binary_dir "/$") +endif (CMAKE_CONFIGURATION_TYPES) + +add_custom_command(TARGET vax POST_BUILD + COMMAND "${CMAKE_COMMAND}" + -DSRCFILE=vax${CMAKE_EXECUTABLE_SUFFIX} + -DDSTFILE=microvax3900${CMAKE_EXECUTABLE_SUFFIX} + -DWORKING_DIR=${vax_binary_dir} + -P ${CMAKE_SOURCE_DIR}/cmake/file-link-copy.cmake + COMMENT "Symlink vax${CMAKE_EXECUTABLE_SUFFIX} to microvax3900${CMAKE_EXECUTABLE_SUFFIX}" + WORKING_DIRECTORY ${vax_binary_dir}) + +install( + CODE " + execute_process( + COMMAND ${CMAKE_COMMAND} + -DSRCFILE=vax${CMAKE_EXECUTABLE_SUFFIX} + -DDSTFILE=microvax3900${CMAKE_EXECUTABLE_SUFFIX} + -DWORKING_DIR=\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin + -P ${CMAKE_SOURCE_DIR}/cmake/file-link-copy.cmake)" + COMPONENT vax_family) +''') + stream.write('\n') + diff --git a/cmake/simh-packaging.cmake b/cmake/simh-packaging.cmake new file mode 100644 index 00000000..11e50a5c --- /dev/null +++ b/cmake/simh-packaging.cmake @@ -0,0 +1,30 @@ +## The default runtime support component/family: +cpack_add_component(runtime_support + DISPLAY_NAME "Runtime support" + DESCRIPTION "Required SIMH runtime support (documentation, shared libraries)" + REQUIRED +) + +## Basic documentation for SIMH +install(FILES doc/simh.doc TYPE DOC COMPONENT runtime_support) + +cpack_add_component(b5500_family + DISPLAY_NAME "Burroughs 5500" + DESCRIPTION "The Burroughs 5500 system simulator. Simulators: b5500" +) +cpack_add_component(gould_family + DISPLAY_NAME "Gould simulators" + DESCRIPTION "Gould Systems simulators. Simulators: sel32" +) +cpack_add_component(ibm_family + DISPLAY_NAME "IBM" + DESCRIPTION "IBM system simulators. Simulators: i701, i7010, i704, i7070, i7080, i7090, ibm360" +) +cpack_add_component(ict_family + DISPLAY_NAME "ICT" + DESCRIPTION "International Computers and Tabulators simulators. Simulators: icl1900" +) +cpack_add_component(pdp10_family + DISPLAY_NAME "DEC PDP-10 collection" + DESCRIPTION "DEC PDP-10 architecture simulators and variants. Simulators: pdp10-ka, pdp10-ki, pdp10-kl, pdp10-ks, pdp6" +) diff --git a/cmake/simh-simulators.cmake b/cmake/simh-simulators.cmake new file mode 100644 index 00000000..3fc7e6aa --- /dev/null +++ b/cmake/simh-simulators.cmake @@ -0,0 +1,48 @@ +## +## This is an automagically generated file. Do NOT EDIT. +## Any changes you make will be overwritten!! +## +## Make changes to the SIMH top-level makefile and then run the +## "cmake/generate.py" script to regenerate these files. +## +## cd cmake; python -m generate --help +## +## ------------------------------------------------------------ +set(B5500D "${CMAKE_SOURCE_DIR}/B5500") +set(I7000D "${CMAKE_SOURCE_DIR}/I7000") +set(I7010D "${CMAKE_SOURCE_DIR}/I7000") +set(IBM360D "${CMAKE_SOURCE_DIR}/IBM360") +set(ICL1900D "${CMAKE_SOURCE_DIR}/ICL1900") +set(KA10D "${CMAKE_SOURCE_DIR}/PDP10") +set(KI10D "${CMAKE_SOURCE_DIR}/PDP10") +set(KL10D "${CMAKE_SOURCE_DIR}/PDP10") +set(KS10D "${CMAKE_SOURCE_DIR}/PDP10") +set(PDP10D "${CMAKE_SOURCE_DIR}/PDP10") +set(PDP6D "${CMAKE_SOURCE_DIR}/PDP10") +set(SEL32D "${CMAKE_SOURCE_DIR}/SEL32") + +set(DISPLAYD "${CMAKE_SOURCE_DIR}/display") +set(DISPLAY340 "${DISPLAYD}/type340.c") +set(DISPLAYIII "${DISPLAYD}/iii.c") +set(DISPLAYNG "${DISPLAYD}/ng.c") +set(DISPLAYVT "${DISPLAYD}/vt11.c") + +## ---------------------------------------- + +if (NOT WITH_VIDEO) + ### Hack: Unset these variables so that they don't expand if + ### not building with video: + set(DISPLAY340 "") + set(DISPLAYIII "") + set(DISPLAYNG "") + set(DISPLAYVT "") +endif () + +## ---------------------------------------- + +add_subdirectory(B5500) +add_subdirectory(I7000) +add_subdirectory(IBM360) +add_subdirectory(ICL1900) +add_subdirectory(PDP10) +add_subdirectory(SEL32) diff --git a/cmake/vcpkg-setup.cmake b/cmake/vcpkg-setup.cmake new file mode 100644 index 00000000..144de7af --- /dev/null +++ b/cmake/vcpkg-setup.cmake @@ -0,0 +1,88 @@ +##+=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~ +## vcpkg setup for MSVC. MinGW builds should use 'pacman' to install +## required dependency libraries. +##-=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~ + +if (NOT USING_VCPKG) + return () +endif () + +if (NOT DEFINED VCPKG_TARGET_TRIPLET) + if (DEFINED ENV{VCPKG_DEFAULT_TRIPLET}) + ## User has a target triplet in mind, so use it. + set(VCPKG_TARGET_TRIPLET ENV{VCPKG_DEFAULT_TRIPLET}) + else () + ## Set the target triplet: + if (CMAKE_SYSTEM_NAME STREQUAL "Windows") + ## Default to x64, unless otherwise directed: + set(SIMH_VCPKG_ARCH "x64") + if(CMAKE_GENERATOR_PLATFORM MATCHES "[Ww][Ii][Nn]32") + set(SIMH_VCPKG_ARCH "x86") + elseif(CMAKE_GENERATOR_PLATFORM MATCHES "[Aa][Rr][Mm]64") + set(SIMH_VCPKG_ARCH "arm64") + elseif(CMAKE_GENERATOR_PLATFORM MATCHES "[Aa][Rr][Mm]") + set(SIMH_VCPKG_ARCH "arm") + endif() + + if (MSVC OR CMAKE_C_COMPILER_ID MATCHES ".*Clang") + set(SIMH_VCPKG_PLATFORM "windows") + set(SIMH_VCPKG_RUNTIME "") + if (NOT BUILD_SHARED_DEPS) + set(SIMH_VCPKG_RUNTIME "static") + endif () + elseif (MINGW OR CMAKE_C_COMPILER_ID STREQUAL "GNU") + set(SIMH_VCPKG_PLATFORM "mingw") + set(SIMH_VCPKG_RUNTIME "dynamic") + endif () + elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux") + if (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "aarch64") + set(SIMH_VCPKG_ARCH "arm64") + else () + set(SIMH_VCPKG_ARCH "x64") + endif () + + set (SIMH_VCPKG_PLATFORM "linux") + else () + message(FATAL_ERROR "Could not determine VCPKG platform and system triplet." + "\n" + "(a) Are you sure that VCPKG is usable on this system? Check VCPKG_ROOT and ensure that" + "you have properly boostrapped VCPKG." + "\n" + "(b) If VCPKG is not usable on this system, unset the VCPKG_ROOT environment variable.") + endif () + + ## Set the default triplet in the environment; older vcpkg installs on + ## appveyor don't necessarily support the "--triplet" command line argument. + set(use_triplet "${SIMH_VCPKG_ARCH}-${SIMH_VCPKG_PLATFORM}") + if (SIMH_VCPKG_RUNTIME) + string(APPEND use_triplet "-${SIMH_VCPKG_RUNTIME}") + endif () + + set(VCPKG_TARGET_TRIPLET "${use_triplet}" CACHE STRING "Vcpkg target triplet (ex. x86-windows)" FORCE) + unset(use_triplet) + + set(ENV{VCPKG_DEFAULT_TRIPLET} ${VCPKG_TARGET_TRIPLET}) + endif () +endif () + +## Set VCPKG_CRT_LINKAGE to pass down so that SIMH matches the triplet's link +## environment. Otherwise, the build will get a lot of "/NODEFAULTLIB" warnings. +set(VCPKG_CRT_LINKAGE "dynamic") +if (VCPKG_TARGET_TRIPLET MATCHES ".*-static") + set(VCPKG_CRT_LINKAGE "static") +endif () + +message(STATUS "Executing deferred vcpkg toolchain initialization.\n" + " .. VCPKG target triplet is ${VCPKG_TARGET_TRIPLET}\n" + " .. VCPKG_CRT_LINKAGE is ${VCPKG_CRT_LINKAGE}") + +## Initialize vcpkg after CMake detects the compiler and we've to set the platform triplet. +## VCPKG_INSTALL_OPTIONS are additional args to 'vcpkg install'. Don't need to see the +## usage instructions each time... +if (NOT ("--no-print-usage" IN_LIST VCPKG_INSTALL_OPTIONS)) + list(APPEND VCPKG_INSTALL_OPTIONS + "--no-print-usage" + ) +endif () + +include(${SIMH_CMAKE_TOOLCHAIN_FILE})