Repository: CMakePP/CMinx (language: python)

Issue:
PIP Package
Once all other 1.0 stuff is done, we should actually deploy a PIP package. Ideally we'd do this via CI (automatically deploy for each minor bump?). This issue is mainly a reminder to make that happen.


Discussion / hints:

Fix patch:
diff --git a/.github/.licenserc.yaml b/.github/.licenserc.yaml
index 882fd59a..86399235 100644
--- a/.github/.licenserc.yaml
+++ b/.github/.licenserc.yaml
@@ -19,14 +19,14 @@ header:
 
   paths-ignore:
     - .github/
-    - cminx/parser/
+    - src/cminx/parser/
     - docs/source/.templates
     - docs/source/developer/uml_diagrams/*.drawio
     - docs/Makefile
     - tests/
-    - CODE_OF_CONDUCT.md
     - LICENSE
-    - VERSION.txt
+    - MANIFEST.in
+    - setup.cfg
 
 
   comment: never
diff --git a/.github/workflows/pip_install.yaml b/.github/workflows/pip_install.yaml
index c5c99be9..ed57c081 100644
--- a/.github/workflows/pip_install.yaml
+++ b/.github/workflows/pip_install.yaml
@@ -42,14 +42,13 @@ jobs:
           python3 --version
           python3 -m venv virt_env
           source virt_env/bin/activate
-          pip3 install wheel
-          pip3 install .
-          pip3 install -r tests/requirements.txt
+          pip3 install -e .
+          pip3 install -r requirements.txt
       - name: Test
         run: |
           source virt_env/bin/activate
           cd tests
-          pytest --cov=../cminx/ --cov-report=xml
+          pytest --cov=../src/cminx/ --cov-report=xml
       - name: Upload coverage to Codecov
         uses: codecov/codecov-action@v2
         with:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 94930135..7a8d6ffb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,5 @@
-cmake_minimum_required(VERSION 3.14) #For fetchcontent_makeavailable
+cmake_minimum_required(VERSION 3.19) #For Python3 recognizing version
+find_package(Python3 3.8 COMPONENTS Interpreter REQUIRED)
 list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
 
 include(get_version_from_git)
@@ -14,128 +15,113 @@ project(
 
 option(BUILD_TESTING "Whether tests should be built ad ctest configured" OFF)
 
-#Allows us to locate any arbitrary Python3 module.
-include(find_python_module)
+################################################################################
+# Work out install paths
+################################################################################
+# Path, relative to install root, where binaries go
+set(CMINX_BIN_DIR "bin")
 
-find_package(Python3 COMPONENTS Interpreter REQUIRED)
+# Path, relative to install root, where libraries go
+set(CMINX_LIB_DIR "lib/cminx")
 
+# Path, relative to install root, where CMake modules and config files go
+set(CMINX_CMAKE_DIR "${CMINX_LIB_DIR}/cmake")
 
-#We have Python, now let's check to see if we have setuptools and venv
-find_python_module(setuptools REQUIRED)
-find_python_module(venv REQUIRED)
+# During the configuration, building, and testing phases we use a staging
+# directory as the install root. The install phase simply copies the staging
+# directory to the install location. This is the absolute path of the staging
+# directory.
+set(CMINX_STAGE_DIR "${CMAKE_CURRENT_BINARY_DIR}/stage")
 
-set(DEPS        "${CMAKE_CURRENT_SOURCE_DIR}/cminx/__init__.py")
-set(OUTPUT      "${CMAKE_CURRENT_BINARY_DIR}/cminx")
+################################################################################
+# Install Dependencies
+################################################################################
 
 
-#[[[
-# Points to the location of the build virtual environment.
-#]]
-set(
-    CMINX_VENV "${CMAKE_CURRENT_BINARY_DIR}/virtual-env"
-    CACHE FILEPATH "Directory of the virtual environment"
-)
+include(python_virt_env)
 
-#Create virtual environment in the build directory
-message(STATUS "Creating virtual environment for build directory...")
+# Points to the location of the build virtual environment.
+set(CMINX_VENV "${CMAKE_CURRENT_BINARY_DIR}/virtual-env")
 
-execute_process(COMMAND "${Python3_EXECUTABLE}" "-m"
-                        "venv" "${CMINX_VENV}"
-                        RESULT_VARIABLE _venv_status
-                        ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
+make_python_venv("${Python3_EXECUTABLE}" "${CMINX_VENV}")
+python_venv_pip_install("${CMINX_VENV}" "pyinstaller")
+python_venv_pip_install("${CMINX_VENV}" "wheel")
+python_venv_pip_install("${CMINX_VENV}" "-e" "${CMAKE_CURRENT_SOURCE_DIR}")
 
-if (NOT _venv_status EQUAL "0")
-	message(
-        FATAL_ERROR
-        "Virtual environment could not be created, venv "
-        "returned ${_venv_status}"
-    )
-endif()
+################################################################################
+# Build CMinx
+################################################################################
 
-#[[[
-# Location of the virtual environment python 3 executable.
-#]]
-set(
-    CMINX_VENV_PYTHON_EXECUTABLE "${CMINX_VENV}/bin/python3"
-    CACHE
-    FILEPATH
-    "The location of the virtual environment Python3 executable."
+set(CMINX_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/cminx)
+set(CMINX_CONFIG_FILE ${CMINX_SRC_DIR}/config_default.yaml)
+# N.B. this runs during configure so CMinx can be used downstream
+# via fetch content
+execute_process(
+    COMMAND ${CMINX_VENV}/bin/python3 "-m" "PyInstaller"
+                                      "--collect-data" "src/cminx"
+                                      "--add-data" "${CMINX_CONFIG_FILE}:cminx"
+                                      "--name" "cminx"
+                                      "--onefile"
+                                      "${CMAKE_CURRENT_SOURCE_DIR}/src/main.py"
+    WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
 )
 
-#[[[
-# Location of the virtual environment pip.
-#]]
-set(
-    CMINX_VENV_PIP "${CMINX_VENV}/bin/pip3"
-    CACHE
-    FILEPATH
-    "The location of the Pip3 to be used for the virtual environment"
+# Move CMinx (and the CMinx CMake module) to the stage directory
+file(
+    COPY ${CMAKE_CURRENT_BINARY_DIR}/dist/cminx
+    DESTINATION ${CMINX_STAGE_DIR}/${CMINX_BIN_DIR}
 )
-
-#Install needed dependencies
-message(STATUS "Installing build dependencies in virtual environment...")
-
-#Install our build tool to our build environment
-execute_process(
-    COMMAND "${CMINX_VENV_PIP}" "install" "pyinstaller"
+file(
+    COPY ${CMAKE_CURRENT_LIST_DIR}/cmake/cminx.cmake
+    DESTINATION ${CMINX_STAGE_DIR}/${CMINX_CMAKE_DIR}
 )
 
+################################################################################
+# Write package config files
+################################################################################
 
-#Install program specific dependencies so that pyinstaller can find them.
-execute_process(
-    COMMAND "${CMINX_VENV_PIP}" "install" "-e" "${CMAKE_CURRENT_SOURCE_DIR}"
+include(CMakePackageConfigHelpers)
+configure_package_config_file(
+    "${CMAKE_CURRENT_LIST_DIR}/cmake/templates/cminx-config.cmake.in"
+    "${CMINX_STAGE_DIR}/${CMINX_CMAKE_DIR}/cminx-config.cmake"
+    INSTALL_DESTINATION "${CMINX_CMAKE_DIR}"
+    PATH_VARS CMINX_BIN_DIR CMINX_CMAKE_DIR
 )
 
-#Write package config files
-configure_file(
-    "${CMAKE_CURRENT_LIST_DIR}/cmake/templates/CMinxConfig.cmake"
-    "${CMAKE_CURRENT_BINARY_DIR}"
-    @ONLY
-)
-configure_file(
-    "${CMAKE_CURRENT_LIST_DIR}/cmake/templates/CMinxConfigVersion.cmake"
-    "${CMAKE_CURRENT_BINARY_DIR}"
-    @ONLY
+write_basic_package_version_file(
+    "${CMINX_STAGE_DIR}/${CMINX_CMAKE_DIR}/cminx-config-version.cmake"
+    COMPATIBILITY AnyNewerVersion
 )
-set(CMINX_PACKAGE_SEARCH_PATH "${CMAKE_CURRENT_BINARY_DIR}")
-
-#[[
-# Build command.
-# First line calls PyInstaller to build the single file executable.
-# Second line copies the resultant executable to the binary directory.
-#]]
-add_custom_target(
-    cminx ALL
-    ${CMINX_VENV_PYTHON_EXECUTABLE}
-      "-m" "PyInstaller" "--collect-data" "cminx" "--onefile"
-      "${CMAKE_CURRENT_SOURCE_DIR}/main.py"
-    COMMAND "sh" "-c" "cp ${CMAKE_CURRENT_BINARY_DIR}/dist/main ${OUTPUT}"
-    DEPENDS ${DEPS}
-    BYPRODUCTS ${OUTPUT}
-)
-
 
-install(PROGRAMS ${OUTPUT} DESTINATION bin)
+################################################################################
+# Testing
+################################################################################
 
 if(BUILD_TESTING)
     message("Building tests")
     include("${PROJECT_SOURCE_DIR}/cmake/get_cpp.cmake")
     include("${PROJECT_SOURCE_DIR}/cmake/get_cmake_test.cmake")
 
-    execute_process(
-        COMMAND "${CMINX_VENV_PIP}"
-                "install"
-                "-r"
-                "${CMAKE_CURRENT_SOURCE_DIR}/tests/requirements.txt"
+    python_venv_pip_install(
+        ${CMINX_VENV} "-r" "${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt"
     )
 
     include(cmake_test/cmake_test)
     include(CTest)
 
-
     add_subdirectory("tests")
 
 endif()
 
-#Define functions and set source directory
-include("cminx")
+################################################################################
+# Installation
+################################################################################
+
+install(
+    DIRECTORY ${CMINX_STAGE_DIR}/ 
+    DESTINATION ${CMAKE_INSTALL_PREFIX}
+    USE_SOURCE_PERMISSIONS
+)
+
+# For fetch content purposes we make cminx_gen_rst available to the caller
+find_package(cminx REQUIRED CONFIG HINTS ${CMINX_STAGE_DIR})
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 00000000..56cc724b
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,9 @@
+include setup.py
+include MANIFEST.in
+include LICENSE
+include README.rst
+
+graft tests
+graft examples
+graft docs
+graft src
diff --git a/README.md b/README.md
deleted file mode 100644
index a923cc42..00000000
--- a/README.md
+++ /dev/null
@@ -1,22 +0,0 @@
-<!--
-  ~ Copyright 2021 CMakePP
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~ http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  ~
--->
-# CMinx
-[![codecov](https://codecov.io/gh/CMakePP/CMinx/branch/master/graph/badge.svg?token=wbiPq8Gnrs)](https://codecov.io/gh/CMakePP/CMinx)
-
-Full documentation, including details on how to install CMinx and quickly get
-started can be found [here](https://cmakepp.github.io/CMinx/).
-
diff --git a/README.rst b/README.rst
new file mode 100644
index 00000000..c252a0a4
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,24 @@
+..
+   Copyright 2021 CMakePP
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+#####
+CMinx
+#####
+
+.. image:: https://codecov.io/gh/CMakePP/CMinx/branch/master/graph/badge.svg?token=wbiPq8Gnrs
+   :target: https://codecov.io/gh/CMakePP/CMinx
+
+Full documentation, including details on how to install CMinx and quickly get
+started can be found `here <https://cmakepp.github.io/CMinx/>`__.
diff --git a/cmake/cminx.cmake b/cmake/cminx.cmake
index 12bac54d..d60858ea 100644
--- a/cmake/cminx.cmake
+++ b/cmake/cminx.cmake
@@ -14,9 +14,6 @@
 #
 include_guard()
 
-set(CMAKEDOC_SRC "${CMAKE_CURRENT_LIST_DIR}/.." CACHE FILEPATH "Location of CMinx")
-
-
 #[[[
 # Generate documentation RST from source CMake files.
 #
@@ -24,39 +21,35 @@ set(CMAKEDOC_SRC "${CMAKE_CURRENT_LIST_DIR}/.." CACHE FILEPATH "Location of CMin
 # :type dir: dir
 # :param output: Directory to store output
 # :type output: dir
-# :param \*args: Optional arguments below. Pass to the function after all other parameters, order-sensitive.
-#
-# :Optional Arguments:
-#    * *Prefix* (``string``) -- A prefix to be passed to CMinx. All files will have
-#      the prefix prepended to their RST titles, and root-level files will have their
-#      titles replaced by the prefix in recursive mode.
+# :param \*args: Additional parameters to forward to CMinx. Parameters are
+#                forwarded verbatim.
 #]]
 function(cminx_gen_rst _cgd_dir _cgd_output)
-        set(_cgr_cminx_options "")
-        if(IS_DIRECTORY "${_cgd_dir}")
-            list(APPEND _cgr_cminx_options "-r")
-        endif()
+    set(_cgr_cminx_options "")
+    if(IS_DIRECTORY "${_cgd_dir}")
+        list(APPEND _cgr_cminx_options "-r")
+    endif()
 
-        if(${ARGC} GREATER 2)
-            list(APPEND _cgr_cminx_options -p ${ARGV2})
-        endif()
+    if(${ARGC} GREATER 2)
+        list(APPEND _cgr_cminx_options "${ARGN}")
+    endif()
 
-        execute_process(
-            COMMAND
-                "${CMINX_VENV_PYTHON_EXECUTABLE}"
-                "${CMAKEDOC_SRC}/main.py"
-                "${_cgd_dir}"
-                ${_cgr_cminx_options}
+    execute_process(
+        COMMAND "${CMINX_EXECUTABLE}" "${_cgd_dir}" ${_cgr_cminx_options} 
                 "-o" "${_cgd_output}"
-            WORKING_DIRECTORY "${CMAKEDOC_SRC}"
-            OUTPUT_VARIABLE process_output
-            ERROR_VARIABLE process_err
-            RESULT_VARIABLE process_result
-        )
+        OUTPUT_VARIABLE process_output
+        ERROR_VARIABLE process_err
+        RESULT_VARIABLE process_result
+    )
 
 	message("${process_output}")
-        if(NOT process_result EQUAL 0)
-            message(FATAL_ERROR "Failed to generate RST. Result code was: ${process_result}. Error output was: ${process_err}. Process output was: ${process_output}")
-        endif()
-
-endfunction()
+    if(NOT process_result EQUAL 0)
+        string(
+            CONCAT error_msg "Failed to generate RST.\n"
+                             "Result code was: ${process_result}.\n" 
+                             "Error output was: ${process_err}.\n" 
+                             "Process output was: ${process_output}\n"
+        )
+        message(FATAL_ERROR "${error_msg}")
+    endif()
+endfunction()
\ No newline at end of file
diff --git a/cmake/find_python_module.cmake b/cmake/find_python_module.cmake
index 6c644820..b98308f6 100644
--- a/cmake/find_python_module.cmake
+++ b/cmake/find_python_module.cmake
@@ -14,8 +14,11 @@
 #
 include_guard()
 
+
+
 #[[[
-# Custom function to locate a Python module. Requires the Python interpreter to be located first.
+# Custom function to locate a Python module. Requires the Python interpreter to
+# be located first.
 #
 # Sets PY_${module_upper} to the module file's location if found.
 #
@@ -28,7 +31,9 @@ function(find_python_module module)
         set(options REQUIRED)
         set(single_vals "")
         set(multi_vals "")
-        cmake_parse_arguments(PYMODULE "${options}" "${single_vals}" "${multi_vals}" ${ARGN})
+        cmake_parse_arguments(
+                PYMODULE "${options}" "${single_vals}" "${multi_vals}" ${ARGN}
+        )
 
         string(TOUPPER ${module} module_upper)
         if(NOT PY_${module_upper})
@@ -37,13 +42,13 @@ function(find_python_module module)
                 endif()
                 # A module's location is usually a directory, but for binary modules
                 # it's a .so file.
-                execute_process(COMMAND "${Python3_EXECUTABLE}" "-c" 
+                execute_process(COMMAND "${Python3_EXECUTABLE}" "-c"
                         "import re, ${module}; print(re.compile('/__init__.py.*').sub('',${module}.__file__))"
-                        RESULT_VARIABLE _${module}_status 
+                        RESULT_VARIABLE _${module}_status
                         OUTPUT_VARIABLE _${module}_location
                         ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
                 if(NOT _${module}_status)
-                        set(PY_${module_upper} ${_${module}_location} CACHE STRING 
+                        set(PY_${module_upper} ${_${module}_location} CACHE STRING
                                 "Location of Python module ${module}")
                 endif(NOT _${module}_status)
         endif(NOT PY_${module_upper})
diff --git a/cmake/python_virt_env.cmake b/cmake/python_virt_env.cmake
new file mode 100644
index 00000000..9ce24fc4
--- /dev/null
+++ b/cmake/python_virt_env.cmake
@@ -0,0 +1,52 @@
+# Copyright 2021 CMakePP
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+include_guard()
+
+#[[[
+# Wraps the process of creating a virtual Python environment.
+#
+# :param mpv_exe: The Python interpreter to use to make the virtual environment
+# :param mpv_venv: The full path to the virtual environment
+#]]
+function(make_python_venv mpv_exe mpv_venv)
+    message(STATUS "Creating Python virtual environment: ${mpv_venv}")
+    execute_process(
+        COMMAND "${mpv_exe}" "-m" "venv" "${mpv_venv}"
+        RESULT_VARIABLE _venv_status
+        ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE
+    )
+
+    if (NOT _venv_status EQUAL "0")
+        string(
+            CONCAT error_msg "Virtual environment could not be created, venv "
+                             "returned ${_venv_status}"
+        )
+        message(FATAL_ERROR "${error_msg}")
+    endif()
+endfunction()
+
+#[[[
+# Wraps the process of installing a Python module into a virtual Python
+# environment, via pip.
+#
+# :param pvpi_venv: The full path to the Python virtual environment into which
+#                   we are installing the Python module.
+# :param pvpi_module: The name of the pip package we are installing.
+#
+#]]
+function(python_venv_pip_install pvpi_venv)
+    set(_pvpi_pip ${pvpi_venv}/bin/pip3)
+    execute_process(COMMAND "${_pvpi_pip}" "install" ${ARGN})
+endfunction()
diff --git a/cmake/templates/CMinxConfigVersion.cmake b/cmake/templates/cminx-config.cmake.in
similarity index 53%
rename from cmake/templates/CMinxConfigVersion.cmake
rename to cmake/templates/cminx-config.cmake.in
index a5bc913e..beafe91b 100644
--- a/cmake/templates/CMinxConfigVersion.cmake
+++ b/cmake/templates/cminx-config.cmake.in
@@ -12,12 +12,25 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-if(PACKAGE_FIND_NAME STREQUAL "CMinx")
+@PACKAGE_INIT@
 
-	if(PACKAGE_FIND_VERSION STREQUAL "@CMINX_VERSION@")
-		set(PACKAGE_VERSION_EXACT TRUE)
-		set(PACKAGE_VERSION_COMPATIBLE TRUE)
-	endif()
-	set(PACKAGE_VERSION "@CMINX_VERSION@")
+# Find the CMinx binary
+set_and_check(CMINX_BIN_DIR "@PACKAGE_CMINX_BIN_DIR@")
+find_program(
+    CMINX_EXECUTABLE_FOUND cminx 
+    HINTS "${CMINX_BIN_DIR}"
+    REQUIRED
+)
+set(CMINX_EXECUTABLE ${CMINX_BIN_DIR}/cminx)
 
-endif()
+# Find the CMinx CMake module
+set_and_check(CMINX_CMAKE_DIR "@PACKAGE_CMINX_CMAKE_DIR@")
+find_file(
+    CMINX_CMAKE_MODULE_FOUND cminx.cmake 
+    HINTS "${CMINX_CMAKE_DIR}" 
+    REQUIRED
+)
+
+check_required_components(cminx)
+
+include("${CMINX_CMAKE_DIR}/cminx.cmake")
diff --git a/codecov.yml b/codecov.yml
index 69da0030..e77df36a 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -14,7 +14,7 @@
 
 # Ignore Antlr4-generated files
 ignore:
-  - "cminx/parser/CMakeVisitor.py"
-  - "cminx/parser/CMakeParser.py"
-  - "cminx/parser/CMakeListener.py"
-  - "cminx/parser/CMakeLexer.py"
+  - "src/cminx/parser/CMakeVisitor.py"
+  - "src/cminx/parser/CMakeParser.py"
+  - "src/cminx/parser/CMakeListener.py"
+  - "src/cminx/parser/CMakeLexer.py"
diff --git a/docs/source/installation.rst b/docs/source/installation.rst
index f6b01018..f833cb11 100644
--- a/docs/source/installation.rst
+++ b/docs/source/installation.rst
@@ -26,35 +26,29 @@ purely personal preference.
 Pythonically
 ************
 
-When installing CMinx Pythonically, we strongly recommend you use a virtual 
-environment. To obtain the CMinx source code and setup the virtual environment
-run the following commands, one at a time, in a terminal:
+Unless you are developing CMinx we recommend you install CMinx from PyPI.
+From a terminal this is simply:
 
 .. code:: console
 
-   foo@bar:~$ git clone https://github.com/CMakePP/CMinx.git
-   foo@bar:~$ cd CMinx/
-   foo@bar:~/CMinx$ python3 -m venv virt-env #Create our virtual environment
-   foo@bar:~/CMinx$ source virt-env/bin/activate #Activate virtual environment
-
-To finish off the installation using PIP run:
-
-.. code:: console
-
-   (virt-env) foo@bar:~/CMinx$ pip3 install wheel
-   (virt-env) foo@bar:~/CMinx$ pip3 install .
+   foo@bar:~$ pip install CMinx
 
-or if you prefer to install without PIP run:
+If you are developing CMinx then we recommend doing an editable install of
+CMinx in a virtual environment:
 
 .. code:: console
 
-   (virt-env) foo@bar:~/CMinx$ python3 setup.py install #If pip not installed
+   foo@bar:~$ git clone https://github.com/CMakePP/CMinx.git
+   foo@bar:~$ cd CMinx/
+   foo@bar:~/CMinx$ python3 -m venv virt-env #Create our virtual environment
+   foo@bar:~/CMinx$ source virt-env/bin/activate #Activate virtual environment
+   (virt-env) foo@bar:~/CMinx$ pip3 install -e .
 
 **********
 With CMake
 **********
 
-Installing CMinx with CMake follows the usual CMake workflow, namely, run the 
+Installing CMinx with CMake follows the usual CMake workflow, namely, run the
 following commands, one at a time, in a terminal:
 
 .. code:: console
@@ -73,4 +67,4 @@ The CMake build of CMinx honors relevant CMake variables. In particular:
 CMAKE_INSTALL_PREFIX
    Can be used to control where CMinx is installed. It is recommended that users
    use full paths. Given the path `/where/cminx/should/go` the actual CMinx
-   executable will live at `/where/cminx/should/go/bin/cminx`
\ No newline at end of file
+   executable will live at `/where/cminx/should/go/bin/cminx`
diff --git a/cmake/templates/CMinxConfig.cmake b/pyproject.toml
similarity index 71%
rename from cmake/templates/CMinxConfig.cmake
rename to pyproject.toml
index 7fdaa8b9..c14df246 100644
--- a/cmake/templates/CMinxConfig.cmake
+++ b/pyproject.toml
@@ -12,9 +12,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-include(cminx)
+[build-system]
+requires = [ "setuptools>=41", "wheel", "setuptools-git-versioning", ]
+build-backend = "setuptools.build_meta"
 
-set(CMINX_VENV "@CMINX_VENV@")
-set(CMINX_VENV_PYTHON_EXECUTABLE "@CMINX_VENV_PYTHON_EXECUTABLE@")
-set(CMINX_VENV_PIP "@CMINX_VENV_PIP@")
-set(CMINX_SRC "@CMINX_SRC@")
+[tool.setuptools-git-versioning]
+enabled = true
+dev_template = "{tag}"
+dirty_template = "{tag}"
diff --git a/requirements.txt b/requirements.txt
index c680afb1..13a6115a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,4 @@
-antlr4-python3-runtime==4.7.2
-confuse>=1.7
-pathspec>=0.9.0
+coverage
+docutils>=0.10
+pytest
+pytest-cov
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 00000000..4d166054
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,36 @@
+[metadata]
+name = CMinx
+url = https://github.com/CMakePP/CMinx
+license = Apache 2.0
+license_files = LICENSE
+description = Documentation Generator for the CMake Language
+long_description = file: README.rst
+long_description_content_type = text/x-rst
+author = CMakePP
+keywords =
+    CMake
+    Documentaion
+classifiers=
+    Development Status :: 4 - Beta
+    License :: OSI Approved :: Apache Software License
+    Programming Language :: Python :: 3 :: Only
+    Topic :: Documentation
+    Topic :: Software Development :: Documentation
+    Topic :: Software Development :: Build Tools
+
+[options]
+packages = find:
+package_dir =
+    = src
+include_package_data = True
+install_requires =
+    antlr4-python3-runtime==4.7.2
+    confuse>=1.7
+    pathspec>=0.9.0
+
+[options.packages.find]
+where=src
+
+[options.entry_points]
+console_scripts =
+    cminx = cminx:main
diff --git a/setup.py b/setup.py
index 57a07c2f..1483cf3b 100755
--- a/setup.py
+++ b/setup.py
@@ -14,33 +14,6 @@
 # limitations under the License.
 #
 
-import os
-import subprocess
 from setuptools import setup
 
-# Get the version from the git tag
-git_cmd = ['git', 'describe', '--tags', '--abbrev=0']
-version = subprocess.check_output(git_cmd).strip().decode()[1:]
-
-# This will fail if requirements.txt not found, as it should
-with open("requirements.txt", "r") as f:
-    dependencies = f.read().split()
-
-setup(
-    name='CMinx',
-    version=version,
-    description='Documentation Generator for the CMake language',
-    author='Branden Butler',
-    author_email='bwtbutler@hotmail.com',
-    url='https =//github.com/CMakePP/CMinx',
-    packages=['cminx', 'cminx.parser'],
-    entry_points={
-        'console_scripts': [
-            'cminx = cminx:main',
-        ],
-    },
-    package_data={
-        "": ["*.g4", "*.interp", "*.tokens", "*.yaml"]
-    },
-    install_requires=dependencies
-)
+setup()
diff --git a/cminx/__init__.py b/src/cminx/__init__.py
similarity index 99%
rename from cminx/__init__.py
rename to src/cminx/__init__.py
index 7a037719..5805db03 100644
--- a/cminx/__init__.py
+++ b/src/cminx/__init__.py
@@ -126,6 +126,7 @@ def main(args: List[str] = tuple(sys.argv[1:])):
 
     args = parser.parse_args(args)
     settings = Configuration("cminx", __name__)
+
     if args.settings is not None:
         settings.set_file(os.path.abspath(args.settings))
 
diff --git a/cminx/config.py b/src/cminx/config.py
similarity index 100%
rename from cminx/config.py
rename to src/cminx/config.py
diff --git a/cminx/config_default.yaml b/src/cminx/config_default.yaml
similarity index 100%
rename from cminx/config_default.yaml
rename to src/cminx/config_default.yaml
diff --git a/cminx/documenter.py b/src/cminx/documenter.py
similarity index 100%
rename from cminx/documenter.py
rename to src/cminx/documenter.py
diff --git a/cminx/parser/CMake.g4 b/src/cminx/parser/CMake.g4
similarity index 100%
rename from cminx/parser/CMake.g4
rename to src/cminx/parser/CMake.g4
diff --git a/cminx/parser/CMake.interp b/src/cminx/parser/CMake.interp
similarity index 100%
rename from cminx/parser/CMake.interp
rename to src/cminx/parser/CMake.interp
diff --git a/cminx/parser/CMake.tokens b/src/cminx/parser/CMake.tokens
similarity index 100%
rename from cminx/parser/CMake.tokens
rename to src/cminx/parser/CMake.tokens
diff --git a/cminx/parser/CMakeLexer.interp b/src/cminx/parser/CMakeLexer.interp
similarity index 100%
rename from cminx/parser/CMakeLexer.interp
rename to src/cminx/parser/CMakeLexer.interp
diff --git a/cminx/parser/CMakeLexer.py b/src/cminx/parser/CMakeLexer.py
similarity index 100%
rename from cminx/parser/CMakeLexer.py
rename to src/cminx/parser/CMakeLexer.py
diff --git a/cminx/parser/CMakeLexer.tokens b/src/cminx/parser/CMakeLexer.tokens
similarity index 100%
rename from cminx/parser/CMakeLexer.tokens
rename to src/cminx/parser/CMakeLexer.tokens
diff --git a/cminx/parser/CMakeListener.py b/src/cminx/parser/CMakeListener.py
similarity index 100%
rename from cminx/parser/CMakeListener.py
rename to src/cminx/parser/CMakeListener.py
diff --git a/cminx/parser/CMakeParser.py b/src/cminx/parser/CMakeParser.py
similarity index 100%
rename from cminx/parser/CMakeParser.py
rename to src/cminx/parser/CMakeParser.py
diff --git a/cminx/parser/CMakeVisitor.py b/src/cminx/parser/CMakeVisitor.py
similarity index 100%
rename from cminx/parser/CMakeVisitor.py
rename to src/cminx/parser/CMakeVisitor.py
diff --git a/cminx/parser/__init__.py b/src/cminx/parser/__init__.py
similarity index 100%
rename from cminx/parser/__init__.py
rename to src/cminx/parser/__init__.py
diff --git a/cminx/parser/aggregator.py b/src/cminx/parser/aggregator.py
similarity index 100%
rename from cminx/parser/aggregator.py
rename to src/cminx/parser/aggregator.py
diff --git a/cminx/rstwriter.py b/src/cminx/rstwriter.py
similarity index 100%
rename from cminx/rstwriter.py
rename to src/cminx/rstwriter.py
diff --git a/main.py b/src/main.py
old mode 100755
new mode 100644
similarity index 72%
rename from main.py
rename to src/main.py
index bef2de1e..2d6adcd0
--- a/main.py
+++ b/src/main.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python3
+
 # Copyright 2021 CMakePP
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,9 +15,12 @@
 #
 
 """
-Convenience script to call cminx.main()
+If CMinx is built with CMake, we use PyInstaller to create an executable.
+PyInstaller requires us to import CMinx so it can discover the contents of
+CMinx. This file imports CMinx and calls its entry point so PyInstaller can
+do its thing.
 
-:Author: Branden Butler
+:Author: CMakePP
 :License: Apache 2.0
 """
 


Test patch:
diff --git a/tests/.coveragerc b/tests/.coveragerc
index d3147bff..b6b9d94c 100644
--- a/tests/.coveragerc
+++ b/tests/.coveragerc
@@ -1,2 +1,2 @@
 [run]
-include=../cminx/*
+include=../src/cminx/*
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index bfe8b4d9..5d5a13c7 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -18,7 +18,7 @@ ct_set_print_length("${CT_DEFAULT_PRINT_LENGTH}")
 
 ct_add_dir(
     "cmake"
-    CMAKE_OPTIONS "-DCMINX_PACKAGE_SEARCH_PATH=${CMINX_PACKAGE_SEARCH_PATH}"
+    CMAKE_OPTIONS "-DCMINX_STAGE_DIR=${CMINX_STAGE_DIR}"
                   "-DPROJECT_VERSION=${PROJECT_VERSION}"
 )
 
@@ -28,6 +28,6 @@ ct_add_dir(
 
 add_test(
     NAME "CMinx_Unit_Tests"
-    COMMAND "${CMINX_VENV_PYTHON_EXECUTABLE}" "-B" "test_all.py"
+    COMMAND "${CMINX_VENV}/bin/python3" "-B" "test_all.py"
     WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}"
 )
diff --git a/tests/cmake/gen_rst.cmake b/tests/cmake/gen_rst.cmake
index cb4845d2..128038e6 100644
--- a/tests/cmake/gen_rst.cmake
+++ b/tests/cmake/gen_rst.cmake
@@ -1,11 +1,10 @@
 find_package(CMinx
              ${PROJECT_VERSION}
-             PATHS "${CMINX_PACKAGE_SEARCH_PATH}"
+             PATHS "${CMINX_STAGE_DIR}"
              NO_DEFAULT_PATH
              REQUIRED
              CONFIG
 )
-include(cminx)
 
 set(cwd "${CMAKE_CURRENT_LIST_DIR}")
 
@@ -46,7 +45,6 @@ function(${test_gen_rst})
   function(${test_document})
     do_setup()
     cminx_gen_rst("${input_file}" "${output_dir}")
-
     ct_assert_file_exists("${output_file}")
 
     do_teardown()
@@ -56,7 +54,7 @@ function(${test_gen_rst})
   ct_add_section(NAME test_document_prefix)
   function(${test_document_prefix})
     do_setup()
-    cminx_gen_rst("${input_file}" "${output_dir}" "input")
+    cminx_gen_rst("${input_file}" "${output_dir}" "-p" "input")
 
     ct_assert_file_exists("${output_file}")
 
diff --git a/tests/requirements.txt b/tests/requirements.txt
deleted file mode 100644
index 13a6115a..00000000
--- a/tests/requirements.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-coverage
-docutils>=0.10
-pytest
-pytest-cov