├── .gitignore ├── LICENSE ├── MANIFEST.in ├── docs ├── Makefile ├── make.bat ├── make_docs.sh ├── presentations │ ├── pybay_2016_james_abel_osnap.pdf │ └── sf_python_sep_2016_james_abel_osnap.pdf ├── readme.txt └── source │ ├── announcements.rst │ ├── conf.py │ ├── example.rst │ ├── help.rst │ ├── index.rst │ ├── license.rst │ ├── limitations.rst │ ├── ossupport.rst │ ├── quickstart.rst │ ├── relatedtools.rst │ ├── requirements.rst │ └── userguide.rst ├── icons ├── icon.icns ├── icon.png ├── icon64x64.ico ├── make_mac_icon.sh └── sources.txt ├── launchers ├── __init__.py ├── dist2zip.py ├── icon.icns ├── icon32x32.ico ├── launch.py ├── make_launcher.bat ├── make_launcher.ps1 ├── make_launcher.sh ├── make_venv.bat ├── make_venv.sh ├── requirements.txt ├── set_path.bat ├── setup.py └── util.py ├── make_dist.sh ├── make_venv.bat ├── make_venv.sh ├── osnap ├── __init__.py ├── installer.py ├── installer_base.py ├── installer_mac.py ├── installer_win.py ├── launchmac.zip ├── launchwin-amd64-window.zip ├── launchwin-x86-console.zip ├── launchwin-x86-window.zip ├── logger.py ├── make_nsis.py ├── make_pkgproj.py ├── osnapy.py ├── osnapy_base.py ├── osnapy_mac.py ├── osnapy_win.py ├── template.pkgproj └── util.py ├── pypi.sh ├── readme.rst ├── requirements.txt ├── setup.cfg ├── setup.py ├── test_example ├── LICENSE ├── all.bat ├── all.sh ├── icons │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ └── source.txt ├── install_osnap.bat ├── install_osnap.sh ├── main.py ├── main.sh ├── make_installer.bat ├── make_installer.py ├── make_installer.sh ├── make_osnapy.bat ├── make_osnapy.py ├── make_osnapy.sh ├── make_venv.bat ├── make_venv.sh ├── readme.txt ├── requirements.txt ├── run_app.sh ├── test_example.ico ├── test_example.pkgproj ├── test_example │ ├── __init__.py │ └── mymodule.py ├── test_osnap.py ├── test_run.bat └── test_run.sh └── testpypi.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | 6 | # JCA 7 | # *.py[cod] 8 | *.py[co] 9 | 10 | *$py.class 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | env/ 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *,cover 52 | .hypothesis/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # IPython Notebook 76 | .ipynb_checkpoints 77 | 78 | # pyenv 79 | .python-version 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # dotenv 85 | .env 86 | 87 | # virtualenv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | ### JetBrains template 97 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 98 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 99 | 100 | # User-specific stuff: 101 | .idea/workspace.xml 102 | .idea/tasks.xml 103 | .idea/dictionaries 104 | .idea/vcs.xml 105 | .idea/jsLibraryMappings.xml 106 | 107 | # Sensitive or high-churn files: 108 | .idea/dataSources.ids 109 | .idea/dataSources.xml 110 | .idea/dataSources.local.xml 111 | .idea/sqlDataSources.xml 112 | .idea/dynamic.xml 113 | .idea/uiDesigner.xml 114 | 115 | # Gradle: 116 | .idea/gradle.xml 117 | .idea/libraries 118 | 119 | # Mongo Explorer plugin: 120 | .idea/mongoSettings.xml 121 | 122 | ## File-based project format: 123 | *.iws 124 | 125 | ## Plugin-specific files: 126 | 127 | # IntelliJ 128 | /out/ 129 | 130 | # mpeltonen/sbt-idea plugin 131 | .idea_modules/ 132 | 133 | # JIRA plugin 134 | atlassian-ide-plugin.xml 135 | 136 | # Crashlytics plugin (for Android Studio and IntelliJ) 137 | com_crashlytics_export_strings.xml 138 | crashlytics.properties 139 | crashlytics-build.properties 140 | fabric.properties 141 | 142 | .idea/ 143 | 144 | # JCA 145 | repo_ignore 146 | venv 147 | temp 148 | history 149 | installers 150 | cache 151 | osnappython 152 | _build_.py 153 | *.nsis 154 | ipch 155 | *.db 156 | *.opendb 157 | *.user 158 | *.tlog 159 | *.ilk 160 | *.pch 161 | *.pdb 162 | *.idb 163 | stdafx.obj 164 | launch.obj 165 | test_example/*.exe 166 | !launchers/win/Debug/launch.exe 167 | osnapy 168 | build 169 | launchers/launchmac 170 | launchers/launchwin 171 | test_example/launchmac 172 | test_example/launchwin 173 | .eggs 174 | *.dmg 175 | .DS_Store 176 | temp.bat 177 | temp*.sh 178 | 179 | # we get this dynamically 180 | install-pyrun.sh 181 | 182 | # this is merely copied from LICENSE 183 | LICENSE.txt 184 | 185 | # generated from readme.rst 186 | readme.md 187 | 188 | # windows app is built here 189 | osnapp 190 | 191 | build_* 192 | 193 | # windows launcher is just a hard link to the regular (console) launcher code so do not put the windows version in repo 194 | launchers/launch.pyw 195 | all.bat 196 | pypi.bat 197 | msvc/ 198 | msvc_downloads/ 199 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 James Abel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # note that this does not include examples and tests - see the source repo for that 2 | include setup.py 3 | include .gitignore 4 | include *.md 5 | include *.rst 6 | include *.txt 7 | include osnap/*.pkgproj 8 | include LICENSE 9 | include MANIFEST.in 10 | include osnap/*.py 11 | include osnap/*.zip 12 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | 7 | # jca 8 | # SPHINXBUILD = sphinx-build 9 | SPHINXBUILD = ../venv/bin/sphinx-build 10 | 11 | PAPER = 12 | BUILDDIR = build 13 | 14 | # Internal variables. 15 | PAPEROPT_a4 = -D latex_paper_size=a4 16 | PAPEROPT_letter = -D latex_paper_size=letter 17 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 18 | # the i18n builder cannot share the environment and doctrees with the others 19 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 20 | 21 | .PHONY: help 22 | help: 23 | @echo "Please use \`make ' where is one of" 24 | @echo " html to make standalone HTML files" 25 | @echo " dirhtml to make HTML files named index.html in directories" 26 | @echo " singlehtml to make a single large HTML file" 27 | @echo " pickle to make pickle files" 28 | @echo " json to make JSON files" 29 | @echo " htmlhelp to make HTML files and a HTML help project" 30 | @echo " qthelp to make HTML files and a qthelp project" 31 | @echo " applehelp to make an Apple Help Book" 32 | @echo " devhelp to make HTML files and a Devhelp project" 33 | @echo " epub to make an epub" 34 | @echo " epub3 to make an epub3" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | @echo " coverage to run coverage check of the documentation (if enabled)" 49 | @echo " dummy to check syntax errors of document sources" 50 | 51 | .PHONY: clean 52 | clean: 53 | rm -rf $(BUILDDIR)/* 54 | 55 | .PHONY: html 56 | html: 57 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 58 | @echo 59 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 60 | 61 | .PHONY: dirhtml 62 | dirhtml: 63 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 64 | @echo 65 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 66 | 67 | .PHONY: singlehtml 68 | singlehtml: 69 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 70 | @echo 71 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 72 | 73 | .PHONY: pickle 74 | pickle: 75 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 76 | @echo 77 | @echo "Build finished; now you can process the pickle files." 78 | 79 | .PHONY: json 80 | json: 81 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 82 | @echo 83 | @echo "Build finished; now you can process the JSON files." 84 | 85 | .PHONY: htmlhelp 86 | htmlhelp: 87 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 88 | @echo 89 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 90 | ".hhp project file in $(BUILDDIR)/htmlhelp." 91 | 92 | .PHONY: qthelp 93 | qthelp: 94 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 95 | @echo 96 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 97 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 98 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/osnap.qhcp" 99 | @echo "To view the help file:" 100 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/osnap.qhc" 101 | 102 | .PHONY: applehelp 103 | applehelp: 104 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 105 | @echo 106 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 107 | @echo "N.B. You won't be able to view it unless you put it in" \ 108 | "~/Library/Documentation/Help or install it in your application" \ 109 | "bundle." 110 | 111 | .PHONY: devhelp 112 | devhelp: 113 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 114 | @echo 115 | @echo "Build finished." 116 | @echo "To view the help file:" 117 | @echo "# mkdir -p $$HOME/.local/share/devhelp/osnap" 118 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/osnap" 119 | @echo "# devhelp" 120 | 121 | .PHONY: epub 122 | epub: 123 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 124 | @echo 125 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 126 | 127 | .PHONY: epub3 128 | epub3: 129 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 130 | @echo 131 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 132 | 133 | .PHONY: latex 134 | latex: 135 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 136 | @echo 137 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 138 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 139 | "(use \`make latexpdf' here to do that automatically)." 140 | 141 | .PHONY: latexpdf 142 | latexpdf: 143 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 144 | @echo "Running LaTeX files through pdflatex..." 145 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 146 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 147 | 148 | .PHONY: latexpdfja 149 | latexpdfja: 150 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 151 | @echo "Running LaTeX files through platex and dvipdfmx..." 152 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 153 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 154 | 155 | .PHONY: text 156 | text: 157 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 158 | @echo 159 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 160 | 161 | .PHONY: man 162 | man: 163 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 164 | @echo 165 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 166 | 167 | .PHONY: texinfo 168 | texinfo: 169 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 170 | @echo 171 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 172 | @echo "Run \`make' in that directory to run these through makeinfo" \ 173 | "(use \`make info' here to do that automatically)." 174 | 175 | .PHONY: info 176 | info: 177 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 178 | @echo "Running Texinfo files through makeinfo..." 179 | make -C $(BUILDDIR)/texinfo info 180 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 181 | 182 | .PHONY: gettext 183 | gettext: 184 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 185 | @echo 186 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 187 | 188 | .PHONY: changes 189 | changes: 190 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 191 | @echo 192 | @echo "The overview file is in $(BUILDDIR)/changes." 193 | 194 | .PHONY: linkcheck 195 | linkcheck: 196 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 197 | @echo 198 | @echo "Link check complete; look for any errors in the above output " \ 199 | "or in $(BUILDDIR)/linkcheck/output.txt." 200 | 201 | .PHONY: doctest 202 | doctest: 203 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 204 | @echo "Testing of doctests in the sources finished, look at the " \ 205 | "results in $(BUILDDIR)/doctest/output.txt." 206 | 207 | .PHONY: coverage 208 | coverage: 209 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 210 | @echo "Testing of coverage in the sources finished, look at the " \ 211 | "results in $(BUILDDIR)/coverage/python.txt." 212 | 213 | .PHONY: xml 214 | xml: 215 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 216 | @echo 217 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 218 | 219 | .PHONY: pseudoxml 220 | pseudoxml: 221 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 222 | @echo 223 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 224 | 225 | .PHONY: dummy 226 | dummy: 227 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 228 | @echo 229 | @echo "Build finished. Dummy builder generates no files." 230 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% source 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. epub3 to make an epub3 31 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 32 | echo. text to make text files 33 | echo. man to make manual pages 34 | echo. texinfo to make Texinfo files 35 | echo. gettext to make PO message catalogs 36 | echo. changes to make an overview over all changed/added/deprecated items 37 | echo. xml to make Docutils-native XML files 38 | echo. pseudoxml to make pseudoxml-XML files for display purposes 39 | echo. linkcheck to check all external links for integrity 40 | echo. doctest to run all doctests embedded in the documentation if enabled 41 | echo. coverage to run coverage check of the documentation if enabled 42 | echo. dummy to check syntax errors of document sources 43 | goto end 44 | ) 45 | 46 | if "%1" == "clean" ( 47 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 48 | del /q /s %BUILDDIR%\* 49 | goto end 50 | ) 51 | 52 | 53 | REM Check if sphinx-build is available and fallback to Python version if any 54 | %SPHINXBUILD% 1>NUL 2>NUL 55 | if errorlevel 9009 goto sphinx_python 56 | goto sphinx_ok 57 | 58 | :sphinx_python 59 | 60 | set SPHINXBUILD=python -m sphinx.__init__ 61 | %SPHINXBUILD% 2> nul 62 | if errorlevel 9009 ( 63 | echo. 64 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 65 | echo.installed, then set the SPHINXBUILD environment variable to point 66 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 67 | echo.may add the Sphinx directory to PATH. 68 | echo. 69 | echo.If you don't have Sphinx installed, grab it from 70 | echo.http://sphinx-doc.org/ 71 | exit /b 1 72 | ) 73 | 74 | :sphinx_ok 75 | 76 | 77 | if "%1" == "html" ( 78 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 79 | if errorlevel 1 exit /b 1 80 | echo. 81 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 82 | goto end 83 | ) 84 | 85 | if "%1" == "dirhtml" ( 86 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 87 | if errorlevel 1 exit /b 1 88 | echo. 89 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 90 | goto end 91 | ) 92 | 93 | if "%1" == "singlehtml" ( 94 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 95 | if errorlevel 1 exit /b 1 96 | echo. 97 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 98 | goto end 99 | ) 100 | 101 | if "%1" == "pickle" ( 102 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 103 | if errorlevel 1 exit /b 1 104 | echo. 105 | echo.Build finished; now you can process the pickle files. 106 | goto end 107 | ) 108 | 109 | if "%1" == "json" ( 110 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 111 | if errorlevel 1 exit /b 1 112 | echo. 113 | echo.Build finished; now you can process the JSON files. 114 | goto end 115 | ) 116 | 117 | if "%1" == "htmlhelp" ( 118 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 119 | if errorlevel 1 exit /b 1 120 | echo. 121 | echo.Build finished; now you can run HTML Help Workshop with the ^ 122 | .hhp project file in %BUILDDIR%/htmlhelp. 123 | goto end 124 | ) 125 | 126 | if "%1" == "qthelp" ( 127 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 128 | if errorlevel 1 exit /b 1 129 | echo. 130 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 131 | .qhcp project file in %BUILDDIR%/qthelp, like this: 132 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\osnap.qhcp 133 | echo.To view the help file: 134 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\osnap.ghc 135 | goto end 136 | ) 137 | 138 | if "%1" == "devhelp" ( 139 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 140 | if errorlevel 1 exit /b 1 141 | echo. 142 | echo.Build finished. 143 | goto end 144 | ) 145 | 146 | if "%1" == "epub" ( 147 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 148 | if errorlevel 1 exit /b 1 149 | echo. 150 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 151 | goto end 152 | ) 153 | 154 | if "%1" == "epub3" ( 155 | %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 156 | if errorlevel 1 exit /b 1 157 | echo. 158 | echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. 159 | goto end 160 | ) 161 | 162 | if "%1" == "latex" ( 163 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 164 | if errorlevel 1 exit /b 1 165 | echo. 166 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdf" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "latexpdfja" ( 181 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 182 | cd %BUILDDIR%/latex 183 | make all-pdf-ja 184 | cd %~dp0 185 | echo. 186 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 187 | goto end 188 | ) 189 | 190 | if "%1" == "text" ( 191 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 192 | if errorlevel 1 exit /b 1 193 | echo. 194 | echo.Build finished. The text files are in %BUILDDIR%/text. 195 | goto end 196 | ) 197 | 198 | if "%1" == "man" ( 199 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 200 | if errorlevel 1 exit /b 1 201 | echo. 202 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 203 | goto end 204 | ) 205 | 206 | if "%1" == "texinfo" ( 207 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 208 | if errorlevel 1 exit /b 1 209 | echo. 210 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 211 | goto end 212 | ) 213 | 214 | if "%1" == "gettext" ( 215 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 216 | if errorlevel 1 exit /b 1 217 | echo. 218 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 219 | goto end 220 | ) 221 | 222 | if "%1" == "changes" ( 223 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 224 | if errorlevel 1 exit /b 1 225 | echo. 226 | echo.The overview file is in %BUILDDIR%/changes. 227 | goto end 228 | ) 229 | 230 | if "%1" == "linkcheck" ( 231 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 232 | if errorlevel 1 exit /b 1 233 | echo. 234 | echo.Link check complete; look for any errors in the above output ^ 235 | or in %BUILDDIR%/linkcheck/output.txt. 236 | goto end 237 | ) 238 | 239 | if "%1" == "doctest" ( 240 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 241 | if errorlevel 1 exit /b 1 242 | echo. 243 | echo.Testing of doctests in the sources finished, look at the ^ 244 | results in %BUILDDIR%/doctest/output.txt. 245 | goto end 246 | ) 247 | 248 | if "%1" == "coverage" ( 249 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 250 | if errorlevel 1 exit /b 1 251 | echo. 252 | echo.Testing of coverage in the sources finished, look at the ^ 253 | results in %BUILDDIR%/coverage/python.txt. 254 | goto end 255 | ) 256 | 257 | if "%1" == "xml" ( 258 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 259 | if errorlevel 1 exit /b 1 260 | echo. 261 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 262 | goto end 263 | ) 264 | 265 | if "%1" == "pseudoxml" ( 266 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 267 | if errorlevel 1 exit /b 1 268 | echo. 269 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 270 | goto end 271 | ) 272 | 273 | if "%1" == "dummy" ( 274 | %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy 275 | if errorlevel 1 exit /b 1 276 | echo. 277 | echo.Build finished. Dummy builder generates no files. 278 | goto end 279 | ) 280 | 281 | :end 282 | -------------------------------------------------------------------------------- /docs/make_docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | make html 3 | -------------------------------------------------------------------------------- /docs/presentations/pybay_2016_james_abel_osnap.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesabel/osnap/fc3f2affc3190d91f0465c35971e8f877269d5fd/docs/presentations/pybay_2016_james_abel_osnap.pdf -------------------------------------------------------------------------------- /docs/presentations/sf_python_sep_2016_james_abel_osnap.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesabel/osnap/fc3f2affc3190d91f0465c35971e8f877269d5fd/docs/presentations/sf_python_sep_2016_james_abel_osnap.pdf -------------------------------------------------------------------------------- /docs/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | This is the osnap documentation directory. This is built and published at http://osnap.readthedocs.io/ . 3 | 4 | This is built with Sphinx, so see the documentation on Sphinx at http://www.sphinx-doc.org/ . 5 | -------------------------------------------------------------------------------- /docs/source/announcements.rst: -------------------------------------------------------------------------------- 1 | 2 | Announcements 3 | ============= 4 | 5 | 0.0.6 has been released. 6 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # osnap documentation build configuration file, created by 5 | # sphinx-quickstart on Sun Aug 28 21:08:50 2016. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | # import os 21 | # import sys 22 | # sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | # 28 | # needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | 'sphinx.ext.autodoc', 35 | 36 | # jca 37 | # 'sphinx.ext.githubpages', 38 | ] 39 | 40 | # Add any paths that contain templates here, relative to this directory. 41 | templates_path = ['_templates'] 42 | 43 | # The suffix(es) of source filenames. 44 | # You can specify multiple suffix as a list of string: 45 | # 46 | # source_suffix = ['.rst', '.md'] 47 | source_suffix = '.rst' 48 | 49 | # The encoding of source files. 50 | # 51 | # source_encoding = 'utf-8-sig' 52 | 53 | # The master toctree document. 54 | master_doc = 'index' 55 | 56 | # General information about the project. 57 | project = 'osnap' 58 | copyright = '2016, James Abel' 59 | author = 'James Abel' 60 | 61 | # The version info for the project you're documenting, acts as replacement for 62 | # |version| and |release|, also used in various other places throughout the 63 | # built documents. 64 | # 65 | # The short X.Y version. 66 | version = '0.0' 67 | # The full version, including alpha/beta/rc tags. 68 | release = '0.0' 69 | 70 | # The language for content autogenerated by Sphinx. Refer to documentation 71 | # for a list of supported languages. 72 | # 73 | # This is also used if you do content translation via gettext catalogs. 74 | # Usually you set "language" from the command line for these cases. 75 | language = None 76 | 77 | # There are two options for replacing |today|: either, you set today to some 78 | # non-false value, then it is used: 79 | # 80 | # today = '' 81 | # 82 | # Else, today_fmt is used as the format for a strftime call. 83 | # 84 | # today_fmt = '%B %d, %Y' 85 | 86 | # List of patterns, relative to source directory, that match files and 87 | # directories to ignore when looking for source files. 88 | # This patterns also effect to html_static_path and html_extra_path 89 | exclude_patterns = [] 90 | 91 | # The reST default role (used for this markup: `text`) to use for all 92 | # documents. 93 | # 94 | # default_role = None 95 | 96 | # If true, '()' will be appended to :func: etc. cross-reference text. 97 | # 98 | # add_function_parentheses = True 99 | 100 | # If true, the current module name will be prepended to all description 101 | # unit titles (such as .. function::). 102 | # 103 | # add_module_names = True 104 | 105 | # If true, sectionauthor and moduleauthor directives will be shown in the 106 | # output. They are ignored by default. 107 | # 108 | # show_authors = False 109 | 110 | # The name of the Pygments (syntax highlighting) style to use. 111 | pygments_style = 'sphinx' 112 | 113 | # A list of ignored prefixes for module index sorting. 114 | # modindex_common_prefix = [] 115 | 116 | # If true, keep warnings as "system message" paragraphs in the built documents. 117 | # keep_warnings = False 118 | 119 | # If true, `todo` and `todoList` produce output, else they produce nothing. 120 | todo_include_todos = False 121 | 122 | 123 | # -- Options for HTML output ---------------------------------------------- 124 | 125 | # The theme to use for HTML and HTML Help pages. See the documentation for 126 | # a list of builtin themes. 127 | # 128 | html_theme = 'alabaster' 129 | 130 | # Theme options are theme-specific and customize the look and feel of a theme 131 | # further. For a list of options available for each theme, see the 132 | # documentation. 133 | # 134 | # html_theme_options = {} 135 | 136 | # Add any paths that contain custom themes here, relative to this directory. 137 | # html_theme_path = [] 138 | 139 | # The name for this set of Sphinx documents. 140 | # " v documentation" by default. 141 | # 142 | # html_title = 'osnap v0.0' 143 | 144 | # A shorter title for the navigation bar. Default is the same as html_title. 145 | # 146 | # html_short_title = None 147 | 148 | # The name of an image file (relative to this directory) to place at the top 149 | # of the sidebar. 150 | # 151 | # html_logo = None 152 | 153 | # The name of an image file (relative to this directory) to use as a favicon of 154 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 155 | # pixels large. 156 | # 157 | # html_favicon = None 158 | 159 | # Add any paths that contain custom static files (such as style sheets) here, 160 | # relative to this directory. They are copied after the builtin static files, 161 | # so a file named "default.css" will overwrite the builtin "default.css". 162 | html_static_path = ['_static'] 163 | 164 | # Add any extra paths that contain custom files (such as robots.txt or 165 | # .htaccess) here, relative to this directory. These files are copied 166 | # directly to the root of the documentation. 167 | # 168 | # html_extra_path = [] 169 | 170 | # If not None, a 'Last updated on:' timestamp is inserted at every page 171 | # bottom, using the given strftime format. 172 | # The empty string is equivalent to '%b %d, %Y'. 173 | # 174 | # html_last_updated_fmt = None 175 | 176 | # If true, SmartyPants will be used to convert quotes and dashes to 177 | # typographically correct entities. 178 | # 179 | # html_use_smartypants = True 180 | 181 | # Custom sidebar templates, maps document names to template names. 182 | # 183 | # html_sidebars = {} 184 | 185 | # Additional templates that should be rendered to pages, maps page names to 186 | # template names. 187 | # 188 | # html_additional_pages = {} 189 | 190 | # If false, no module index is generated. 191 | # 192 | # html_domain_indices = True 193 | 194 | # If false, no index is generated. 195 | # 196 | # html_use_index = True 197 | 198 | # If true, the index is split into individual pages for each letter. 199 | # 200 | # html_split_index = False 201 | 202 | # If true, links to the reST sources are added to the pages. 203 | # 204 | # html_show_sourcelink = True 205 | 206 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 207 | # 208 | # html_show_sphinx = True 209 | 210 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 211 | # 212 | # html_show_copyright = True 213 | 214 | # If true, an OpenSearch description file will be output, and all pages will 215 | # contain a tag referring to it. The value of this option must be the 216 | # base URL from which the finished HTML is served. 217 | # 218 | # html_use_opensearch = '' 219 | 220 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 221 | # html_file_suffix = None 222 | 223 | # Language to be used for generating the HTML full-text search index. 224 | # Sphinx supports the following languages: 225 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 226 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' 227 | # 228 | # html_search_language = 'en' 229 | 230 | # A dictionary with options for the search language support, empty by default. 231 | # 'ja' uses this config value. 232 | # 'zh' user can custom change `jieba` dictionary path. 233 | # 234 | # html_search_options = {'type': 'default'} 235 | 236 | # The name of a javascript file (relative to the configuration directory) that 237 | # implements a search results scorer. If empty, the default will be used. 238 | # 239 | # html_search_scorer = 'scorer.js' 240 | 241 | # Output file base name for HTML help builder. 242 | htmlhelp_basename = 'osnapdoc' 243 | 244 | # -- Options for LaTeX output --------------------------------------------- 245 | 246 | latex_elements = { 247 | # The paper size ('letterpaper' or 'a4paper'). 248 | # 249 | # 'papersize': 'letterpaper', 250 | 251 | # The font size ('10pt', '11pt' or '12pt'). 252 | # 253 | # 'pointsize': '10pt', 254 | 255 | # Additional stuff for the LaTeX preamble. 256 | # 257 | # 'preamble': '', 258 | 259 | # Latex figure (float) alignment 260 | # 261 | # 'figure_align': 'htbp', 262 | } 263 | 264 | # Grouping the document tree into LaTeX files. List of tuples 265 | # (source start file, target name, title, 266 | # author, documentclass [howto, manual, or own class]). 267 | latex_documents = [ 268 | (master_doc, 'osnap.tex', 'osnap Documentation', 269 | 'James Abel', 'manual'), 270 | ] 271 | 272 | # The name of an image file (relative to this directory) to place at the top of 273 | # the title page. 274 | # 275 | # latex_logo = None 276 | 277 | # For "manual" documents, if this is true, then toplevel headings are parts, 278 | # not chapters. 279 | # 280 | # latex_use_parts = False 281 | 282 | # If true, show page references after internal links. 283 | # 284 | # latex_show_pagerefs = False 285 | 286 | # If true, show URL addresses after external links. 287 | # 288 | # latex_show_urls = False 289 | 290 | # Documents to append as an appendix to all manuals. 291 | # 292 | # latex_appendices = [] 293 | 294 | # It false, will not define \strong, \code, itleref, \crossref ... but only 295 | # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added 296 | # packages. 297 | # 298 | # latex_keep_old_macro_names = True 299 | 300 | # If false, no module index is generated. 301 | # 302 | # latex_domain_indices = True 303 | 304 | 305 | # -- Options for manual page output --------------------------------------- 306 | 307 | # One entry per manual page. List of tuples 308 | # (source start file, name, description, authors, manual section). 309 | man_pages = [ 310 | (master_doc, 'osnap', 'osnap Documentation', 311 | [author], 1) 312 | ] 313 | 314 | # If true, show URL addresses after external links. 315 | # 316 | # man_show_urls = False 317 | 318 | 319 | # -- Options for Texinfo output ------------------------------------------- 320 | 321 | # Grouping the document tree into Texinfo files. List of tuples 322 | # (source start file, target name, title, author, 323 | # dir menu entry, description, category) 324 | texinfo_documents = [ 325 | (master_doc, 'osnap', 'osnap Documentation', 326 | author, 'osnap', 'One line description of project.', 327 | 'Miscellaneous'), 328 | ] 329 | 330 | # Documents to append as an appendix to all manuals. 331 | # 332 | # texinfo_appendices = [] 333 | 334 | # If false, no module index is generated. 335 | # 336 | # texinfo_domain_indices = True 337 | 338 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 339 | # 340 | # texinfo_show_urls = 'footnote' 341 | 342 | # If true, do not generate a @detailmenu in the "Top" node's menu. 343 | # 344 | # texinfo_no_detailmenu = False 345 | -------------------------------------------------------------------------------- /docs/source/example.rst: -------------------------------------------------------------------------------- 1 | 2 | Example 3 | ======= 4 | 5 | See `test_example `_ for an example. Look over 6 | the make_*.* files. These should be used in this order: 7 | 8 | - make_venv.[sh | bat] - makes your Python virtual environment. This is typical Python usage and is **not** 9 | specific to ``OASNAP``. 10 | - make_osnapy.[sh | bat] - make the osnapy Python environment that will be bundled with your installation. 11 | - make_installer.[sh | bat] - makes the installer for the OS that it's running on. This is the final step in the 12 | process. 13 | 14 | -------------------------------------------------------------------------------- /docs/source/help.rst: -------------------------------------------------------------------------------- 1 | Help 2 | ==== 3 | 4 | I need somebody. 5 | 6 | Seriously, if you need help `readthedocs `_ or enter an issue 7 | at `github `_ . 8 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. osnap documentation master file, created by 2 | sphinx-quickstart on Sun Aug 28 21:08:50 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | OSNAP - Overly Simplistic Native Application tool for Python 7 | ============================================================ 8 | 9 | Table of Contents 10 | ----------------- 11 | 12 | .. toctree:: 13 | :maxdepth: 2 14 | 15 | announcements 16 | quickstart 17 | ossupport 18 | userguide 19 | example 20 | relatedtools 21 | requirements 22 | limitations 23 | license 24 | help 25 | 26 | 27 | 28 | Indices and tables 29 | ================== 30 | 31 | * :ref:`genindex` 32 | * :ref:`modindex` 33 | * :ref:`search` 34 | 35 | -------------------------------------------------------------------------------- /docs/source/license.rst: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2016 James Abel 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | 26 | -------------------------------------------------------------------------------- /docs/source/limitations.rst: -------------------------------------------------------------------------------- 1 | Limitations/Inefficiencies 2 | ========================== 3 | 4 | - Requires Python 3.5 and after. (``osnap`` uses the 5 | `embedded Python `_ for Windows, first 6 | provided in Python 3.5) 7 | - Since ``osnap`` includes everything a package installs, the resultant installation will generally be larger than the 8 | minimum actually required by the application. 9 | - The user has to specify all the required packages. There is no 'auto discovery'. Since auto discovery can be a 10 | source of issues, this is actually more of a feature. Also, this is derived from ``requirements.txt``, so the 11 | user should have this already anyway. 12 | -------------------------------------------------------------------------------- /docs/source/ossupport.rst: -------------------------------------------------------------------------------- 1 | 2 | OS Support 3 | ========== 4 | 5 | Background 6 | ---------- 7 | The philosophy around ``OSNAP`` is to bundle an embedded Python environment (interpreter) with your application. 8 | Windows and OSX/MacOS [1]_ are supported. However, currently the 9 | support for embedded Python for each OS is quite different from each other (hopefully in the future these 10 | will converge). 11 | 12 | Windows 13 | ------- 14 | In Python 3.5, `Steve Dower added an embedded Python zip `_ 15 | to the general distribution on python.org. This makes embedding Python in an application fairly straightforward. 16 | So, this is used directly by ``OSNAP``. 17 | 18 | OSX/MacOS 19 | --------- 20 | As of this writing there is no embedded Python for Mac in the general distribution. ``OSNAP`` has two techniques 21 | to fill this, each with their pros and cons: 22 | 23 | Compilation 24 | ^^^^^^^^^^^ 25 | This technique compiles Python as part of the creation of the Python environment (what's in the ``osnapy`` 26 | directory). Mac compliation of Python requires absolute path names, so we predetermine the path that ``osnapy`` 27 | will be on the end user's system - i.e. ``/Applications/.app/Contents/MacOS/osnapy/`` - and 28 | compile and "install" into that location. The pros/cons are: 29 | 30 | Pros: 31 | 32 | - This should be a complete solution since we have a regular Python environment : the Python interpreter, pip, etc. 33 | - All the tools are generally available and free. 34 | 35 | Cons: 36 | 37 | - We have to compile. 38 | - We need to install tools/libraries like XCode and OpenSSL. 39 | - There is always a chance that compilation doesn't work for some reason. 40 | - It's compiling (actually installing) into the /Applications directory, which requires root (sudo) for part of it. 41 | 42 | eGenix™ PyRun™ 43 | ^^^^^^^^^^^^^^ 44 | This uses `eGenix PyRun `_, which is essentially an embedded 45 | Python environment. The pros/cons are: 46 | 47 | Pros: 48 | 49 | - Prebuilt 50 | - Compact 51 | - Easy to use (like the Windows Embedded Python) 52 | 53 | Cons: 54 | 55 | - Is not necessarily 100% compatible with the general Python distribution. May not work with all packages. 56 | 57 | Current Default 58 | ^^^^^^^^^^^^^^^ 59 | In order to support the widest range of end user applications, currently the compilation technique is the default. 60 | 61 | .. [1] Here we are using OSX and MacOS interchangeably. -------------------------------------------------------------------------------- /docs/source/quickstart.rst: -------------------------------------------------------------------------------- 1 | 2 | OSNAP Quick Start 3 | ================= 4 | 5 | If you haven't done so already, create a `virtualenv `_ for your Python 6 | project. We will refer to your Python virtual environment directory as ``venv``. 7 | 8 | Install osnap from PyPI by executing ``pip install osnap``. Alternatively you can get osnap 9 | from `github `_ and install it into your ``venv`` by executing 10 | ``python setup.py install`` from the osnap directory. 11 | 12 | Create a `requirements.txt `_ file for your project. 13 | You can add to this as you go, but make sure you keep this up to date with all the packages you need for your project. 14 | 15 | Run ``venv/bin/python -m osnap.osnapy`` (or equivalent for the OS you're on). This should create an osnapy directory, 16 | which contains a complete stand-alone Python environment for your project. If you want a Python version different than 17 | the default, use the ``--python `` switch. 18 | 19 | Develop your Python application. 20 | 21 | Run ``venv/bin/python -m osnap.installer --author --app `` . This will 22 | create an installer for your application for the OS that you are running on. ``author name`` may be your name or your 23 | company's name. If you have spaces in a name, put the name in double quotes. 24 | 25 | -------------------------------------------------------------------------------- /docs/source/relatedtools.rst: -------------------------------------------------------------------------------- 1 | 2 | Related Tools 3 | ============= 4 | 5 | Several tools already exist in this space, such as: 6 | 7 | - `cx_freeze `_ 8 | - `py2exe `_ 9 | - `py2app `_ 10 | - `briefcase `_ 11 | - `pyinstaller `_ 12 | - `bbfreeze `_ 13 | - `pynsist `_ 14 | - `pyqtdeploy `_ 15 | 16 | However, ``osnap`` provides some specific features not found in these other tools: 17 | 18 | - Works on hard-to-freeze packages such as `cryptography `_. 19 | - Provides an executable file as the application's main invocation. This way it looks like a traditional native 20 | application to the OS. 21 | - Provides an installer (some tools only freeze the app and leave it up to the developer to do the installer). 22 | - The self-contained Python environment created for the installer can also be used by the developer during development 23 | and debug. This can help reduce issues encountered in the final user's machine. 24 | -------------------------------------------------------------------------------- /docs/source/requirements.rst: -------------------------------------------------------------------------------- 1 | 2 | Requirements 3 | ============ 4 | 5 | - Python 3.5+ . (OSNAP uses the embedded Python release for Windows, first provided on Python 3.5) 6 | - Windows: `NSIS `_ (Version 3.0+) . 7 | - Mac (OSX/MacOS): 8 | `XCode `_ . 9 | `OpenSSL `_ . 10 | `Packages `_ . 11 | - Python modules : requests , jinja2 . 12 | -------------------------------------------------------------------------------- /docs/source/userguide.rst: -------------------------------------------------------------------------------- 1 | 2 | User Guide 3 | ========== 4 | 5 | Introduction 6 | ------------ 7 | `OSNAP` essentially has two phases - the development phase and the installer phase. Freezing and installing 8 | applications can run into issues and it's best to proceed step by step. This is handy in case there are problems, so 9 | that debugging is a more reasonable task. 10 | 11 | Development Phase 12 | ^^^^^^^^^^^^^^^^^ 13 | In the development phase you create the Python environment that you use to run your code. You first create a 14 | traditional virtual Python environment. We use `venv` for this Python environment directory name. 15 | This is separate from using `OSNAP`, but it allows the programmer to first develop and test their application in the 16 | traditional way. 17 | 18 | The `OSNAP` package is installed into this `venv`. :: 19 | 20 | pip install osnap 21 | 22 | Once some basic application functionality is achieved it is recommended that `OSNAP` be used to create the 23 | `OSNAP` Python environment. This this is done by: :: 24 | 25 | venv/bin/python3 -m osnap.osnapy # e.g. for OSX 26 | 27 | This Python environment is in the `osnapy` directory (note the 'y' - osnapy is short for `OSNAP Python`). 28 | The `osnapy` directory is a separate and stand-alone Python environment that will be packaged up as part of the 29 | application delivered to end users. `requirements.txt` is used to determine what Python packages are installed 30 | into osnapy. The Python interpreter in `osnapy` should be used for testing to ensure functionality. 31 | 32 | Installation Phase 33 | ^^^^^^^^^^^^^^^^^^ 34 | In this phase we create the installer that you will deliver to end users. This is where your application, 35 | the `osnapy` Python environment, and the OS-specific launcher are combined into a complete application for a 36 | specific OS. 37 | 38 | Since there are several parameters given to the installer, you must create a small Python program like this: :: 39 | 40 | import osnap.installer 41 | import test_example 42 | 43 | APPLICATION_NAME = 'test_example' 44 | AUTHOR = 'test' 45 | 46 | def make_installer(verbose): 47 | osnap.installer.make_installer(AUTHOR, APPLICATION_NAME, 'this is my test example', 'www.mydomain.com', 48 | [APPLICATION_NAME], test_example.__version__, verbose=verbose) 49 | 50 | if __name__ == '__main__': 51 | make_installer(True) 52 | 53 | Run this program to create the appropriate installer for the OS you are running on. 54 | 55 | Application Layout 56 | ------------------ 57 | 58 | Lay out your Python application like this: :: 59 | 60 | - < project root > 61 | - main.py 62 | - my_project 63 | - < project files ... > 64 | 65 | At a minimum `main.py` must have this structure: :: 66 | 67 | def main(): 68 | < your code > 69 | 70 | if __name__ == '__main__': 71 | main() 72 | 73 | Typically your code will be contained in a separate package, so it will look more like: :: 74 | 75 | import my_application.my_module 76 | 77 | def main(): 78 | 79 | # initialization or argument processing code can go here ... 80 | 81 | my_application.my_module.run() 82 | 83 | if __name__ == '__main__': 84 | main() 85 | 86 | See the test_example code for a more complete example. 87 | -------------------------------------------------------------------------------- /icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesabel/osnap/fc3f2affc3190d91f0465c35971e8f877269d5fd/icons/icon.icns -------------------------------------------------------------------------------- /icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesabel/osnap/fc3f2affc3190d91f0465c35971e8f877269d5fd/icons/icon.png -------------------------------------------------------------------------------- /icons/icon64x64.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesabel/osnap/fc3f2affc3190d91f0465c35971e8f877269d5fd/icons/icon64x64.ico -------------------------------------------------------------------------------- /icons/make_mac_icon.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | mkdir icon.iconset 3 | sips -z 16 16 icon.png --out icon.iconset/icon_16x16.png 4 | sips -z 32 32 icon.png --out icon.iconset/icon_16x16@2x.png 5 | sips -z 32 32 icon.png --out icon.iconset/icon_32x32.png 6 | sips -z 64 64 icon.png --out icon.iconset/icon_32x32@2x.png 7 | sips -z 128 128 icon.png --out icon.iconset/icon_128x128.png 8 | sips -z 256 256 icon.png --out icon.iconset/icon_128x128@2x.png 9 | sips -z 256 256 icon.png --out icon.iconset/icon_256x256.png 10 | sips -z 512 512 icon.png --out icon.iconset/icon_256x256@2x.png 11 | sips -z 512 512 icon.png --out icon.iconset/icon_512x512.png 12 | cp icon.png icon.iconset/icon_512x512@2x.png 13 | iconutil -c icns icon.iconset 14 | rm -R icon.iconset -------------------------------------------------------------------------------- /icons/sources.txt: -------------------------------------------------------------------------------- 1 | http://cdn.mysitemyway.com/etc-mysitemyway/icons/legacy-previews/icons-256/blue-jelly-icons-symbols-shapes/017875-blue-jelly-icon-symbols-shapes-shapes-circle.png 2 | -------------------------------------------------------------------------------- /launchers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesabel/osnap/fc3f2affc3190d91f0465c35971e8f877269d5fd/launchers/__init__.py -------------------------------------------------------------------------------- /launchers/dist2zip.py: -------------------------------------------------------------------------------- 1 | 2 | import base64 3 | import bz2 4 | import time 5 | import os 6 | import shutil 7 | 8 | import util 9 | 10 | 11 | def main(): 12 | dist_dir = util.get_launch_name() 13 | print('zipping %s (%s)' % (dist_dir, os.path.abspath(dist_dir))) 14 | shutil.make_archive(dist_dir, 'zip', dist_dir) 15 | 16 | if __name__ == '__main__': 17 | main() 18 | -------------------------------------------------------------------------------- /launchers/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesabel/osnap/fc3f2affc3190d91f0465c35971e8f877269d5fd/launchers/icon.icns -------------------------------------------------------------------------------- /launchers/icon32x32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesabel/osnap/fc3f2affc3190d91f0465c35971e8f877269d5fd/launchers/icon32x32.ico -------------------------------------------------------------------------------- /launchers/launch.py: -------------------------------------------------------------------------------- 1 | 2 | # This file exists under 2 names. The 'real' one is launch.py. 3 | # launch.pyw is a hard link to launch.py 4 | # (launch.pyw - a .pyw since we're launching without a console window) 5 | 6 | import appdirs 7 | import glob 8 | import logging 9 | import logging.config 10 | import os 11 | import platform 12 | import sys 13 | import subprocess 14 | 15 | # Just for the launcher, not the user's app that OSNAP is launching 16 | AUTHOR = 'abel' 17 | APPLICATION = 'osnap_launcher' 18 | PROGRAM = 'main.py' 19 | 20 | 21 | def find_osnapy(path_leaf): 22 | """ 23 | go up directory levels until we find our python interpreter 24 | this is necessary the way various operating systems (e.g. Mac) launch in a subdirectory (e.g. Contents/MacOS) 25 | """ 26 | LOGGER = logging.getLogger('osnap_launcher') 27 | path = path_leaf 28 | while path != os.path.dirname(path): 29 | potential_path = os.path.join(path, 'osnapy') 30 | LOGGER.debug("potential_path : %s" % potential_path) 31 | if os.path.exists(potential_path): 32 | LOGGER.debug("Found osnapy at %s", potential_path) 33 | return potential_path 34 | 35 | # special directories to follow back 'up' 36 | for d in ['MacOS', 'osnapp']: 37 | potential_path = os.path.join(path, d, 'osnapy') 38 | if os.path.exists(potential_path): 39 | LOGGER.debug("Found osnapy at %s", potential_path) 40 | return potential_path 41 | path = os.path.dirname(path) 42 | return None 43 | 44 | 45 | def pick_osnapy(python_folder): 46 | "Find the osnapy directory and chdir to it" 47 | LOGGER = logging.getLogger('osnap_launcher') 48 | potential_paths = [] 49 | if len(sys.argv) > 1: 50 | # first, try the folder that contains our target 51 | potential_paths.append(os.path.dirname(sys.argv[1])) 52 | # next, try the folder that contains the launcher 53 | potential_paths.append(os.path.dirname(sys.argv[0])) 54 | # finally, try the folder we are starting from 55 | potential_paths.append(os.getcwd()) 56 | LOGGER.debug('looking in %s' % potential_paths) 57 | for potential_path in potential_paths: 58 | osnapy_path = find_osnapy(potential_path) 59 | if osnapy_path: 60 | if os.path.exists(osnapy_path): 61 | os.chdir(os.path.dirname(osnapy_path)) 62 | return 63 | 64 | 65 | def launch(): 66 | VERSION = '0.0.6' 67 | LOGGER = logging.getLogger('osnap_launcher') 68 | 69 | # conventions 70 | python_folder = 'osnapy' 71 | 72 | if platform.system().lower()[0] == 'w': 73 | # windows 74 | python_binary = 'pythonw.exe' 75 | python_path = os.path.join(python_folder, python_binary) 76 | 77 | elif platform.system().lower()[0] == 'd': 78 | # macOS/OSX reports 'Darwin' 79 | python_binary = 'python3' 80 | python_path = os.path.join(python_folder, 'bin', python_binary) 81 | else: 82 | raise NotImplementedError 83 | 84 | LOGGER.info('launcher version : %s', VERSION) 85 | LOGGER.info('sys.path : %s', sys.path) 86 | LOGGER.info('sys.argv : %s', sys.argv) 87 | LOGGER.info('original cwd : %s', os.getcwd()) 88 | 89 | pick_osnapy(python_folder) 90 | 91 | if not os.path.exists(python_path): 92 | raise Exception('{} does not exist - exiting'.format(python_path)) 93 | 94 | # set up environment variables (if needed) 95 | if platform.system().lower()[0] == 'w': 96 | env_vars = None 97 | elif platform.system().lower()[0] == 'd': 98 | site_packages_pattern = python_folder + os.sep + 'lib' + os.sep + 'python*' + os.sep + 'site-packages' + os.sep 99 | site_packages_glob = glob.glob(site_packages_pattern) 100 | if len(site_packages_glob) == 0: 101 | raise Exception('"{}" could not be found - exiting'.format(site_packages_pattern)) 102 | elif len(site_packages_glob) > 1: 103 | LOGGER.warning('warning : "%s" yielded mulitple results', site_packages_glob) 104 | env_vars = {'PYTHONPATH': site_packages_glob[0]} 105 | else: 106 | raise NotImplementedError("The platform '{}' is not supported by OSNAP yet".format(platform.system())) 107 | 108 | call_parameters = ' '.join([python_path, PROGRAM]) 109 | LOGGER.info('calling : %s with env=%s', call_parameters, env_vars) 110 | return_code = subprocess.call(call_parameters, env=env_vars, shell=True) 111 | LOGGER.info('return code : %s', return_code) 112 | 113 | 114 | def main(): 115 | logfile = os.path.join(appdirs.user_log_dir(APPLICATION, AUTHOR), 'osnap_launcher.log') 116 | logdir = os.path.dirname(logfile) 117 | if not os.path.exists(logdir): 118 | os.makedirs(logdir) 119 | logging.config.dictConfig({ 120 | 'version' : 1, 121 | 'formatters' : { 122 | 'detailed' : { 123 | 'format' : '[%(asctime)s] %(levelname)s pid:%(process)d %(name)s:%(lineno)d %(message)s', 124 | 'dateformat': '%d/%b/%Y:%H:%M:%S %z', 125 | }, 126 | 'simple' : { 127 | 'format' : '[%(asctime)s] %(levelname)s %(name)s:%(lineno)d %(message)s', 128 | 'dateformat': '%d/%b/%Y:%H:%M:%S %z', 129 | }, 130 | }, 131 | 'handlers' : { 132 | 'console' : { 133 | 'class' : 'logging.StreamHandler', 134 | # 'level' needs to be WARNING or above - if it's DEBUG Windows will try to make a log file for GUI apps, 135 | # which is either an access error or pops up as an annoying dialog box. 136 | 'level' : 'ERROR', 137 | 'formatter' : 'simple', 138 | }, 139 | 'file' : { 140 | 'class' : 'logging.FileHandler', 141 | 'filename' : logfile, 142 | 'formatter' : 'detailed', 143 | 'level' : 'DEBUG', 144 | }, 145 | }, 146 | 'loggers' : { 147 | '' : { 148 | 'handlers' : ['file', 'console'], 149 | 'level' : 'DEBUG', 150 | 'propogate' : True, 151 | }, 152 | }, 153 | 'root' : { 154 | 'level' : 'DEBUG', 155 | 'handlers' : ['file', 'console'], 156 | }, 157 | }) 158 | logging.getLogger().info("Installed logging") 159 | try: 160 | launch() 161 | return 0 162 | except Exception as e: 163 | logging.getLogger().exception("Unhandled exception in launcher: %s", e) 164 | return 1 165 | 166 | if __name__ == '__main__': 167 | sys.exit(main()) 168 | 169 | -------------------------------------------------------------------------------- /launchers/make_launcher.bat: -------------------------------------------------------------------------------- 1 | REM launch.py is the real file, but we need launch.pyw for windows versions so just create a link 2 | del /Q launch.pyw 3 | mklink /H launch.pyw launch.py 4 | REM you may want to turn off your virus scanner for this 5 | REM for some reason I have seen py2exe fail if the virus scanner is running 6 | del /Q /S launchwin 7 | REM assumes python is in the path 8 | REM create the windows app using py2exe 9 | venv\Scripts\python.exe setup.py py2exe 10 | REM put the app in a zip 11 | venv\Scripts\python.exe dist2zip.py 12 | REM copy over to the test app so we can test the launcher out 13 | REM del /Q /S ..\test_example\launchwin 14 | REM mkdir ..\test_example\launchwin 15 | REM xcopy /S launchwin\*.* ..\test_example\launchwin 16 | del /Q ..\osnap\launchwin-amd64-window.zip 17 | move launchwin.zip ..\osnap\launchwin-amd64-window.zip -------------------------------------------------------------------------------- /launchers/make_launcher.ps1: -------------------------------------------------------------------------------- 1 | param([string]$python = "C:\Python34\python.exe", [string]$arch = $ENV:PROCESSOR_ARCHITECTURE) 2 | Write-Host "Starting build of windows launcher on $arch" 3 | $variants = @("window", "console") 4 | foreach ($variant in $variants) { 5 | If (Test-Path launchwin) { 6 | Remove-Item launchwin -Recurse 7 | } 8 | If (Test-Path launch.exe) { 9 | Remove-Item launch.exe 10 | } 11 | If (Test-Path launchwin.zip) { 12 | Remove-Item launchwin.zip 13 | } 14 | Write-Host "Building launcher with py2exe" 15 | & $python setup.py py2exe --variant=$variant 16 | If (!(Test-Path launchwin\launch.exe)) { 17 | Write-Host "Unable to create launch\launch.exe. Exiting" 18 | exit 1 19 | } 20 | Write-Host "Zipping up launcher" 21 | & $python dist2zip.py 22 | If (!(Test-Path launchwin.zip)) { 23 | Write-Host "Unable to create launchwin.zip. Exiting" 24 | exit 1 25 | } 26 | switch ($ENV:PROCESSOR_ARCHITECTURE) 27 | { 28 | "AMD64" {$OUTPUT = "launchwin-amd64-$variant.zip"} 29 | "x86" {$OUTPUT = "launchwin-x86-$variant.zip"} 30 | default {$OUTPUT = "launchwin-unknown-$variant.zip"} 31 | } 32 | Write-Host "Copying launcher to expected location at ..\osnap\$OUTPUT" 33 | Copy-Item launchwin.zip ..\osnap\$OUTPUT 34 | } 35 | -------------------------------------------------------------------------------- /launchers/make_launcher.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # I'd like to use the venv python, but py2app doesn't seem to work with a venv version 4 | # LOCALPYTHON=./venv/bin/python3 5 | LOCALPYTHON=/usr/local/bin/python3 6 | # and in case we don't already have appdirs, install it (yes I know this messes up the main python ... but it's 7 | # only on osnap developer's machines) 8 | pip3 install -U appdirs 9 | # 10 | rm -r build/ 11 | rm -r launchmac/ 12 | # 13 | # create the macos app using py2app 14 | $LOCALPYTHON setup.py py2app 15 | # 16 | # put the app into a zip 17 | $LOCALPYTHON dist2zip.py 18 | # 19 | mv -f *.zip ../osnap 20 | -------------------------------------------------------------------------------- /launchers/make_venv.bat: -------------------------------------------------------------------------------- 1 | echo on 2 | REM currently 3.5 is not working for me for the launcher so use 3.4 3 | \Python34\python.exe \Python34\Tools\Scripts\pyvenv.py --clear venv 4 | venv\Scripts\pip3.exe install -U pip 5 | venv\Scripts\pip3.exe install -U setuptools 6 | venv\Scripts\pip3.exe install -U -r requirements.txt 7 | REM install osnap from current source 8 | pushd . 9 | cd .. 10 | launchers\venv\Scripts\python.exe setup.py install 11 | popd 12 | -------------------------------------------------------------------------------- /launchers/make_venv.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -x 3 | /usr/local/bin/pyvenv --clear venv 4 | ./venv/bin/pip3 install -U pip 5 | ./venv/bin/pip3 install -U setuptools 6 | ./venv/bin/pip3 install -U appdirs 7 | ./venv/bin/pip3 install -U py2app 8 | # install osnap from current source 9 | pushd . 10 | cd .. 11 | launchers/venv/bin/python3 setup.py install 12 | popd 13 | -------------------------------------------------------------------------------- /launchers/requirements.txt: -------------------------------------------------------------------------------- 1 | appdirs 2 | py2exe 3 | -------------------------------------------------------------------------------- /launchers/set_path.bat: -------------------------------------------------------------------------------- 1 | set PATH=%PATH%;c:\Python34 -------------------------------------------------------------------------------- /launchers/setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import util 4 | 5 | WINDOW_APP = 'launch.pyw' 6 | CONSOLE_APP = 'launch.py' 7 | 8 | 9 | def main(): 10 | if util.is_mac(): 11 | from setuptools import setup 12 | setup( 13 | app=[WINDOW_APP], 14 | options={'py2app': {'dist_dir': util.get_launch_name(), 15 | 'iconfile': 'icon.icns'}}, 16 | setup_requires=['py2app'], 17 | ) 18 | elif util.is_windows(): 19 | # Windows 20 | from setuptools import setup 21 | from setuptools.command.install import install 22 | import py2exe 23 | import py2exe.distutils_buildexe 24 | try: 25 | import appdirs 26 | except ImportError: 27 | raise Exception(( 28 | "You must have appdirs installed to build the launchers on windows." 29 | "Try pip installing them with 'pip install appdirs'")) 30 | 31 | if '3.5' in sys.version: 32 | raise Exception(( 33 | "You cannot build the launcher on windows with Python 3.5." 34 | "py2exe only supports up to python 3.4. Please install python 3.4 and " 35 | "run this script using that installation")) 36 | 37 | class InstallCommand(py2exe.distutils_buildexe.py2exe): 38 | user_options = install.user_options + [ 39 | ('variant=', None, 'Specify the variant of the launcher to create'), 40 | ] 41 | def initialize_options(self): 42 | super().initialize_options() 43 | self.variant = 'window' 44 | 45 | def finalize_options(self): 46 | super().finalize_options() 47 | assert self.variant in ('console', 'window'), "You must specify either window or console as the variant" 48 | 49 | def run(self): 50 | if self.variant == 'window': 51 | self.distribution.windows = self.distribution.windows 52 | elif self.variant == 'console': 53 | self.distribution.console = self.distribution.windows 54 | self.distribution.console[0]['script'] = CONSOLE_APP 55 | self.distribution.windows = [] 56 | else: 57 | raise Exception("Unrecognized variant {}".format(self.variant)) 58 | super().run() 59 | 60 | setup( 61 | cmdclass={ 62 | 'py2exe': InstallCommand, 63 | }, 64 | windows=[{ 65 | "icon_resources": [(1, "icon32x32.ico")], 66 | "script" : WINDOW_APP, 67 | }], 68 | options={'py2exe' : {'dist_dir': util.get_launch_name()}}, 69 | zipfile=None, 70 | ) 71 | else: 72 | raise NotImplementedError 73 | 74 | 75 | if __name__ == '__main__': 76 | main() 77 | -------------------------------------------------------------------------------- /launchers/util.py: -------------------------------------------------------------------------------- 1 | 2 | import platform 3 | 4 | 5 | def is_windows(): 6 | return platform.system().lower()[0] == 'w' 7 | 8 | 9 | def is_mac(): 10 | # macOS/OSX reports 'Darwin' 11 | return platform.system().lower()[0] == 'd' 12 | 13 | 14 | def get_os_name(): 15 | if is_mac(): 16 | return 'mac' 17 | elif is_windows(): 18 | return 'win' 19 | else: 20 | raise NotImplementedError 21 | 22 | 23 | def get_launch_name(): 24 | return 'launch' + get_os_name() 25 | 26 | -------------------------------------------------------------------------------- /make_dist.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # copy over the files that are used for other repos to formats that setup wants 4 | pandoc --from=rst --to=markdown --output=readme.md readme.rst 5 | pandoc --from=rst --to=plain --output=readme.txt readme.rst 6 | cp LICENSE LICENSE.txt 7 | python3 setup.py sdist 8 | # 9 | -------------------------------------------------------------------------------- /make_venv.bat: -------------------------------------------------------------------------------- 1 | rmdir /Q /S venv 2 | "C:\Program Files\Python37\python.exe" -m venv --clear venv 3 | venv\Scripts\python.exe -m pip install --upgrade pip 4 | venv\Scripts\pip3 install -U setuptools 5 | venv\Scripts\pip3 install -r requirements.txt 6 | -------------------------------------------------------------------------------- /make_venv.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | python3 -m venv --clear venv 4 | ./venv/bin/pip3 install -U pip 5 | ./venv/bin/pip3 install -U setuptools 6 | ./venv/bin/pip3 install -U -r requirements.txt 7 | -------------------------------------------------------------------------------- /osnap/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | # PEP 440 compliant semver (https://semver.org/) 3 | __version__ = '0.3.1' 4 | 5 | __application_name__ = 'osnap' 6 | package_name = __application_name__ 7 | 8 | __author__ = 'James Abel' 9 | 10 | # this is the same as the launcher uses 11 | python_folder = 'osnapy' 12 | 13 | main_program_py = 'main.py' 14 | 15 | windows_app_dir = 'osnapp' 16 | 17 | dist_dir = 'dist' 18 | 19 | CACHE_FOLDER = 'cache' 20 | TEMP_FOLDER = 'temp' 21 | 22 | import platform 23 | 24 | if platform.system().lower()[0] == 'w': 25 | default_python_version = '3.6.3' 26 | else: 27 | default_python_version = '3.6.3' 28 | 29 | from .logger import get_logger, init_logger_from_args 30 | -------------------------------------------------------------------------------- /osnap/installer.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import platform 3 | 4 | from osnap import default_python_version, get_logger, init_logger_from_args, __application_name__ 5 | import osnap.util 6 | import osnap.installer_mac 7 | import osnap.installer_win 8 | 9 | LOGGER = get_logger(__application_name__) 10 | 11 | 12 | def make_installer( 13 | python_version, 14 | application_name, 15 | application_version, 16 | author = '', 17 | description = '', 18 | url = '', 19 | compile_code = True, 20 | use_pyrun = False, 21 | create_installer = True, 22 | architecture = '64bit', 23 | variant = 'window', 24 | ): 25 | 26 | if osnap.util.is_mac(): 27 | class_ = osnap.installer_mac.OsnapInstallerMac 28 | elif osnap.util.is_windows(): 29 | class_ = osnap.installer_win.OsnapInstallerWin 30 | else: 31 | raise NotImplementedError("Your platform - {} - is not supported".format(platform.system())) 32 | 33 | installer = class_( 34 | python_version, 35 | application_name, 36 | application_version, 37 | author, 38 | description, 39 | url, 40 | compile_code, 41 | use_pyrun, 42 | create_installer, 43 | architecture, 44 | variant, 45 | ) 46 | 47 | installer.make_installer() 48 | 49 | 50 | def main(): 51 | 52 | parser = argparse.ArgumentParser(description='create the osnap installer', 53 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 54 | parser.add_argument('-a', '--application', default=None, help='application name (required for OSX/MacOS)') 55 | parser.add_argument('-A', '--architecture', default='64bit', choices=['64bit', '32bit'], help="The architecture to use for the launcher") 56 | parser.add_argument('-b', '--binary-only', action='store_true', default=False, help=( 57 | "Only produce the binary of the script as an executable without creating an installer. This avoids the NSIS requirement on windows" 58 | )) 59 | parser.add_argument('-c', '--console', action='store_true', default=False, help="Use a console for the application") 60 | parser.add_argument('-p', '--python_version', default=default_python_version, help='python version') 61 | parser.add_argument('-e', '--egenix_pyrun', action='store_true', default=False, help='use eGenix™ PyRun™') 62 | parser.add_argument('-n', '--name_of_author', default='', help='author name') 63 | parser.add_argument('-d', '--description', default='', help='application description') 64 | parser.add_argument('-u', '--url', default='', help='application URL') 65 | parser.add_argument('-v', '--verbose', action='store_true', default=False, help='print more verbose messages') 66 | args = parser.parse_args() 67 | 68 | init_logger_from_args(args) 69 | 70 | try: 71 | make_installer( 72 | args.python_version, 73 | args.application, 74 | args.name_of_author, 75 | args.description, 76 | args.url, 77 | True, 78 | args.egenix_pyrun, 79 | create_installer = not args.binary_only, 80 | architecture = args.architecture, 81 | variant = 'console' if args.console else 'window', 82 | ) 83 | except Exception as e: 84 | LOGGER.exception("Fatal error: %s", e) 85 | 86 | 87 | if __name__ == '__main__': 88 | main() 89 | -------------------------------------------------------------------------------- /osnap/installer_base.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import py_compile 4 | import fnmatch 5 | import zipfile 6 | import site 7 | 8 | from osnap import python_folder, __application_name__, get_logger 9 | import osnap.util 10 | 11 | LOGGER = get_logger(__application_name__) 12 | 13 | 14 | class OsnapInstaller: 15 | def __init__(self, 16 | python_version, 17 | application_name, 18 | application_version, 19 | author, 20 | description, 21 | url, 22 | compile_code, 23 | use_pyrun=False, 24 | create_installer=True, 25 | architecture='64bit', 26 | variant='window', 27 | ): 28 | self.application_name = application_name 29 | self.application_version = application_version 30 | self.architecture = architecture 31 | self.author = author 32 | self.compile_code = compile_code 33 | self.create_installer = create_installer 34 | self.description = description 35 | self.python_version = python_version 36 | self.variant = variant 37 | self.url = url 38 | self.use_pyrun = use_pyrun 39 | 40 | def make_installer(self): 41 | 42 | # If the user has been using osnap's python, you shouldn't have to compile the code here, which keeps the 43 | # distribution size smaller. However, just in case the installed program is having an issue with non-compiled 44 | # code (e.g. it's been installed into a read-only area), we have this option. 45 | if self.compile_code: 46 | LOGGER.debug('compiling') 47 | for root, dirnames, filenames in os.walk(python_folder): 48 | for filename in fnmatch.filter(filenames, '*.py'): 49 | path = os.path.join(root, filename) 50 | # special case: don't compile Python 2 code from PyQt 51 | if 'port_v2' not in path: 52 | py_compile.compile(path) 53 | 54 | # derived classes will finish making the installer 55 | 56 | def unzip_launcher(self, destination): 57 | 58 | # find zip 59 | launch_zip_name = osnap.util.get_launch_name(self.architecture, self.variant) + '.zip' 60 | locations = set() 61 | LOGGER.debug("Looking for launch zip '%s'", launch_zip_name) 62 | possible_locations = site.getsitepackages() + [os.path.dirname(__file__)] 63 | for d in possible_locations: 64 | LOGGER.debug("Searching site package %s", d) 65 | for r, _, fs in os.walk(d): 66 | for f in fs: 67 | if f == launch_zip_name: 68 | p = os.path.join(r, f) 69 | if osnap.util.is_windows(): 70 | p = p.lower() 71 | locations.add(p) 72 | if len(locations) != 1: 73 | raise Exception('Looking for exactly one {} : found {}'.format(launch_zip_name, locations)) 74 | launch_zip_path = locations.pop() 75 | 76 | LOGGER.debug('unzipping %s to %s', launch_zip_path, destination) 77 | zip_ref = zipfile.ZipFile(launch_zip_path, 'r') 78 | zip_ref.extractall(destination) 79 | zip_ref.close() 80 | os.chmod(destination, 0o755) 81 | 82 | 83 | -------------------------------------------------------------------------------- /osnap/installer_mac.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import subprocess 4 | import shutil 5 | import distutils.dir_util 6 | 7 | import lxml.etree as ElementTree 8 | 9 | from osnap import dist_dir, main_program_py, get_logger, __application_name__ 10 | import osnap.util 11 | import osnap.installer_base 12 | import osnap.make_pkgproj 13 | 14 | 15 | LOGGER = get_logger(__application_name__) 16 | 17 | 18 | class OsnapInstallerMac(osnap.installer_base.OsnapInstaller): 19 | 20 | def make_installer(self): 21 | super().make_installer() 22 | 23 | osnap.util.rm_mk_tree(dist_dir) 24 | self.unzip_launcher(dist_dir) 25 | dist_app_path = os.path.join(dist_dir, self.application_name + '.app') 26 | # the launcher app in the zip is a generic name of 'launch.app' - rename it to our app's name 27 | os.rename(os.path.join(dist_dir, 'launch.app'), dist_app_path) 28 | 29 | macos_path = os.path.join(dist_app_path, 'Contents', 'MacOS') 30 | 31 | for d in [self.application_name, osnap.util.get_osnapy_path_in_application_dir(self.application_name)]: 32 | if os.path.exists(d): 33 | dst = os.path.join(macos_path, os.path.basename(os.path.normpath(d))) 34 | LOGGER.info('copying %s to %s' % (d, dst)) 35 | distutils.dir_util.copy_tree(d, dst) 36 | else: 37 | LOGGER.warning('%s does not exist' % d) 38 | 39 | for f in [main_program_py]: 40 | LOGGER.info('copying %s to %s' % (f, macos_path)) 41 | if os.path.exists(f): 42 | shutil.copy2(f, macos_path) 43 | else: 44 | LOGGER.warning('expected %s (%s) to exist but it does not' % (f, os.path.abspath(f))) 45 | 46 | # mac icon file 47 | icon_file_name = 'icon.icns' 48 | src = os.path.join('icons', icon_file_name) 49 | if os.path.exists(src): 50 | dst = os.path.join(dist_app_path, 'Contents', 'Resources', icon_file_name) 51 | if os.path.exists(dst): 52 | # there should be a default file already there 53 | os.remove(dst) 54 | else: 55 | # if the default icon file does not exist, something is very wrong 56 | LOGGER.error('default icon file %s does not already exist' % dst) 57 | LOGGER.info('copying %s to %s' % (src, dst)) 58 | shutil.copy2(src, dst) 59 | else: 60 | LOGGER.warning( 61 | '%s (%s) does not exist - using default icons.\n' 62 | 'This will work for one program but MacOS will not install more than one program with the same icon\n' 63 | 'so it should be fixed for any program you plan on distributing.' % (src, os.path.abspath(src))) 64 | 65 | # fix up the keys in the Info.plist file 66 | reverse_dns_identifier = self._info_plist_substitute(os.path.join(dist_app_path, 'Contents', 'Info.plist')) 67 | 68 | os.chmod(os.path.join(macos_path, 'launch'), 0o777) 69 | 70 | # make dmg 71 | dmg_command = ['hdiutil', 'create', '-volname', self.application_name, '-srcfolder', os.path.abspath('dist'), '-ov', 72 | '-format', 'UDZO', self.application_name + '.dmg'] 73 | dmg_command = ' '.join(dmg_command) 74 | LOGGER.debug('Executing %s' % dmg_command) 75 | subprocess.check_call(dmg_command, shell=True) 76 | 77 | if not self.create_installer: 78 | LOGGER.debug("Not creating installer - it was not requested") 79 | return 80 | 81 | # make pkg based installer 82 | pkgproj_path = self.application_name + '.pkgproj' 83 | packages_path = os.path.join(os.sep, 'usr', 'local', 'bin', 'packagesbuild') 84 | if not os.path.exists(packages_path): 85 | raise Exception(( 86 | 'Packages tool could not be found (expected at {})' 87 | 'See http://s.sudre.free.fr/Software/Packages/about.html (Packages by Stéphane Sudre) for ' 88 | 'information on how to obtain the Packages tool.').format(packages_path)) 89 | pkgproj_command = [packages_path, pkgproj_path] 90 | osnap.make_pkgproj.make_pkgproj(self.application_name, reverse_dns_identifier, pkgproj_path) 91 | pkgproj_command = ' '.join(pkgproj_command) 92 | LOGGER.debug('Executing %s' % pkgproj_command) 93 | subprocess.check_call(pkgproj_command, shell=True) 94 | 95 | # todo: delete the osnapy in /Applications (actually the entire /Applications/.app ) 96 | 97 | def _info_plist_substitute(self, info_plist_path): 98 | LOGGER.info('fixing up %s' % info_plist_path) 99 | tree = ElementTree.parse(info_plist_path) 100 | translations = {} 101 | 102 | # reverse-DNS format 103 | url_fields = self.url.split('.') 104 | if len(url_fields) != 3: 105 | LOGGER.error('URL "%s" improperly formatted - must be x.y.z' % self.url) 106 | reverse_dns_identifier = '%s.%s.%s' % (url_fields[-1], url_fields[-2], self.application_name) 107 | 108 | translations['CFBundleName'] = self.application_name 109 | translations['CFBundleDisplayName'] = self.application_name 110 | translations['CFBundleIdentifier'] = reverse_dns_identifier 111 | translations['CFBundleVersion'] = self.application_version 112 | translations['CFBundleShortVersionString'] = self.application_version 113 | translations['CFBundleSignature'] = url_fields[-2][0:4].upper() 114 | translations['NSHumanReadableCopyright'] = 'Copyright %s' % self.author 115 | next_translation = None 116 | for elem in tree.getiterator(): 117 | if elem.tag == 'key' and elem.text in translations: 118 | next_translation = elem.text 119 | elif next_translation and elem.tag == 'string': 120 | LOGGER.debug('replaced', elem.tag, elem.text, translations[next_translation]) 121 | elem.text = translations[next_translation] 122 | next_translation = None 123 | if False: 124 | for ds in tree.iterfind('dict'): 125 | print(ds.findtext('dict')) 126 | for d in ds: 127 | print(d.findtext('key')) 128 | 129 | doctype = '' 130 | with open(info_plist_path, 'w') as f: 131 | f.write(ElementTree.tostring(tree, xml_declaration=True, pretty_print=True, encoding='UTF-8', 132 | doctype=doctype).decode()) 133 | 134 | return reverse_dns_identifier 135 | -------------------------------------------------------------------------------- /osnap/installer_win.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import shutil 4 | import collections 5 | import subprocess 6 | import distutils.dir_util 7 | 8 | from osnap import dist_dir, windows_app_dir, python_folder, main_program_py, get_logger, __application_name__ 9 | import osnap.util 10 | import osnap.make_nsis 11 | import osnap.installer_base 12 | 13 | 14 | LOGGER = get_logger(__application_name__) 15 | 16 | 17 | class OsnapInstallerWin(osnap.installer_base.OsnapInstaller): 18 | 19 | def make_installer(self): 20 | 21 | osnap.util.rm_mk_tree(dist_dir) 22 | osnap.util.rm_mk_tree('installers') 23 | self.unzip_launcher(dist_dir) 24 | 25 | distutils.dir_util.copy_tree(self.application_name, os.path.join(dist_dir, 26 | windows_app_dir, 27 | self.application_name)) 28 | distutils.dir_util.copy_tree(python_folder, 29 | os.path.join(dist_dir, windows_app_dir, 30 | python_folder)) 31 | win_app_dir_path = os.path.join(dist_dir, windows_app_dir) 32 | for f in [main_program_py]: 33 | LOGGER.debug('copying %s to %s', f, win_app_dir_path) 34 | if os.path.exists(f): 35 | shutil.copy2(f, win_app_dir_path) 36 | else: 37 | raise Exception('expected {} ({}) to exist but it does not'.format(f, os.path.abspath(f))) 38 | 39 | # copy over any MSVC files (e.g. .dlls) needed 40 | msvc_dir = 'msvc' 41 | if os.path.exists(msvc_dir): 42 | for file_name in os.listdir(msvc_dir): 43 | src = os.path.join(msvc_dir, file_name) 44 | for dest_folder in [dist_dir, os.path.join(dist_dir, windows_app_dir)]: 45 | dest = os.path.join(dest_folder, file_name) 46 | LOGGER.info('copying %s to %s' % (src, dest)) 47 | shutil.copy2(src, dest) 48 | else: 49 | LOGGER.warning( 50 | 'nothing in folder %s (%s) to copy over (no DLLs needed?)' % (msvc_dir, os.path.abspath(msvc_dir))) 51 | 52 | # application .exe 53 | exe_name = self.application_name + '.exe' 54 | orig_launch_exe_path = os.path.join(dist_dir, 'launch.exe') 55 | dist_exe_path = os.path.join(dist_dir, exe_name) 56 | LOGGER.debug('moving %s to %s', orig_launch_exe_path, dist_exe_path) 57 | os.rename(orig_launch_exe_path, dist_exe_path) 58 | 59 | # support files 60 | for f in [self.application_name + '.ico', 'LICENSE']: 61 | shutil.copy2(f, dist_dir) 62 | 63 | # write NSIS script 64 | if not self.create_installer: 65 | LOGGER.debug("Not creating NSIS installer - it was not requested") 66 | return 67 | 68 | nsis_file_name = self.application_name + '.nsis' 69 | 70 | nsis_defines = collections.OrderedDict() 71 | nsis_defines['COMPANYNAME'] = self.author 72 | nsis_defines['APPNAME'] = self.application_name 73 | nsis_defines['EXENAME'] = exe_name 74 | nsis_defines['DESCRIPTION'] = '"' + self.description + '"' # the description must be in quotes 75 | 76 | v = self.application_version.split('.') 77 | if len(v) != 3: 78 | LOGGER.error('version string "%s" does not adhere to the x.y.z format' % self.application_version) 79 | nsis_defines['VERSIONMAJOR'] = '0' 80 | nsis_defines['VERSIONMINOR'] = '0' 81 | nsis_defines['VERSIONBUILD'] = '0' 82 | else: 83 | nsis_defines['VERSIONMAJOR'] = v[0] 84 | nsis_defines['VERSIONMINOR'] = v[1] 85 | nsis_defines['VERSIONBUILD'] = v[2] 86 | 87 | # These will be displayed by the "Click here for support information" link in "Add/Remove Programs" 88 | # It is possible to use "mailto:" links in here to open the email client 89 | nsis_defines['HELPURL'] = self.url # "Support Information" link 90 | nsis_defines['UPDATEURL'] = self.url # "Product Updates" link 91 | nsis_defines['ABOUTURL'] = self.url # "Publisher" link 92 | 93 | LOGGER.debug('writing %s', nsis_file_name) 94 | nsis = osnap.make_nsis.MakeNSIS(nsis_defines, nsis_file_name) 95 | nsis.write_all() 96 | 97 | shutil.copy(nsis_file_name, dist_dir) 98 | 99 | os.chdir(dist_dir) 100 | 101 | nsis_path = os.path.join('c:', os.sep, 'Program Files (x86)', 'NSIS', 'makensis.exe') 102 | if os.path.exists(nsis_path): 103 | pkgproj_command = [nsis_path, nsis_file_name] 104 | LOGGER.debug('Executing %s', pkgproj_command) 105 | subprocess.check_call(pkgproj_command) 106 | else: 107 | raise Exception(('NSIS tool could not be found (expected at {})' 108 | 'See http://nsis.sourceforge.net for information on how to obtain the NSIS ' 109 | '(Nullsoft Scriptable Install System) tool.').format(nsis_path)) 110 | -------------------------------------------------------------------------------- /osnap/launchmac.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesabel/osnap/fc3f2affc3190d91f0465c35971e8f877269d5fd/osnap/launchmac.zip -------------------------------------------------------------------------------- /osnap/launchwin-amd64-window.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesabel/osnap/fc3f2affc3190d91f0465c35971e8f877269d5fd/osnap/launchwin-amd64-window.zip -------------------------------------------------------------------------------- /osnap/launchwin-x86-console.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesabel/osnap/fc3f2affc3190d91f0465c35971e8f877269d5fd/osnap/launchwin-x86-console.zip -------------------------------------------------------------------------------- /osnap/launchwin-x86-window.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesabel/osnap/fc3f2affc3190d91f0465c35971e8f877269d5fd/osnap/launchwin-x86-window.zip -------------------------------------------------------------------------------- /osnap/logger.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import logging 4 | import logging.handlers 5 | import shutil 6 | 7 | import appdirs 8 | 9 | from osnap import __application_name__, __author__ 10 | 11 | 12 | g_formatter = logging.Formatter('%(asctime)s - %(name)s - %(filename)s - %(lineno)s - %(funcName)s - %(levelname)s - %(message)s') 13 | 14 | 15 | def get_logger(name): 16 | """ 17 | Special "get logger" where you can pass in __file__ and it extracts the module name, or a string that is 18 | the application name. 19 | :param name: name of the logger to get, optionally as a python file path 20 | :return: a logger 21 | """ 22 | 23 | # If name is a python file, or a path to a python file, extract the module name. Otherwise just use name 24 | # as is. 25 | if os.sep in name: 26 | name = name.split(os.sep)[-1] 27 | if name.endswith('.py'): 28 | name = name[:-3] 29 | return logging.getLogger(name) 30 | 31 | 32 | log = get_logger(__application_name__) 33 | 34 | handlers = {} 35 | 36 | 37 | def init_logger(name, author=None, log_directory=None, verbose=False, delete_existing_log_files=False, 38 | max_bytes=100*1E6, backup_count=3): 39 | """ 40 | Initialize the logger. Call once from the application 'main'. 41 | """ 42 | 43 | global handlers 44 | 45 | root_log = logging.getLogger() # we init the root logger so all child loggers inherit this functionality 46 | 47 | if root_log.hasHandlers(): 48 | root_log.error('logger already initialized') 49 | return root_log 50 | 51 | if verbose: 52 | root_log.setLevel(logging.DEBUG) 53 | else: 54 | root_log.setLevel(logging.INFO) 55 | 56 | console_handler = logging.StreamHandler() 57 | console_handler.setFormatter(g_formatter) 58 | if verbose: 59 | console_handler.setLevel(logging.INFO) 60 | else: 61 | console_handler.setLevel(logging.WARNING) 62 | root_log.addHandler(console_handler) 63 | handlers['console'] = console_handler 64 | 65 | # create file handler 66 | if log_directory is None: 67 | log_directory = appdirs.user_log_dir(name, author) 68 | if delete_existing_log_files: 69 | shutil.rmtree(log_directory, ignore_errors=True) 70 | os.makedirs(log_directory, exist_ok=True) 71 | fh_path = os.path.join(log_directory, '%s.log' % name) 72 | file_handler = logging.handlers.RotatingFileHandler(fh_path, maxBytes=max_bytes, backupCount=backup_count) 73 | file_handler.setFormatter(g_formatter) 74 | if verbose: 75 | file_handler.setLevel(logging.DEBUG) 76 | else: 77 | file_handler.setLevel(logging.INFO) 78 | root_log.addHandler(file_handler) 79 | handlers['file'] = file_handler 80 | root_log.info('log file path : "%s" ("%s")' % (fh_path, os.path.abspath(fh_path))) 81 | 82 | return root_log, handlers, fh_path 83 | 84 | 85 | def set_verbose(verbose=True): 86 | if verbose: 87 | log.setLevel(logging.DEBUG) 88 | handlers['file'].setLevel(logging.DEBUG) 89 | handlers['console'].setLevel(logging.INFO) 90 | handlers['dialog'].setLevel(logging.WARNING) 91 | else: 92 | log.setLevel(logging.INFO) 93 | handlers['file'].setLevel(logging.INFO) 94 | handlers['console'].setLevel(logging.WARNING) 95 | handlers['dialog'].setLevel(logging.ERROR) 96 | 97 | 98 | def init_logger_from_args(args): 99 | return init_logger(__application_name__, __author__, verbose=args.verbose) 100 | -------------------------------------------------------------------------------- /osnap/make_nsis.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import datetime 4 | import string 5 | 6 | from osnap import windows_app_dir, __application_name__, get_logger 7 | 8 | 9 | LOGGER = get_logger(__application_name__) 10 | 11 | 12 | class MakeNSIS: 13 | # basic format is from: 14 | # http://nsis.sourceforge.net/A_simple_installer_with_start_menu_shortcut_and_uninstaller 15 | 16 | def __init__(self, defines, file_path): 17 | self.defines = defines # ordered dict with the defines 18 | self.file_path = file_path 19 | self.file = None 20 | # we run this in a special subfolder so designate the installers one level up 21 | self.installers_folder = os.path.join('..', 'installers') 22 | 23 | def write_line(self, l): 24 | self.file.write(l) 25 | self.file.write('\n') 26 | 27 | def write_all(self): 28 | self.file = open(self.file_path, 'w') 29 | self.write_line('') 30 | self.write_line('# *** DO NOT EDIT ***') 31 | self.write_line('# Programmatically generated by %s on %s.' % (__file__, str(datetime.datetime.now()))) 32 | self.write_line('') 33 | for define in self.defines: 34 | v = self.defines[define] 35 | for white_space in string.whitespace: 36 | if white_space in v: 37 | LOGGER.error('removing whitespace in define: "%s" "%s"' % (define, self.defines[define])) 38 | v = v.replace(white_space, '') 39 | self.write_line('!define %s %s' % (define, self.defines[define])) 40 | self.header() 41 | self.pages() 42 | self.admin() 43 | self.install() 44 | self.uninstall() 45 | self.file.close() 46 | 47 | def header(self): 48 | self.write_line('') 49 | self.write_line('RequestExecutionLevel admin ;Require admin rights on NT6+ (When UAC is turned on)') 50 | self.write_line('InstallDir "$PROGRAMFILES\${COMPANYNAME}\${APPNAME}"') 51 | self.write_line('LicenseData "LICENSE"') 52 | self.write_line("# This will be in the installer/uninstaller's title bar") 53 | self.write_line('Name "${COMPANYNAME} - ${APPNAME}"') 54 | self.write_line('Icon "${APPNAME}.ico"') 55 | self.write_line('outFile "%s\${APPNAME}_installer.exe"' % self.installers_folder) 56 | self.write_line('') 57 | self.write_line('!include LogicLib.nsh') 58 | 59 | def pages(self): 60 | # child classes can override this if they have a different set of pages 61 | self.write_line('') 62 | self.write_line('page license') 63 | self.write_line('page directory') 64 | self.write_line('Page instfiles') 65 | 66 | def admin(self): 67 | self.write_line('') 68 | self.write_line('!macro VerifyUserIsAdmin') 69 | self.write_line('UserInfo::GetAccountType') 70 | self.write_line('pop $0') 71 | self.write_line('${If} $0 != "admin" ;Require admin rights on NT4+') 72 | self.write_line(' messageBox mb_iconstop "Administrator rights required!"') 73 | self.write_line(' setErrorLevel 740 ;ERROR_ELEVATION_REQUIRED') 74 | self.write_line(' quit') 75 | self.write_line('${EndIf}') 76 | self.write_line('!macroend') 77 | self.write_line('') 78 | self.write_line('function .onInit') 79 | self.write_line(' setShellVarContext all') 80 | self.write_line(' !insertmacro VerifyUserIsAdmin') 81 | self.write_line('functionEnd') 82 | 83 | def install(self): 84 | self.write_line('') 85 | self.write_line('section "install"') 86 | self.write_line('# Files for the install directory - to build the installer, these should be in the same directory as the install script (this file)') 87 | self.write_line('setOutPath $INSTDIR') 88 | self.write_line('# Files added here should be removed by the uninstaller (see section "uninstall")') 89 | self.write_line('file /r *') 90 | 91 | self.write_line('') 92 | self.write_line('# Uninstaller - See function un.onInit and section "uninstall" for configuration') 93 | self.write_line('writeUninstaller "$INSTDIR\\uninstall.exe"') 94 | self.write_line('') 95 | self.write_line('# Start Menu') 96 | self.write_line('createDirectory "$SMPROGRAMS\\${COMPANYNAME}"') 97 | self.write_line('createShortCut "$SMPROGRAMS\\${COMPANYNAME}\\${APPNAME}.lnk" "$INSTDIR\\${EXENAME}" "" "$INSTDIR\\${APPNAME}.ico"') 98 | self.write_line('') 99 | self.write_line('# run on Windows startup') 100 | self.write_line('WriteRegStr HKEY_LOCAL_MACHINE "Software\\Microsoft\\Windows\\CurrentVersion\\Run" "${APPNAME}" "$INSTDIR\\${EXENAME}"') 101 | self.write_line('') 102 | self.write_line('# Registry information for add/remove programs') 103 | self.write_line('WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${COMPANYNAME} ${APPNAME}" "DisplayName" "${COMPANYNAME} - ${APPNAME} - ${DESCRIPTION}"') 104 | self.write_line('WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${COMPANYNAME} ${APPNAME}" "UninstallString" "$\\"$INSTDIR\\uninstall.exe$\\""') 105 | self.write_line('WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${COMPANYNAME} ${APPNAME}" "QuietUninstallString" "$\\"$INSTDIR\\uninstall.exe$\\" /S"') 106 | self.write_line('WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${COMPANYNAME} ${APPNAME}" "InstallLocation" "$INSTDIR"') 107 | self.write_line('WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${COMPANYNAME} ${APPNAME}" "DisplayIcon" "$\\"$INSTDIR\\logo.ico$\\""') 108 | self.write_line('WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${COMPANYNAME} ${APPNAME}" "Publisher" "${COMPANYNAME}"') 109 | self.write_line('WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${COMPANYNAME} ${APPNAME}" "HelpLink" "${HELPURL}"') 110 | self.write_line('WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${COMPANYNAME} ${APPNAME}" "URLUpdateInfo" "${UPDATEURL}"') 111 | self.write_line('WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${COMPANYNAME} ${APPNAME}" "URLInfoAbout" "${ABOUTURL}"') 112 | self.write_line('WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${COMPANYNAME} ${APPNAME}" "DisplayVersion" "${VERSIONMAJOR}.${VERSIONMINOR}.${VERSIONBUILD}"') 113 | self.write_line('WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${COMPANYNAME} ${APPNAME}" "VersionMajor" ${VERSIONMAJOR}') 114 | self.write_line('WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${COMPANYNAME} ${APPNAME}" "VersionMinor" ${VERSIONMINOR}') 115 | self.write_line('WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${COMPANYNAME} ${APPNAME}" "NoModify" 1') 116 | self.write_line('WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${COMPANYNAME} ${APPNAME}" "NoRepair" 1') 117 | self.write_line('WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${COMPANYNAME} ${APPNAME}" "EstimatedSize" ${INSTALLSIZE}') 118 | self.write_line('sectionEnd') 119 | 120 | def uninstall(self): 121 | self.write_line('# Uninstaller') 122 | self.write_line('function un.onInit') 123 | self.write_line(' SetShellVarContext all') 124 | 125 | self.write_line('# Verify the uninstaller - last chance to back out') 126 | self.write_line(' MessageBox MB_OKCANCEL "Permanantly remove ${APPNAME}?" IDOK next') 127 | self.write_line(' Abort') 128 | self.write_line(' next:') 129 | self.write_line('!insertmacro VerifyUserIsAdmin') 130 | self.write_line('functionEnd') 131 | 132 | self.write_line('section "uninstall"') 133 | 134 | self.write_line('# Remove Start Menu launcher') 135 | self.write_line('delete "$SMPROGRAMS\\${COMPANYNAME}\\${APPNAME}.lnk"') 136 | self.write_line('# Try to remove the Start Menu folder - this will only happen if it is empty') 137 | self.write_line('rmDir "$SMPROGRAMS\\${COMPANYNAME}"') 138 | 139 | self.write_line('# Remove files') 140 | self.write_line('RMDir /r $INSTDIR\\%s' % windows_app_dir) # all the user files should be here 141 | # use these patterns so that we delete the uninstaller last 142 | self.write_line('delete $INSTDIR\\LICENSE') 143 | self.write_line('delete $INSTDIR\\COPY') # for GPL 144 | self.write_line('delete $INSTDIR\\${EXENAME}') 145 | self.write_line('delete $INSTDIR\\*.pyd') 146 | self.write_line('delete $INSTDIR\\*.dll') 147 | self.write_line('delete $INSTDIR\\*.ico') 148 | self.write_line('delete $INSTDIR\\*.nsis') 149 | 150 | self.write_line('# Always delete uninstaller as the last action') 151 | self.write_line('delete $INSTDIR\\uninstall.exe') 152 | 153 | self.write_line('# Try to remove the install directory - this will only happen if it is empty') 154 | self.write_line('rmDir $INSTDIR') 155 | self.write_line('rmDir "$PROGRAMFILES\${COMPANYNAME}"') 156 | 157 | self.write_line('# Remove uninstaller information from the registry') 158 | self.write_line('DeleteRegKey HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${COMPANYNAME} ${APPNAME}"') 159 | self.write_line('sectionEnd') 160 | 161 | 162 | def get_folder_size(folder_path): 163 | total_size = 0 164 | for d, _, fns in os.walk(folder_path): 165 | for f in fns: 166 | total_size += os.path.getsize(os.path.join(d, f)) 167 | return total_size 168 | -------------------------------------------------------------------------------- /osnap/make_pkgproj.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import sys 4 | import site 5 | import uuid 6 | 7 | from osnap import __application_name__, get_logger 8 | import osnap.util 9 | 10 | from jinja2 import Template 11 | 12 | 13 | LOGGER = get_logger(__application_name__) 14 | 15 | 16 | def make_pkgproj(application_name, reverse_dns_identifier, pkgproj_path): 17 | 18 | # find Packages project file 19 | template_file = 'template.pkgproj' 20 | locations = set() 21 | for d in site.getsitepackages(): 22 | for r, _, fs in os.walk(d): 23 | for f in fs: 24 | if f == template_file: 25 | p = os.path.join(r, f) 26 | if osnap.util.is_windows(): 27 | p = p.lower() 28 | locations.add(p) 29 | if len(locations) != 1: 30 | s = 'error : looking for exactly one %s : found %s' % (template_file, str(locations)) 31 | print(s) 32 | sys.exit(s) 33 | template_file_path = locations.pop() 34 | 35 | LOGGER.debug('using %s as template', template_file_path) 36 | with open(template_file_path) as template_file: 37 | template = Template(template_file.read()) 38 | with open(pkgproj_path, 'w') as f: 39 | f.write(template.render(application_name=application_name, 40 | reverse_dns_identifier=reverse_dns_identifier, 41 | uuid=str(uuid.uuid4()))) 42 | -------------------------------------------------------------------------------- /osnap/osnapy.py: -------------------------------------------------------------------------------- 1 | 2 | import argparse 3 | 4 | from osnap import default_python_version, get_logger, init_logger_from_args, __application_name__ 5 | import osnap.osnapy_win 6 | import osnap.osnapy_mac 7 | import osnap.util 8 | 9 | LOGGER = get_logger(__application_name__) 10 | 11 | 12 | def make_osnapy( 13 | python_version, 14 | application_name = None, 15 | clean_cache = False, 16 | use_pyrun = False, # support for eGenix™ PyRun™ has been removed 17 | force_app_uninstall = False, 18 | architecture = '64bit', 19 | ): 20 | 21 | LOGGER.debug('creating osnapy Python environment using python %s' % python_version) 22 | if osnap.util.is_mac() and application_name is None: 23 | raise Exception('must specify the application name on mac') 24 | 25 | osnapy = None 26 | if osnap.util.is_windows(): 27 | osnapy = osnap.osnapy_win.OsnapyWin(python_version, application_name, clean_cache, architecture=architecture) 28 | elif osnap.util.is_mac(): 29 | if use_pyrun: 30 | LOGGER.critical('pyrun capability has been removed') 31 | else: 32 | osnapy = osnap.osnapy_mac.OsnapyMac(python_version, application_name, clean_cache, force_app_uninstall) 33 | else: 34 | raise NotImplementedError 35 | osnapy.create_python() 36 | osnapy.pip('pip') 37 | osnapy.pip('setuptools') 38 | osnapy.pip('Cython') # e.g. for kivy 39 | osnapy.pip(None) # install all from requirements.txt 40 | 41 | 42 | def main(): 43 | parser = argparse.ArgumentParser(description='create the osnapy Python environment', 44 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 45 | parser.add_argument('-a', '--application', default=None, help='application name (required for OSX/MacOS)') 46 | parser.add_argument('-A', '--architecture', default='64bit', choices=['64bit', '32bit'], help='The architecture to use for the launcher') 47 | parser.add_argument('-p', '--python_version', default=default_python_version, help='python version') 48 | parser.add_argument('-c', '--clear', action='store_true', default=False, help='clear cache') 49 | parser.add_argument('-f', '--force_uninstall', action='store_true', default=False, 50 | help='force application uninstalls if necessary') 51 | parser.add_argument('-v', '--verbose', action='store_true', default=False, help='print more verbose messages') 52 | 53 | args = parser.parse_args() 54 | init_logger_from_args(args) 55 | 56 | make_osnapy( 57 | python_version = args.python_version, 58 | application_name = args.application, 59 | clean_cache = args.clear, 60 | use_pyrun = False, # support for eGenix™ PyRun™ has been removed 61 | force_app_uninstall = args.force_uninstall, 62 | architecture = args.architecture 63 | ) 64 | 65 | 66 | if __name__ == '__main__': 67 | main() 68 | -------------------------------------------------------------------------------- /osnap/osnapy_base.py: -------------------------------------------------------------------------------- 1 | 2 | # todo: to add packages from requirements file, just do a -r requirements 3 | # todo: since everything we do now is using pip, just have a pip routine (not add_package() ) 4 | 5 | 6 | class OsnapyBase(): 7 | def __init__( 8 | self, 9 | python_version, 10 | application_name = None, 11 | clean_cache = False, 12 | force_uninstalls = False, 13 | architecture = '64bit', 14 | ): 15 | self.architecture = architecture 16 | self.application_name = application_name 17 | self.clean_cache = clean_cache 18 | self.force_uninstalls = force_uninstalls 19 | self.python_version = python_version 20 | 21 | def pip(self, package): 22 | raise NotImplementedError 23 | 24 | def create_python(self): 25 | "Create a full, stand-alone python installation with the required packages" 26 | raise NotImplementedError # derived class provides this 27 | -------------------------------------------------------------------------------- /osnap/osnapy_mac.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import subprocess 4 | 5 | try: 6 | import pwd 7 | except ImportError: 8 | pass 9 | 10 | from osnap import get_logger, __application_name__, TEMP_FOLDER, CACHE_FOLDER 11 | import osnap.util 12 | import osnap.osnapy_base 13 | 14 | LOGGER = get_logger(__application_name__) 15 | 16 | 17 | class OsnapyMac(osnap.osnapy_base.OsnapyBase): 18 | 19 | def pip(self, package): 20 | pip_path = os.path.join(osnap.util.get_osnapy_path_in_application_dir(self.application_name), 'bin', 'pip3') 21 | cmd = ['sudo', '-H', pip_path, 'install', '-U'] 22 | if package is None: 23 | cmd += ['-r', 'requirements.txt'] 24 | else: 25 | cmd.append(package) 26 | cmd_str = ' '.join(cmd) 27 | LOGGER.debug('executing %s', cmd_str) 28 | return subprocess.check_call(cmd_str, shell=True) 29 | 30 | def create_python(self): 31 | 32 | if os.getuid() != 0: 33 | s = 'error: must execute as root (i.e. "sudo -H ") - you are "%s"' % pwd.getpwuid(os.getuid())[0] 34 | print(s) 35 | exit(s) 36 | 37 | LOGGER.debug('running as "%s"', pwd.getpwuid(os.getuid())[0]) 38 | 39 | root_dir = TEMP_FOLDER 40 | base_dir = os.path.join(root_dir, 'build_osx') 41 | cache_dir = os.path.join(root_dir, CACHE_FOLDER) 42 | logs_dir = os.path.abspath('logs') 43 | application_dir = osnap.util.get_application_dir(self.application_name) 44 | prefix_dir = osnap.util.get_osnapy_path_in_application_dir(self.application_name) 45 | python_str = 'Python-%s' % self.python_version 46 | source_file = python_str + '.tgz' 47 | 48 | # e.g. https://www.python.org/ftp/python/3.5.2/Python-3.5.2.tgz 49 | source_url = 'http://www.python.org/ftp/python/%s/%s' % (self.python_version, source_file) 50 | 51 | LOGGER.debug('cwd : %s (%s)', os.getcwd(), os.path.abspath(os.getcwd())) 52 | LOGGER.debug('base_dir : %s (%s)', base_dir, os.path.abspath(base_dir)) 53 | LOGGER.debug('cache_dir : %s (%s)', cache_dir, os.path.abspath(cache_dir)) 54 | LOGGER.debug('logs_dir : %s', logs_dir) 55 | LOGGER.debug('python_str : %s', python_str) 56 | LOGGER.debug('source_file : %s', source_file) 57 | LOGGER.debug('source_url : %s', source_url) 58 | LOGGER.debug('prefix_dir : %s', prefix_dir) 59 | LOGGER.debug('logs_dir : %s', logs_dir) 60 | 61 | if os.path.exists(application_dir): 62 | if self.force_uninstalls: 63 | osnap.util.rm_mk_tree(application_dir) 64 | else: 65 | s = 'error : the application "%s" already exists at %s - please uninstall before continuing' % \ 66 | (self.application_name, application_dir) 67 | print(s) 68 | exit(s) 69 | 70 | if self.clean_cache: 71 | osnap.util.rm_mk_tree(cache_dir) 72 | try: 73 | os.makedirs(cache_dir) 74 | except FileExistsError: 75 | pass 76 | 77 | osnap.util.rm_mk_tree(base_dir) 78 | osnap.util.rm_mk_tree(prefix_dir) 79 | osnap.util.rm_mk_tree(logs_dir) 80 | 81 | osnap.util.get(source_url, cache_dir, source_file) 82 | osnap.util.extract(cache_dir, source_file, base_dir) 83 | 84 | save_cwd = os.getcwd() 85 | os.chdir(os.path.join(base_dir, python_str)) 86 | 87 | # todo: a check for xcode 88 | # todo: a check for SSL 89 | 90 | self._make_call(' '.join(['./configure', '--enable-shared=no', '--prefix=%s' % prefix_dir]), logs_dir, 91 | 'configure') 92 | self._make_call('make', logs_dir, 'make') 93 | self._make_call('make install', logs_dir, 'make_install') 94 | 95 | os.chdir(save_cwd) 96 | 97 | def _make_call(self, cmd_str, logs_dir, label): 98 | env = {'CPPFLAGS': '-I/usr/local/opt/openssl/include', 99 | 'LDFLAGS': '-L/usr/local/opt/openssl/lib', 100 | 'PATH': '/opt/local/bin:/opt/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin'} 101 | LOGGER.debug("Executing %s with env %s", cmd_str, env) 102 | subprocess.check_call(cmd_str, env=env, shell=True, 103 | stdout=open(os.path.join(logs_dir, label + '.log'), 'w'), 104 | stderr=open(os.path.join(logs_dir, label + '_err.log'), 'w')) 105 | -------------------------------------------------------------------------------- /osnap/osnapy_win.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import platform 4 | import subprocess 5 | import shutil 6 | import glob 7 | 8 | 9 | from osnap import __application_name__, get_logger, python_folder 10 | import osnap.make_nsis 11 | import osnap.util 12 | import osnap.osnapy_base 13 | 14 | LOGGER = get_logger(__application_name__) 15 | 16 | 17 | class OsnapyWin(osnap.osnapy_base.OsnapyBase): 18 | 19 | def create_python(self): 20 | """ 21 | Create a full, stand-alone python installation with the required packages 22 | """ 23 | 24 | cache_folder = 'cache' 25 | 26 | osnap.util.make_dir(python_folder, True) 27 | osnap.util.make_dir(cache_folder, self.clean_cache) 28 | 29 | # get the embeddable Python .zip 30 | if self.architecture == '64bit': 31 | zip_file = 'python-%s-embed-amd64.zip' % self.python_version 32 | elif self.architecture == '32bit': 33 | zip_file = 'python-%s-embed-win32.zip' % self.python_version 34 | else: 35 | raise Exception("Sorry, we don't currently support your architecture on windows ({}). Please submit a ticket at https://github.com/jamesabel/osnap/issues/".format(platform.machine())) 36 | 37 | zip_url = 'https://www.python.org/ftp/python/{}/{}'.format(self.python_version, zip_file) 38 | LOGGER.debug("Getting embeddable python from %s", zip_url) 39 | if osnap.util.get(zip_url, cache_folder, zip_file): 40 | osnap.util.extract(cache_folder, zip_file, python_folder) 41 | else: 42 | raise Exception('could not get embeddable Python ({} from {}) - exiting'.format(zip_file, zip_url)) 43 | 44 | # we need to use an unzipped version of pythonXX.zip since some packages can't read into the .zip 45 | # (e.g. https://bugs.python.org/issue24960) 46 | zip_list = glob.glob(os.path.join(python_folder, 'python*.zip')) 47 | if len(zip_list) != 1: 48 | raise Exception('too many zip files in {}'.format(zip_list)) 49 | pythonxx_zip_path = zip_list[0] 50 | temp_file = 'temp.zip' 51 | temp_path = os.path.join(python_folder, temp_file) 52 | os.rename(pythonxx_zip_path, temp_path) 53 | osnap.util.extract(python_folder, temp_file, 54 | os.path.join(python_folder, os.path.basename(pythonxx_zip_path))) 55 | os.remove(temp_path) 56 | 57 | python_path = os.path.join(python_folder, 'python.exe') 58 | 59 | # Programmatically edit ._pth file, e.g. osnapy\python36._pth 60 | # see https://github.com/pypa/pip/issues/4207 61 | glob_path = os.path.join(python_folder, 'python*._pth') 62 | pth_glob = glob.glob(glob_path) 63 | if pth_glob is None or len(pth_glob) != 1: 64 | LOGGER.critical("could not find '._pth' file at %s" % glob_path) 65 | else: 66 | pth_path = pth_glob[0] 67 | LOGGER.info('uncommenting import site in %s' % pth_path) 68 | pth_contents = open(pth_path).read() 69 | pth_save_path = pth_path.replace('._pth', '_orig._pth') 70 | shutil.move(pth_path, pth_save_path) 71 | pth_contents = pth_contents.replace('#import site', 'import site') # uncomment import site 72 | pth_contents = '..\n' + pth_contents # add where main.py will be (one dir 'up' from python.exe) 73 | open(pth_path, 'w').write(pth_contents) 74 | 75 | # get and install pip 76 | get_pip_file = 'get-pip.py' 77 | osnap.util.get('https://bootstrap.pypa.io/get-pip.py', cache_folder, get_pip_file) 78 | 79 | shutil.copyfile(os.path.join(cache_folder, get_pip_file), os.path.join(python_folder, get_pip_file)) 80 | cmd = [python_path, os.path.join(python_folder, get_pip_file)] 81 | LOGGER.debug('Executing %s', cmd) 82 | subprocess.check_call(cmd) 83 | 84 | def pip(self, package): 85 | cmd = [os.path.join(python_folder, 'Scripts', 'pip.exe'), 'install', '-U'] 86 | if package is None: 87 | cmd += ['-r', 'requirements.txt'] 88 | else: 89 | cmd.append(package) 90 | cmd_str = ' '.join(cmd) 91 | LOGGER.debug('executing %s', cmd_str) 92 | return subprocess.check_call(cmd_str, shell=True) 93 | 94 | 95 | -------------------------------------------------------------------------------- /osnap/template.pkgproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PACKAGES 6 | 7 | 8 | PACKAGE_FILES 9 | 10 | DEFAULT_INSTALL_LOCATION 11 | / 12 | HIERARCHY 13 | 14 | CHILDREN 15 | 16 | 17 | CHILDREN 18 | 19 | 20 | CHILDREN 21 | 22 | GID 23 | 80 24 | PATH 25 | dist/{{ application_name }}.app 26 | PATH_TYPE 27 | 1 28 | PERMISSIONS 29 | 493 30 | TYPE 31 | 3 32 | UID 33 | 0 34 | 35 | 36 | GID 37 | 80 38 | PATH 39 | Applications 40 | PATH_TYPE 41 | 0 42 | PERMISSIONS 43 | 509 44 | TYPE 45 | 1 46 | UID 47 | 0 48 | 49 | 50 | CHILDREN 51 | 52 | 53 | CHILDREN 54 | 55 | GID 56 | 80 57 | PATH 58 | Application Support 59 | PATH_TYPE 60 | 0 61 | PERMISSIONS 62 | 493 63 | TYPE 64 | 1 65 | UID 66 | 0 67 | 68 | 69 | CHILDREN 70 | 71 | GID 72 | 0 73 | PATH 74 | Automator 75 | PATH_TYPE 76 | 0 77 | PERMISSIONS 78 | 493 79 | TYPE 80 | 1 81 | UID 82 | 0 83 | 84 | 85 | CHILDREN 86 | 87 | GID 88 | 0 89 | PATH 90 | Documentation 91 | PATH_TYPE 92 | 0 93 | PERMISSIONS 94 | 493 95 | TYPE 96 | 1 97 | UID 98 | 0 99 | 100 | 101 | CHILDREN 102 | 103 | GID 104 | 0 105 | PATH 106 | Extensions 107 | PATH_TYPE 108 | 0 109 | PERMISSIONS 110 | 493 111 | TYPE 112 | 1 113 | UID 114 | 0 115 | 116 | 117 | CHILDREN 118 | 119 | GID 120 | 0 121 | PATH 122 | Filesystems 123 | PATH_TYPE 124 | 0 125 | PERMISSIONS 126 | 493 127 | TYPE 128 | 1 129 | UID 130 | 0 131 | 132 | 133 | CHILDREN 134 | 135 | GID 136 | 0 137 | PATH 138 | Frameworks 139 | PATH_TYPE 140 | 0 141 | PERMISSIONS 142 | 493 143 | TYPE 144 | 1 145 | UID 146 | 0 147 | 148 | 149 | CHILDREN 150 | 151 | GID 152 | 0 153 | PATH 154 | Input Methods 155 | PATH_TYPE 156 | 0 157 | PERMISSIONS 158 | 493 159 | TYPE 160 | 1 161 | UID 162 | 0 163 | 164 | 165 | CHILDREN 166 | 167 | GID 168 | 0 169 | PATH 170 | Internet Plug-Ins 171 | PATH_TYPE 172 | 0 173 | PERMISSIONS 174 | 493 175 | TYPE 176 | 1 177 | UID 178 | 0 179 | 180 | 181 | CHILDREN 182 | 183 | GID 184 | 0 185 | PATH 186 | LaunchAgents 187 | PATH_TYPE 188 | 0 189 | PERMISSIONS 190 | 493 191 | TYPE 192 | 1 193 | UID 194 | 0 195 | 196 | 197 | CHILDREN 198 | 199 | GID 200 | 0 201 | PATH 202 | LaunchDaemons 203 | PATH_TYPE 204 | 0 205 | PERMISSIONS 206 | 493 207 | TYPE 208 | 1 209 | UID 210 | 0 211 | 212 | 213 | CHILDREN 214 | 215 | GID 216 | 0 217 | PATH 218 | PreferencePanes 219 | PATH_TYPE 220 | 0 221 | PERMISSIONS 222 | 493 223 | TYPE 224 | 1 225 | UID 226 | 0 227 | 228 | 229 | CHILDREN 230 | 231 | GID 232 | 0 233 | PATH 234 | Preferences 235 | PATH_TYPE 236 | 0 237 | PERMISSIONS 238 | 493 239 | TYPE 240 | 1 241 | UID 242 | 0 243 | 244 | 245 | CHILDREN 246 | 247 | GID 248 | 80 249 | PATH 250 | Printers 251 | PATH_TYPE 252 | 0 253 | PERMISSIONS 254 | 493 255 | TYPE 256 | 1 257 | UID 258 | 0 259 | 260 | 261 | CHILDREN 262 | 263 | GID 264 | 0 265 | PATH 266 | PrivilegedHelperTools 267 | PATH_TYPE 268 | 0 269 | PERMISSIONS 270 | 493 271 | TYPE 272 | 1 273 | UID 274 | 0 275 | 276 | 277 | CHILDREN 278 | 279 | GID 280 | 0 281 | PATH 282 | QuickLook 283 | PATH_TYPE 284 | 0 285 | PERMISSIONS 286 | 493 287 | TYPE 288 | 1 289 | UID 290 | 0 291 | 292 | 293 | CHILDREN 294 | 295 | GID 296 | 0 297 | PATH 298 | QuickTime 299 | PATH_TYPE 300 | 0 301 | PERMISSIONS 302 | 493 303 | TYPE 304 | 1 305 | UID 306 | 0 307 | 308 | 309 | CHILDREN 310 | 311 | GID 312 | 0 313 | PATH 314 | Screen Savers 315 | PATH_TYPE 316 | 0 317 | PERMISSIONS 318 | 493 319 | TYPE 320 | 1 321 | UID 322 | 0 323 | 324 | 325 | CHILDREN 326 | 327 | GID 328 | 0 329 | PATH 330 | Scripts 331 | PATH_TYPE 332 | 0 333 | PERMISSIONS 334 | 493 335 | TYPE 336 | 1 337 | UID 338 | 0 339 | 340 | 341 | CHILDREN 342 | 343 | GID 344 | 0 345 | PATH 346 | Services 347 | PATH_TYPE 348 | 0 349 | PERMISSIONS 350 | 493 351 | TYPE 352 | 1 353 | UID 354 | 0 355 | 356 | 357 | CHILDREN 358 | 359 | GID 360 | 0 361 | PATH 362 | Widgets 363 | PATH_TYPE 364 | 0 365 | PERMISSIONS 366 | 493 367 | TYPE 368 | 1 369 | UID 370 | 0 371 | 372 | 373 | GID 374 | 0 375 | PATH 376 | Library 377 | PATH_TYPE 378 | 0 379 | PERMISSIONS 380 | 493 381 | TYPE 382 | 1 383 | UID 384 | 0 385 | 386 | 387 | CHILDREN 388 | 389 | 390 | CHILDREN 391 | 392 | GID 393 | 0 394 | PATH 395 | Shared 396 | PATH_TYPE 397 | 0 398 | PERMISSIONS 399 | 1023 400 | TYPE 401 | 1 402 | UID 403 | 0 404 | 405 | 406 | GID 407 | 80 408 | PATH 409 | Users 410 | PATH_TYPE 411 | 0 412 | PERMISSIONS 413 | 493 414 | TYPE 415 | 1 416 | UID 417 | 0 418 | 419 | 420 | GID 421 | 0 422 | PATH 423 | / 424 | PATH_TYPE 425 | 0 426 | PERMISSIONS 427 | 493 428 | TYPE 429 | 1 430 | UID 431 | 0 432 | 433 | PAYLOAD_TYPE 434 | 0 435 | VERSION 436 | 4 437 | 438 | PACKAGE_SCRIPTS 439 | 440 | RESOURCES 441 | 442 | 443 | PACKAGE_SETTINGS 444 | 445 | AUTHENTICATION 446 | 1 447 | CONCLUSION_ACTION 448 | 0 449 | IDENTIFIER 450 | {{ reverse_dns_identifier }} 451 | NAME 452 | {{ application_name }} 453 | OVERWRITE_PERMISSIONS 454 | 455 | VERSION 456 | 0.0 457 | 458 | UUID 459 | {{ uuid }} 460 | 461 | 462 | PROJECT 463 | 464 | PROJECT_COMMENTS 465 | 466 | NOTES 467 | 468 | PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBIVE1M 469 | IDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQv 470 | c3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPG1ldGEgaHR0cC1l 471 | cXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7 472 | IGNoYXJzZXQ9VVRGLTgiPgo8bWV0YSBodHRwLWVxdWl2PSJDb250 473 | ZW50LVN0eWxlLVR5cGUiIGNvbnRlbnQ9InRleHQvY3NzIj4KPHRp 474 | dGxlPjwvdGl0bGU+CjxtZXRhIG5hbWU9IkdlbmVyYXRvciIgY29u 475 | dGVudD0iQ29jb2EgSFRNTCBXcml0ZXIiPgo8bWV0YSBuYW1lPSJD 476 | b2NvYVZlcnNpb24iIGNvbnRlbnQ9IjE0MDQuNDciPgo8c3R5bGUg 477 | dHlwZT0idGV4dC9jc3MiPgo8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5 478 | Pgo8L2JvZHk+CjwvaHRtbD4K 479 | 480 | 481 | PROJECT_PRESENTATION 482 | 483 | INSTALLATION_STEPS 484 | 485 | 486 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 487 | ICPresentationViewIntroductionController 488 | INSTALLER_PLUGIN 489 | Introduction 490 | LIST_TITLE_KEY 491 | InstallerSectionTitle 492 | 493 | 494 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 495 | ICPresentationViewReadMeController 496 | INSTALLER_PLUGIN 497 | ReadMe 498 | LIST_TITLE_KEY 499 | InstallerSectionTitle 500 | 501 | 502 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 503 | ICPresentationViewLicenseController 504 | INSTALLER_PLUGIN 505 | License 506 | LIST_TITLE_KEY 507 | InstallerSectionTitle 508 | 509 | 510 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 511 | ICPresentationViewDestinationSelectController 512 | INSTALLER_PLUGIN 513 | TargetSelect 514 | LIST_TITLE_KEY 515 | InstallerSectionTitle 516 | 517 | 518 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 519 | ICPresentationViewInstallationTypeController 520 | INSTALLER_PLUGIN 521 | PackageSelection 522 | LIST_TITLE_KEY 523 | InstallerSectionTitle 524 | 525 | 526 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 527 | ICPresentationViewInstallationController 528 | INSTALLER_PLUGIN 529 | Install 530 | LIST_TITLE_KEY 531 | InstallerSectionTitle 532 | 533 | 534 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 535 | ICPresentationViewSummaryController 536 | INSTALLER_PLUGIN 537 | Summary 538 | LIST_TITLE_KEY 539 | InstallerSectionTitle 540 | 541 | 542 | INTRODUCTION 543 | 544 | LOCALIZATIONS 545 | 546 | 547 | TITLE 548 | 549 | LOCALIZATIONS 550 | 551 | 552 | LANGUAGE 553 | English 554 | VALUE 555 | {{ application_name }} 556 | 557 | 558 | 559 | 560 | PROJECT_REQUIREMENTS 561 | 562 | LIST 563 | 564 | POSTINSTALL_PATH 565 | 566 | PREINSTALL_PATH 567 | 568 | RESOURCES 569 | 570 | ROOT_VOLUME_ONLY 571 | 572 | 573 | PROJECT_SETTINGS 574 | 575 | ADVANCED_OPTIONS 576 | 577 | BUILD_FORMAT 578 | 0 579 | BUILD_PATH 580 | 581 | PATH 582 | build 583 | PATH_TYPE 584 | 1 585 | 586 | EXCLUDED_FILES 587 | 588 | 589 | PATTERNS_ARRAY 590 | 591 | 592 | REGULAR_EXPRESSION 593 | 594 | STRING 595 | .DS_Store 596 | TYPE 597 | 0 598 | 599 | 600 | PROTECTED 601 | 602 | PROXY_NAME 603 | Remove .DS_Store files 604 | PROXY_TOOLTIP 605 | Remove ".DS_Store" files created by the Finder. 606 | STATE 607 | 608 | 609 | 610 | PATTERNS_ARRAY 611 | 612 | 613 | REGULAR_EXPRESSION 614 | 615 | STRING 616 | .pbdevelopment 617 | TYPE 618 | 0 619 | 620 | 621 | PROTECTED 622 | 623 | PROXY_NAME 624 | Remove .pbdevelopment files 625 | PROXY_TOOLTIP 626 | Remove ".pbdevelopment" files created by ProjectBuilder or Xcode. 627 | STATE 628 | 629 | 630 | 631 | PATTERNS_ARRAY 632 | 633 | 634 | REGULAR_EXPRESSION 635 | 636 | STRING 637 | CVS 638 | TYPE 639 | 1 640 | 641 | 642 | REGULAR_EXPRESSION 643 | 644 | STRING 645 | .cvsignore 646 | TYPE 647 | 0 648 | 649 | 650 | REGULAR_EXPRESSION 651 | 652 | STRING 653 | .cvspass 654 | TYPE 655 | 0 656 | 657 | 658 | REGULAR_EXPRESSION 659 | 660 | STRING 661 | .svn 662 | TYPE 663 | 1 664 | 665 | 666 | REGULAR_EXPRESSION 667 | 668 | STRING 669 | .git 670 | TYPE 671 | 1 672 | 673 | 674 | REGULAR_EXPRESSION 675 | 676 | STRING 677 | .gitignore 678 | TYPE 679 | 0 680 | 681 | 682 | PROTECTED 683 | 684 | PROXY_NAME 685 | Remove SCM metadata 686 | PROXY_TOOLTIP 687 | Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems. 688 | STATE 689 | 690 | 691 | 692 | PATTERNS_ARRAY 693 | 694 | 695 | REGULAR_EXPRESSION 696 | 697 | STRING 698 | classes.nib 699 | TYPE 700 | 0 701 | 702 | 703 | REGULAR_EXPRESSION 704 | 705 | STRING 706 | designable.db 707 | TYPE 708 | 0 709 | 710 | 711 | REGULAR_EXPRESSION 712 | 713 | STRING 714 | info.nib 715 | TYPE 716 | 0 717 | 718 | 719 | PROTECTED 720 | 721 | PROXY_NAME 722 | Optimize nib files 723 | PROXY_TOOLTIP 724 | Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles. 725 | STATE 726 | 727 | 728 | 729 | PATTERNS_ARRAY 730 | 731 | 732 | REGULAR_EXPRESSION 733 | 734 | STRING 735 | Resources Disabled 736 | TYPE 737 | 1 738 | 739 | 740 | PROTECTED 741 | 742 | PROXY_NAME 743 | Remove Resources Disabled folders 744 | PROXY_TOOLTIP 745 | Remove "Resources Disabled" folders. 746 | STATE 747 | 748 | 749 | 750 | SEPARATOR 751 | 752 | 753 | 754 | PATTERNS_ARRAY 755 | 756 | 757 | REGULAR_EXPRESSION 758 | 759 | STRING 760 | 761 | TYPE 762 | 1 763 | 764 | 765 | PROTECTED 766 | 767 | STATE 768 | 769 | 770 | 771 | NAME 772 | {{ application_name }} 773 | 774 | 775 | TYPE 776 | 0 777 | VERSION 778 | 2 779 | 780 | 781 | -------------------------------------------------------------------------------- /osnap/util.py: -------------------------------------------------------------------------------- 1 | 2 | import platform 3 | import os 4 | import shutil 5 | import zipfile 6 | import tarfile 7 | import time 8 | 9 | import requests 10 | 11 | from osnap import __application_name__, get_logger 12 | 13 | LOGGER = get_logger(__application_name__) 14 | 15 | 16 | def is_windows(): 17 | return platform.system().lower()[0] == 'w' 18 | 19 | 20 | def is_mac(): 21 | # macOS/OSX reports 'Darwin' 22 | return platform.system().lower()[0] == 'd' 23 | 24 | 25 | def get_os_name(): 26 | if is_mac(): 27 | return 'mac' 28 | elif is_windows(): 29 | return 'win' 30 | else: 31 | raise NotImplementedError 32 | 33 | 34 | def get_launch_name(architecture, variant): 35 | if is_mac(): 36 | return 'launchmac' 37 | elif is_windows(): 38 | if variant == 'console': 39 | if architecture == '32bit': 40 | return 'launchwin-x86-console' 41 | elif architecture == '64bit': 42 | return 'launchwin-amd64-console' 43 | else: 44 | if architecture == '32bit': 45 | return 'launchwin-x86-window' 46 | elif architecture == '64bit': 47 | return 'launchwin-amd64-window' 48 | raise Exception("Unrecognized architecture {} for windows".format(architecture)) 49 | else: 50 | raise Exception("Unrecognized operating system") 51 | 52 | def make_dir(path, remove): 53 | if remove and os.path.exists(path): 54 | LOGGER.debug('removing : %s' % path) 55 | shutil.rmtree(path) 56 | if not os.path.exists(path): 57 | LOGGER.debug('making folder : %s' % path) 58 | os.mkdir(path) 59 | 60 | 61 | def extract(source_folder, source_file, destination_folder): 62 | source = os.path.join(source_folder, source_file) 63 | LOGGER.debug('extracting %s to %s' % (source, destination_folder)) 64 | extension = source_file[source_file.rfind('.')+1:] 65 | if extension == 'zip': 66 | with zipfile.ZipFile(source) as zf: 67 | # assumes a trusted .zip 68 | zf.extractall(destination_folder) 69 | elif extension == 'tgz': 70 | with tarfile.open(source) as tf: 71 | tf.extractall(destination_folder) 72 | elif extension == 'gz': 73 | with tarfile.open(source) as tf: 74 | tf.extractall(destination_folder) 75 | else: 76 | raise Exception('Unsupported file type {} (extension : {})'.format(source_file, extension)) 77 | 78 | 79 | def tgz(source_dir, tgz_file_path): 80 | LOGGER.debug('tgz-ing %s (%s) to %s (%s)' % (source_dir, os.path.abspath(source_dir), tgz_file_path, os.path.abspath(tgz_file_path))) 81 | with tarfile.open(tgz_file_path, "w:gz") as tar: 82 | tar.add(source_dir, arcname=os.path.basename(source_dir)) 83 | 84 | 85 | def get(url, destination_folder, file_name): 86 | destination_path = os.path.join(destination_folder, file_name) 87 | if os.path.exists(destination_path): 88 | LOGGER.info('using existing copy of %s from %s' % (file_name, os.path.abspath(destination_path))) 89 | else: 90 | LOGGER.info('get %s to %s' % (url, destination_path)) 91 | response = requests.get(url, stream=True) 92 | if response.status_code == 200: 93 | with open(destination_path, 'wb') as out_file: 94 | shutil.copyfileobj(response.raw, out_file) 95 | del response 96 | else: 97 | raise Exception('error getting {} from {}'.format(file_name, url)) 98 | return True 99 | 100 | 101 | def rm_mk_tree(dir_path): 102 | ''' 103 | Clears out a dir. Makes it if it doesn't exist. 104 | :param dir_path: dir to clear out 105 | :return: True on success 106 | ''' 107 | 108 | # fancy rmtree, since for some reason shutil.rmtree can return before the tree is actually removed 109 | count = 0 110 | LOGGER.debug('removing %s (%s)', dir_path, os.path.abspath(dir_path)) 111 | while os.path.exists(dir_path) and count < 30: 112 | try: 113 | shutil.rmtree(dir_path) 114 | except FileNotFoundError: 115 | pass 116 | except IOError: 117 | if count > 1: 118 | LOGGER.info('retrying removal of %s - perhaps you need to run this as sudo?', dir_path) 119 | time.sleep(2) 120 | count += 1 121 | if os.path.exists(dir_path): 122 | raise Exception('error: could not remove {} - exiting'.format(dir_path)) 123 | 124 | LOGGER.info('making %s (%s)', dir_path, os.path.abspath(dir_path)) 125 | os.makedirs(dir_path) 126 | 127 | return count > 0 128 | 129 | 130 | def get_osnapy_tgz_file_and_url(python_version, os_version): 131 | # https://s3.amazonaws.com/abel.co/osnapy/osnapy_3.5.2_osx.tgz 132 | tgz_file = 'osnapy_%s_%s.tgz' % (python_version, os_version) 133 | return tgz_file, 'https://s3.amazonaws.com/abel.co/osnapy/%s' % tgz_file 134 | 135 | 136 | def get_application_dir(application_name): 137 | return os.path.join(os.sep, 'Applications', application_name + '.app') 138 | 139 | 140 | def get_osnapy_path_in_application_dir(application_name): 141 | # We're building right into the /Application directory which I don't like, but Python's build process 142 | # on Mac requires an absolute path so we have to create osnapy explicitly in its final resting place. 143 | # This will require sudo for the "make install". 144 | # Reference: http://www.diveintopython.net/installing_python/source.html 145 | # Hopefully eventually the install dir won't have to be an absolute path. 146 | return os.path.join(get_application_dir(application_name), 'Contents', 'MacOS', osnap.const.python_folder) 147 | -------------------------------------------------------------------------------- /pypi.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | venv/bin/python setup.py sdist upload -r pypi 3 | -------------------------------------------------------------------------------- /readme.rst: -------------------------------------------------------------------------------- 1 | Note: osnap has been superceeded by pyship. See http://www.pyship.org 2 | ====================================================================== 3 | 4 | osnap - Overly Simplistic Native Application tool for Python 5 | ============================================================ 6 | 7 | Announcements 8 | ------------- 9 | ``OSNAP`` 0.0.6 has been released on github and PyPI. 10 | 11 | New on 0.0.6: 12 | 13 | - 32 bit Windows support 14 | 15 | - console app (not just Windows) support 16 | 17 | - don't require a requirements.txt (i.e. if your Python program is only using built-ins) 18 | 19 | - properly use logging package (drop verbose parameters) 20 | 21 | - provide a switch to not create the NSIS installer (just stop after creating the binary) 22 | 23 | - version checking 24 | 25 | 26 | Introduction 27 | ------------ 28 | ``OSNAP`` is a way to deliver self-contained Python applications to end users for Windows and OSX/MacOS. 29 | This process is known as freezing and installing. Examples include delivering Python applications to Windows 30 | PCs (both laptops and desktops), MacBooks, and iMacs. 31 | 32 | Now on PyPI: 33 | ``pip install osnap`` 34 | 35 | See the documentation at `readthedocs `_ . 36 | 37 | Contributors 38 | ------------ 39 | 40 | James Abel 41 | 42 | Eli Ribble 43 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | sphinx 3 | jinja2 4 | lxml 5 | appdirs 6 | wheel 7 | twine 8 | keyring 9 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = readme.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | 2 | from setuptools import setup 3 | 4 | import osnap 5 | 6 | application_name = 'osnap' 7 | launcher_dir = 'launchers' 8 | 9 | setup( 10 | name=application_name, 11 | description='Turns Python applications into native applications for Windows and OSX/MacOS', 12 | version=osnap.__version__, 13 | author='James Abel', 14 | author_email='j@abel.co', 15 | url='http://osnap.abel.co', 16 | download_url='https://github.com/jamesabel/osnap/tarball/' + osnap.__version__, 17 | keywords=['freeze', 'application', 'native'], 18 | 19 | package_data={'': ['*.pkgproj', 'launch*.zip']}, 20 | 21 | packages=[application_name], 22 | install_requires=[ 23 | 'appdirs', 'requests', 'jinja2', 'lxml' 24 | ], 25 | classifiers=[] 26 | ) 27 | -------------------------------------------------------------------------------- /test_example/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2017 James Abel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /test_example/all.bat: -------------------------------------------------------------------------------- 1 | call make_venv.bat 2 | call make_osnapy.bat 3 | call make_installer.bat 4 | -------------------------------------------------------------------------------- /test_example/all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ./make_venv.sh 3 | ./make_osnapy.sh 4 | ./make_installer.sh 5 | -------------------------------------------------------------------------------- /test_example/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesabel/osnap/fc3f2affc3190d91f0465c35971e8f877269d5fd/test_example/icons/icon.icns -------------------------------------------------------------------------------- /test_example/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesabel/osnap/fc3f2affc3190d91f0465c35971e8f877269d5fd/test_example/icons/icon.ico -------------------------------------------------------------------------------- /test_example/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesabel/osnap/fc3f2affc3190d91f0465c35971e8f877269d5fd/test_example/icons/icon.png -------------------------------------------------------------------------------- /test_example/icons/source.txt: -------------------------------------------------------------------------------- 1 | http://www.iconarchive.com/show/flatastic-9-icons-by-custom-icon-design/Tests-icon.html 2 | -------------------------------------------------------------------------------- /test_example/install_osnap.bat: -------------------------------------------------------------------------------- 1 | pushd . 2 | cd .. 3 | test_example\venv\scripts\python setup.py install 4 | popd -------------------------------------------------------------------------------- /test_example/install_osnap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | pushd . 3 | cd .. 4 | test_example/venv/bin/python setup.py install 5 | popd 6 | -------------------------------------------------------------------------------- /test_example/main.py: -------------------------------------------------------------------------------- 1 | 2 | # the test/example application 3 | 4 | # must be called "main.py" so that it get bundled and launched correctly 5 | 6 | import sys 7 | import logging 8 | import appdirs 9 | import os 10 | 11 | import test_example.mymodule 12 | 13 | APPLICATION_NAME = 'test_example' 14 | AUTHOR = 'test_author' 15 | 16 | 17 | def main(): 18 | 19 | # set up logger like any normal GUI application 20 | logger = logging.getLogger(APPLICATION_NAME) 21 | 22 | log_folder = appdirs.user_log_dir(APPLICATION_NAME, AUTHOR) 23 | if not os.path.exists(log_folder): 24 | os.makedirs(log_folder) 25 | log_file_path = os.path.join(log_folder, 'launch.log') 26 | print(log_file_path) 27 | 28 | file_handler = logging.FileHandler(log_file_path) 29 | formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s') 30 | file_handler.setFormatter(formatter) 31 | logger.addHandler(file_handler) 32 | logger.setLevel(logging.INFO) 33 | 34 | # illustrate what Python we are running 35 | logger.info('test_example : sys.version : %s' % str(sys.version)) 36 | logger.info('test_example : sys.path : %s' % str(sys.path)) 37 | 38 | # run my application 39 | test_example.mymodule.run() 40 | 41 | if __name__ == '__main__': 42 | main() 43 | -------------------------------------------------------------------------------- /test_example/main.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | /Applications/test_example.app/Contents/MacOS/osnapy/bin/python3 main.py -------------------------------------------------------------------------------- /test_example/make_installer.bat: -------------------------------------------------------------------------------- 1 | venv\scripts\python make_installer.py 2 | -------------------------------------------------------------------------------- /test_example/make_installer.py: -------------------------------------------------------------------------------- 1 | 2 | import osnap.installer 3 | 4 | import test_example 5 | 6 | 7 | def make_installer(): 8 | osnap.installer.make_installer(test_example.__python_version__, test_example.__application_name__, 9 | test_example.__version__, test_example.__author__, 'this is my test example', 10 | 'www.abel.co', variant='window' 11 | ) 12 | 13 | 14 | if __name__ == '__main__': 15 | make_installer() 16 | -------------------------------------------------------------------------------- /test_example/make_installer.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | venv/bin/python3 make_installer.py 3 | 4 | -------------------------------------------------------------------------------- /test_example/make_osnapy.bat: -------------------------------------------------------------------------------- 1 | REM can be done either way 2 | REM venv\scripts\python make_osnapy.py 3 | venv\scripts\python.exe -m osnap.osnapy -------------------------------------------------------------------------------- /test_example/make_osnapy.py: -------------------------------------------------------------------------------- 1 | 2 | import osnap.osnapy 3 | 4 | import test_example 5 | 6 | 7 | def create_osnapy(verbose): 8 | 9 | osnap.osnapy.make_osnapy(test_example.__python_version__, application_name=test_example.__application_name__, 10 | force_app_uninstall=True) 11 | 12 | 13 | if __name__ == '__main__': 14 | create_osnapy(True) 15 | -------------------------------------------------------------------------------- /test_example/make_osnapy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # needs sudo since we are installing into /Applications 3 | # -H is required 4 | sudo -H venv/bin/python3 make_osnapy.py 5 | -------------------------------------------------------------------------------- /test_example/make_venv.bat: -------------------------------------------------------------------------------- 1 | REM point this to your python 3 2 | set MYPYTHONHOME="c:\Program Files\Python36" 3 | echo %MYPYTHONHOME% 4 | %MYPYTHONHOME%\python.exe -m venv --clear venv 5 | .\venv\Scripts\pip.exe install -U pip 6 | .\venv\Scripts\pip.exe install -U -r requirements.txt 7 | REM install osnap 8 | pushd . 9 | cd .. 10 | rm -r build 11 | .\test_example\venv\Scripts\python.exe setup.py install 12 | popd 13 | -------------------------------------------------------------------------------- /test_example/make_venv.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -x 3 | python3.6 -m venv --clear venv 4 | ./venv/bin/pip3 install -U pip 5 | ./venv/bin/pip3 install -U setuptools 6 | ./venv/bin/pip3 install -U -r requirements.txt 7 | # install osnap 8 | pushd . 9 | cd .. 10 | rm -r build 11 | ./test_example/venv/bin/python3 setup.py install 12 | popd 13 | -------------------------------------------------------------------------------- /test_example/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | This is both an example for osnap as well as a regression test. -------------------------------------------------------------------------------- /test_example/requirements.txt: -------------------------------------------------------------------------------- 1 | appdirs 2 | PyQt5 3 | cryptography 4 | # 5 | # some packages seem to have a problem if spec'd as a dependency in osnap so install it here as a workaround for now 6 | lxml 7 | jinja2 8 | requests 9 | # 10 | # regular osnap users would have osnap in requirements.txt but since this is our dev environment we install from our local copy 11 | # osnap -------------------------------------------------------------------------------- /test_example/run_app.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # command line version of clicking on the application in Finder 3 | open -a test_example 4 | -------------------------------------------------------------------------------- /test_example/test_example.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesabel/osnap/fc3f2affc3190d91f0465c35971e8f877269d5fd/test_example/test_example.ico -------------------------------------------------------------------------------- /test_example/test_example.pkgproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PACKAGES 6 | 7 | 8 | PACKAGE_FILES 9 | 10 | DEFAULT_INSTALL_LOCATION 11 | / 12 | HIERARCHY 13 | 14 | CHILDREN 15 | 16 | 17 | CHILDREN 18 | 19 | 20 | CHILDREN 21 | 22 | GID 23 | 80 24 | PATH 25 | dist/test_example.app 26 | PATH_TYPE 27 | 1 28 | PERMISSIONS 29 | 493 30 | TYPE 31 | 3 32 | UID 33 | 0 34 | 35 | 36 | GID 37 | 80 38 | PATH 39 | Applications 40 | PATH_TYPE 41 | 0 42 | PERMISSIONS 43 | 509 44 | TYPE 45 | 1 46 | UID 47 | 0 48 | 49 | 50 | CHILDREN 51 | 52 | 53 | CHILDREN 54 | 55 | GID 56 | 80 57 | PATH 58 | Application Support 59 | PATH_TYPE 60 | 0 61 | PERMISSIONS 62 | 493 63 | TYPE 64 | 1 65 | UID 66 | 0 67 | 68 | 69 | CHILDREN 70 | 71 | GID 72 | 0 73 | PATH 74 | Automator 75 | PATH_TYPE 76 | 0 77 | PERMISSIONS 78 | 493 79 | TYPE 80 | 1 81 | UID 82 | 0 83 | 84 | 85 | CHILDREN 86 | 87 | GID 88 | 0 89 | PATH 90 | Documentation 91 | PATH_TYPE 92 | 0 93 | PERMISSIONS 94 | 493 95 | TYPE 96 | 1 97 | UID 98 | 0 99 | 100 | 101 | CHILDREN 102 | 103 | GID 104 | 0 105 | PATH 106 | Extensions 107 | PATH_TYPE 108 | 0 109 | PERMISSIONS 110 | 493 111 | TYPE 112 | 1 113 | UID 114 | 0 115 | 116 | 117 | CHILDREN 118 | 119 | GID 120 | 0 121 | PATH 122 | Filesystems 123 | PATH_TYPE 124 | 0 125 | PERMISSIONS 126 | 493 127 | TYPE 128 | 1 129 | UID 130 | 0 131 | 132 | 133 | CHILDREN 134 | 135 | GID 136 | 0 137 | PATH 138 | Frameworks 139 | PATH_TYPE 140 | 0 141 | PERMISSIONS 142 | 493 143 | TYPE 144 | 1 145 | UID 146 | 0 147 | 148 | 149 | CHILDREN 150 | 151 | GID 152 | 0 153 | PATH 154 | Input Methods 155 | PATH_TYPE 156 | 0 157 | PERMISSIONS 158 | 493 159 | TYPE 160 | 1 161 | UID 162 | 0 163 | 164 | 165 | CHILDREN 166 | 167 | GID 168 | 0 169 | PATH 170 | Internet Plug-Ins 171 | PATH_TYPE 172 | 0 173 | PERMISSIONS 174 | 493 175 | TYPE 176 | 1 177 | UID 178 | 0 179 | 180 | 181 | CHILDREN 182 | 183 | GID 184 | 0 185 | PATH 186 | LaunchAgents 187 | PATH_TYPE 188 | 0 189 | PERMISSIONS 190 | 493 191 | TYPE 192 | 1 193 | UID 194 | 0 195 | 196 | 197 | CHILDREN 198 | 199 | GID 200 | 0 201 | PATH 202 | LaunchDaemons 203 | PATH_TYPE 204 | 0 205 | PERMISSIONS 206 | 493 207 | TYPE 208 | 1 209 | UID 210 | 0 211 | 212 | 213 | CHILDREN 214 | 215 | GID 216 | 0 217 | PATH 218 | PreferencePanes 219 | PATH_TYPE 220 | 0 221 | PERMISSIONS 222 | 493 223 | TYPE 224 | 1 225 | UID 226 | 0 227 | 228 | 229 | CHILDREN 230 | 231 | GID 232 | 0 233 | PATH 234 | Preferences 235 | PATH_TYPE 236 | 0 237 | PERMISSIONS 238 | 493 239 | TYPE 240 | 1 241 | UID 242 | 0 243 | 244 | 245 | CHILDREN 246 | 247 | GID 248 | 80 249 | PATH 250 | Printers 251 | PATH_TYPE 252 | 0 253 | PERMISSIONS 254 | 493 255 | TYPE 256 | 1 257 | UID 258 | 0 259 | 260 | 261 | CHILDREN 262 | 263 | GID 264 | 0 265 | PATH 266 | PrivilegedHelperTools 267 | PATH_TYPE 268 | 0 269 | PERMISSIONS 270 | 493 271 | TYPE 272 | 1 273 | UID 274 | 0 275 | 276 | 277 | CHILDREN 278 | 279 | GID 280 | 0 281 | PATH 282 | QuickLook 283 | PATH_TYPE 284 | 0 285 | PERMISSIONS 286 | 493 287 | TYPE 288 | 1 289 | UID 290 | 0 291 | 292 | 293 | CHILDREN 294 | 295 | GID 296 | 0 297 | PATH 298 | QuickTime 299 | PATH_TYPE 300 | 0 301 | PERMISSIONS 302 | 493 303 | TYPE 304 | 1 305 | UID 306 | 0 307 | 308 | 309 | CHILDREN 310 | 311 | GID 312 | 0 313 | PATH 314 | Screen Savers 315 | PATH_TYPE 316 | 0 317 | PERMISSIONS 318 | 493 319 | TYPE 320 | 1 321 | UID 322 | 0 323 | 324 | 325 | CHILDREN 326 | 327 | GID 328 | 0 329 | PATH 330 | Scripts 331 | PATH_TYPE 332 | 0 333 | PERMISSIONS 334 | 493 335 | TYPE 336 | 1 337 | UID 338 | 0 339 | 340 | 341 | CHILDREN 342 | 343 | GID 344 | 0 345 | PATH 346 | Services 347 | PATH_TYPE 348 | 0 349 | PERMISSIONS 350 | 493 351 | TYPE 352 | 1 353 | UID 354 | 0 355 | 356 | 357 | CHILDREN 358 | 359 | GID 360 | 0 361 | PATH 362 | Widgets 363 | PATH_TYPE 364 | 0 365 | PERMISSIONS 366 | 493 367 | TYPE 368 | 1 369 | UID 370 | 0 371 | 372 | 373 | GID 374 | 0 375 | PATH 376 | Library 377 | PATH_TYPE 378 | 0 379 | PERMISSIONS 380 | 493 381 | TYPE 382 | 1 383 | UID 384 | 0 385 | 386 | 387 | CHILDREN 388 | 389 | 390 | CHILDREN 391 | 392 | GID 393 | 0 394 | PATH 395 | Shared 396 | PATH_TYPE 397 | 0 398 | PERMISSIONS 399 | 1023 400 | TYPE 401 | 1 402 | UID 403 | 0 404 | 405 | 406 | GID 407 | 80 408 | PATH 409 | Users 410 | PATH_TYPE 411 | 0 412 | PERMISSIONS 413 | 493 414 | TYPE 415 | 1 416 | UID 417 | 0 418 | 419 | 420 | GID 421 | 0 422 | PATH 423 | / 424 | PATH_TYPE 425 | 0 426 | PERMISSIONS 427 | 493 428 | TYPE 429 | 1 430 | UID 431 | 0 432 | 433 | PAYLOAD_TYPE 434 | 0 435 | VERSION 436 | 4 437 | 438 | PACKAGE_SCRIPTS 439 | 440 | RESOURCES 441 | 442 | 443 | PACKAGE_SETTINGS 444 | 445 | AUTHENTICATION 446 | 1 447 | CONCLUSION_ACTION 448 | 0 449 | IDENTIFIER 450 | co.abel.test_example 451 | NAME 452 | test_example 453 | OVERWRITE_PERMISSIONS 454 | 455 | VERSION 456 | 0.0 457 | 458 | UUID 459 | 15d058e4-b22d-443c-ae6b-def1204844b0 460 | 461 | 462 | PROJECT 463 | 464 | PROJECT_COMMENTS 465 | 466 | NOTES 467 | 468 | PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBIVE1M 469 | IDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQv 470 | c3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPG1ldGEgaHR0cC1l 471 | cXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7 472 | IGNoYXJzZXQ9VVRGLTgiPgo8bWV0YSBodHRwLWVxdWl2PSJDb250 473 | ZW50LVN0eWxlLVR5cGUiIGNvbnRlbnQ9InRleHQvY3NzIj4KPHRp 474 | dGxlPjwvdGl0bGU+CjxtZXRhIG5hbWU9IkdlbmVyYXRvciIgY29u 475 | dGVudD0iQ29jb2EgSFRNTCBXcml0ZXIiPgo8bWV0YSBuYW1lPSJD 476 | b2NvYVZlcnNpb24iIGNvbnRlbnQ9IjE0MDQuNDciPgo8c3R5bGUg 477 | dHlwZT0idGV4dC9jc3MiPgo8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5 478 | Pgo8L2JvZHk+CjwvaHRtbD4K 479 | 480 | 481 | PROJECT_PRESENTATION 482 | 483 | INSTALLATION_STEPS 484 | 485 | 486 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 487 | ICPresentationViewIntroductionController 488 | INSTALLER_PLUGIN 489 | Introduction 490 | LIST_TITLE_KEY 491 | InstallerSectionTitle 492 | 493 | 494 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 495 | ICPresentationViewReadMeController 496 | INSTALLER_PLUGIN 497 | ReadMe 498 | LIST_TITLE_KEY 499 | InstallerSectionTitle 500 | 501 | 502 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 503 | ICPresentationViewLicenseController 504 | INSTALLER_PLUGIN 505 | License 506 | LIST_TITLE_KEY 507 | InstallerSectionTitle 508 | 509 | 510 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 511 | ICPresentationViewDestinationSelectController 512 | INSTALLER_PLUGIN 513 | TargetSelect 514 | LIST_TITLE_KEY 515 | InstallerSectionTitle 516 | 517 | 518 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 519 | ICPresentationViewInstallationTypeController 520 | INSTALLER_PLUGIN 521 | PackageSelection 522 | LIST_TITLE_KEY 523 | InstallerSectionTitle 524 | 525 | 526 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 527 | ICPresentationViewInstallationController 528 | INSTALLER_PLUGIN 529 | Install 530 | LIST_TITLE_KEY 531 | InstallerSectionTitle 532 | 533 | 534 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 535 | ICPresentationViewSummaryController 536 | INSTALLER_PLUGIN 537 | Summary 538 | LIST_TITLE_KEY 539 | InstallerSectionTitle 540 | 541 | 542 | INTRODUCTION 543 | 544 | LOCALIZATIONS 545 | 546 | 547 | TITLE 548 | 549 | LOCALIZATIONS 550 | 551 | 552 | LANGUAGE 553 | English 554 | VALUE 555 | test_example 556 | 557 | 558 | 559 | 560 | PROJECT_REQUIREMENTS 561 | 562 | LIST 563 | 564 | POSTINSTALL_PATH 565 | 566 | PREINSTALL_PATH 567 | 568 | RESOURCES 569 | 570 | ROOT_VOLUME_ONLY 571 | 572 | 573 | PROJECT_SETTINGS 574 | 575 | ADVANCED_OPTIONS 576 | 577 | BUILD_FORMAT 578 | 0 579 | BUILD_PATH 580 | 581 | PATH 582 | build 583 | PATH_TYPE 584 | 1 585 | 586 | EXCLUDED_FILES 587 | 588 | 589 | PATTERNS_ARRAY 590 | 591 | 592 | REGULAR_EXPRESSION 593 | 594 | STRING 595 | .DS_Store 596 | TYPE 597 | 0 598 | 599 | 600 | PROTECTED 601 | 602 | PROXY_NAME 603 | Remove .DS_Store files 604 | PROXY_TOOLTIP 605 | Remove ".DS_Store" files created by the Finder. 606 | STATE 607 | 608 | 609 | 610 | PATTERNS_ARRAY 611 | 612 | 613 | REGULAR_EXPRESSION 614 | 615 | STRING 616 | .pbdevelopment 617 | TYPE 618 | 0 619 | 620 | 621 | PROTECTED 622 | 623 | PROXY_NAME 624 | Remove .pbdevelopment files 625 | PROXY_TOOLTIP 626 | Remove ".pbdevelopment" files created by ProjectBuilder or Xcode. 627 | STATE 628 | 629 | 630 | 631 | PATTERNS_ARRAY 632 | 633 | 634 | REGULAR_EXPRESSION 635 | 636 | STRING 637 | CVS 638 | TYPE 639 | 1 640 | 641 | 642 | REGULAR_EXPRESSION 643 | 644 | STRING 645 | .cvsignore 646 | TYPE 647 | 0 648 | 649 | 650 | REGULAR_EXPRESSION 651 | 652 | STRING 653 | .cvspass 654 | TYPE 655 | 0 656 | 657 | 658 | REGULAR_EXPRESSION 659 | 660 | STRING 661 | .svn 662 | TYPE 663 | 1 664 | 665 | 666 | REGULAR_EXPRESSION 667 | 668 | STRING 669 | .git 670 | TYPE 671 | 1 672 | 673 | 674 | REGULAR_EXPRESSION 675 | 676 | STRING 677 | .gitignore 678 | TYPE 679 | 0 680 | 681 | 682 | PROTECTED 683 | 684 | PROXY_NAME 685 | Remove SCM metadata 686 | PROXY_TOOLTIP 687 | Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems. 688 | STATE 689 | 690 | 691 | 692 | PATTERNS_ARRAY 693 | 694 | 695 | REGULAR_EXPRESSION 696 | 697 | STRING 698 | classes.nib 699 | TYPE 700 | 0 701 | 702 | 703 | REGULAR_EXPRESSION 704 | 705 | STRING 706 | designable.db 707 | TYPE 708 | 0 709 | 710 | 711 | REGULAR_EXPRESSION 712 | 713 | STRING 714 | info.nib 715 | TYPE 716 | 0 717 | 718 | 719 | PROTECTED 720 | 721 | PROXY_NAME 722 | Optimize nib files 723 | PROXY_TOOLTIP 724 | Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles. 725 | STATE 726 | 727 | 728 | 729 | PATTERNS_ARRAY 730 | 731 | 732 | REGULAR_EXPRESSION 733 | 734 | STRING 735 | Resources Disabled 736 | TYPE 737 | 1 738 | 739 | 740 | PROTECTED 741 | 742 | PROXY_NAME 743 | Remove Resources Disabled folders 744 | PROXY_TOOLTIP 745 | Remove "Resources Disabled" folders. 746 | STATE 747 | 748 | 749 | 750 | SEPARATOR 751 | 752 | 753 | 754 | PATTERNS_ARRAY 755 | 756 | 757 | REGULAR_EXPRESSION 758 | 759 | STRING 760 | 761 | TYPE 762 | 1 763 | 764 | 765 | PROTECTED 766 | 767 | STATE 768 | 769 | 770 | 771 | NAME 772 | test_example 773 | 774 | 775 | TYPE 776 | 0 777 | VERSION 778 | 2 779 | 780 | -------------------------------------------------------------------------------- /test_example/test_example/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | import platform 3 | 4 | # PEP 440 compliant 5 | __version__ = '0.0.dev0' 6 | 7 | # required for OSNAP 8 | __author__ = 'test_author' 9 | __application_name__ = 'test_example' 10 | 11 | if platform.system().lower()[0] == 'w': 12 | # on windows, we can't install pip yet on 3.6 in the embedded Python 13 | __python_version__ = '3.6.3' 14 | else: 15 | __python_version__ = '3.6.3' 16 | -------------------------------------------------------------------------------- /test_example/test_example/mymodule.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | 4 | from PyQt5.QtWidgets import QApplication, QLabel 5 | 6 | import cryptography.fernet 7 | 8 | 9 | def run(): 10 | 11 | app = QApplication(sys.argv) 12 | 13 | m = b'does this work?' 14 | s = 'original message:\n' + str(m) + '\n\n' 15 | 16 | k = cryptography.fernet.Fernet.generate_key() 17 | fernet = cryptography.fernet.Fernet(k) 18 | t = fernet.encrypt(m) 19 | d = fernet.decrypt(t) 20 | s += 'key:\n' + str(k) + '\n\n' 21 | s += 'token:\n' + str(t) + '\n\n' 22 | s += 'decoded message:\n' + str(d) 23 | 24 | window = QLabel(s) 25 | window.show() 26 | 27 | app.exec_() -------------------------------------------------------------------------------- /test_example/test_osnap.py: -------------------------------------------------------------------------------- 1 | 2 | # runs test_example as an actual test (e.g. via py.test) 3 | 4 | import test_example.make_osnapy 5 | import test_example.make_installer 6 | 7 | 8 | def test_osnap(verbose=False): 9 | test_example.make_osnapy.create_python_environment(verbose) 10 | test_example.make_installer.create_installer(verbose) 11 | 12 | 13 | if __name__ == '__main__': 14 | test_osnap(True) 15 | 16 | -------------------------------------------------------------------------------- /test_example/test_run.bat: -------------------------------------------------------------------------------- 1 | del /S /Q /F dist 2 | del /S /Q /F installers 3 | del /S /Q /F venv 4 | del /S /Q /F osnapy 5 | call make_venv.bat 6 | call install_osnap.bat 7 | call make_osnapy.bat 8 | call make_installer.bat 9 | -------------------------------------------------------------------------------- /test_example/test_run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -x 3 | rm -rf .eggs build dist logs venv || { echo 'could not rm -rf : re-run with sudo' ; exit 1; } 4 | set +x 5 | ./make_venv.sh 6 | ./install_osnap.sh 7 | ./make_osnapy.sh 8 | ./make_installer.sh 9 | -------------------------------------------------------------------------------- /testpypi.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | venv/bin/python setup.py sdist upload -r pypitest 3 | --------------------------------------------------------------------------------