Merge branch 'main' into dev
This commit is contained in:
+42
-25
@@ -1,43 +1,60 @@
|
||||
# Contributing
|
||||
# Contributing Guide
|
||||
|
||||
When contributing to this repository, please first discuss the change you wish to make via the
|
||||
issue tracker with the owner of this repository before making a change. If you just want to make a
|
||||
minor correction, like fix a typo or similar, feel free to just make a pull request directly.
|
||||
|
||||
There is a code of conduct. Please follow it in all your interactions with the project.
|
||||
issue tracker or the discussions page with the owner of this repository before making a change. If
|
||||
you just want to make a minor correction, like fix a typo or similar, feel free to just make a pull
|
||||
request directly.
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
1. Make sure your code passes all tests and conforms to the style guide. You can check that the code
|
||||
conforms by running `flake8` from the root of the project folder.
|
||||
1. Make sure your code passes all tests and conforms to the style guide. You can check that the
|
||||
code conforms by running `flake8` from the root of the project folder.
|
||||
2. Please provide a complete description of the changes in the pull request, and a summary that can
|
||||
be copied into the [CHANGELOG](CHANGELOG.md). Remember to reference any issue related by
|
||||
providing the issue number.
|
||||
3. Do not change the version number unless asked to do so. Version numbers are bumped in separate
|
||||
release pull requests by the maintainer.
|
||||
3. Do not change the version number. Version numbers are bumped in separate release pull requests
|
||||
by the maintainer.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
There is a code of conduct. Please follow it in all your interactions with the project.
|
||||
|
||||
### Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers
|
||||
pledge to making participation in our project and our community a harassment-free experience for
|
||||
everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level
|
||||
of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
everyone, regardless of age, body size, disability, ethnicity, gender identity and expression,
|
||||
level of experience, nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
Please see the [CODE_OF_CONDUCT](CODE_OF_CONDUCT.md) file for the full text.
|
||||
|
||||
## Code Style Guide
|
||||
|
||||
The source code of novelWriter broadly follows the [PEP8](https://www.python.org/dev/peps/pep-0008/)
|
||||
style guide , but with a few modifications and exceptions.
|
||||
The source code of novelWriter broadly follows the [PEP8](https://www.python.org/dev/peps/pep-0008)
|
||||
style guide, but with a few modifications and exceptions listed below.
|
||||
|
||||
### Linting with flake8
|
||||
### Line Length
|
||||
|
||||
An excellent tool for checking Python code for errors and coding style is `flake8`.
|
||||
The documentation is available [here](https://flake8.pycqa.org/en/latest/).
|
||||
For this project, source lines should stay within the 79 and 99 character limits described by PEP8.
|
||||
79 characters is often too restrictive, so 99 character lines are acceptable when that is more
|
||||
practical. Readability has priority. Generally, if a code statement requires multiple lines, the
|
||||
lines should wrap at 79 characters, not 99. If wrapping can be avoided by going to 99, then that is
|
||||
generally preferrable.
|
||||
|
||||
The `setup.cfg` file in the root of this project has the following settings:
|
||||
For text files, the text should also be wrapped at 99 character. The exception is markdown image
|
||||
tags and urls.
|
||||
|
||||
Please do not submit pull requests that re-wrap existing source or text unless this has been
|
||||
discussed beforehand.
|
||||
|
||||
### Linting with `flake8`
|
||||
|
||||
An excellent tool for checking Python code for errors and coding style is `flake8`. The
|
||||
documentation is available [here](https://flake8.pycqa.org/en/latest/).
|
||||
|
||||
The `setup.cfg` file in the root of this project has the following settings for `flake8` that
|
||||
matches the coding standard:
|
||||
```conf
|
||||
[flake8]
|
||||
ignore = E203,E221,E226,E228,E241,E251,E261,E266,E302,E305
|
||||
@@ -57,20 +74,20 @@ not conform to the standard.
|
||||
|
||||
## Ignored Errors
|
||||
|
||||
Some errors are ignored in novelWriter, for various reasons. In addition, novelWriter uses camelCase
|
||||
function and variable names due to this being the standard for the Qt libraries, and also because of
|
||||
the author's personal preferences.
|
||||
Some `flake8` error codes are ignored for this project for various reasons. The source also uses
|
||||
camelCase function and variable names. This is the standard for the Qt libraries novelWriter
|
||||
integrates with. It also happens to be the author's personal preferences.
|
||||
|
||||
The reason behind the other ignored error codes are listed below. Many of them are due to PEP8 not
|
||||
permitting column alignment as opposed to many other coding styles. I find them useful in regions of
|
||||
bulk value assignments. There's a reason why tables are more readable than lists. They should be
|
||||
used sparingly though.
|
||||
permitting column alignment as opposed to many other coding styles, like for instance for Go. I
|
||||
find them useful in regions of bulk value assignments. There's a reason why tables are more
|
||||
readable than lists. They should be used sparingly though.
|
||||
|
||||
The ignored errors are all `pycodestyle` errors, and they are documented
|
||||
[here](https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes).
|
||||
|
||||
**E203:** whitespace before ‘:’
|
||||
**Reason:** Column alignment. It is natural to align dictionary columns along the `:` character.
|
||||
**Reason:** Column alignment.
|
||||
|
||||
**E221:** multiple spaces before operator
|
||||
**Reason:** Column alignment.
|
||||
@@ -78,7 +95,7 @@ The ignored errors are all `pycodestyle` errors, and they are documented
|
||||
**E226:** missing whitespace around arithmetic operator
|
||||
**Reason:** This doesn't actually follow the PEP8 recommendation of grouping longer equations by
|
||||
operator precedence like `2*a + 3*b` instead of `a * a + 3 * b`. Generally, don't use spaces around
|
||||
`*`, `/` and `**`, but do use spaces around `+` and `-`. For appending strings, the spaces can be
|
||||
`*`, `/` and `**`, but _do_ use spaces around `+` and `-`. For appending strings, the spaces can be
|
||||
dropped. Don't use the `+` operator for appending multiple strings. Use formatting instead.
|
||||
|
||||
**E228** missing whitespace around modulo operator
|
||||
|
||||
@@ -15,94 +15,44 @@
|
||||
|
||||
<img align="left" style="margin: 0 16px 4px 0;" src="https://raw.githubusercontent.com/vkbo/novelWriter/main/setup/icons/scaled/icon-novelwriter-96.png">
|
||||
|
||||
novelWriter is a Markdown-like text editor designed for writing novels and larger projects of many
|
||||
smaller plain text documents. It uses its own flavour of Markdown that supports a meta data syntax
|
||||
for comments, synopsis, and cross-referencing between files. It's designed to be a simple text
|
||||
editor that allows for easy organisation of text files and notes, built on plain text files for
|
||||
robustness.
|
||||
novelWriter is a Markdown-like text editor designed for writing novels assembled from many smaller
|
||||
text documents. It uses a minimal formatting syntax inspired by Markdown, and adds a meta data
|
||||
syntax for comments, synopsis, and cross-referencing between files. It's designed to be a simple
|
||||
text editor that allows for easy organisation of text files and notes, built on plain text files
|
||||
for robustness.
|
||||
|
||||
The plain text storage is suitable for version control software, and also well suited for file
|
||||
synchronisation tools. The core project structure is stored in a project XML file. Other meta data
|
||||
is primarily saved in JSON files.
|
||||
The plain text files are suitable for version control software, and also well suited for file
|
||||
synchronisation tools. The core project structure is stored in a single project XML file. Other
|
||||
meta data is primarily saved in JSON files.
|
||||
|
||||
The full documentation is available at
|
||||
[novelwriter.readthedocs.io](https://novelwriter.readthedocs.io/).
|
||||
|
||||
The contributing guide is available at
|
||||
[CONTRIBUTING](https://github.com/vkbo/novelWriter/blob/main/CONTRIBUTING.md).
|
||||
|
||||
[novelwriter.readthedocs.io](https://novelwriter.readthedocs.io).
|
||||
|
||||
## Implementation
|
||||
|
||||
The application is written in Python 3 using Qt5 via PyQt5. It is developed on Linux, but it should
|
||||
in principle work fine on other operating systems as well as long as dependencies are met. The unit
|
||||
tests are run on the latest versions of Ubuntu Linux, Windows Server and macOS.
|
||||
in principle work fine on other operating systems as well, as long as dependencies are met. The
|
||||
unit tests are run on the latest versions of Ubuntu Linux, Windows Server and macOS.
|
||||
|
||||
## Project Contributions
|
||||
|
||||
# Installing and Running
|
||||
|
||||
For install instructions, please check the [documentation](https://novelwriter.readthedocs.io/) in
|
||||
the [Getting Started](https://novelwriter.readthedocs.io/en/latest/int_started.html) section.
|
||||
|
||||
## TLDR Instructions
|
||||
|
||||
**Note:** You may need to replace `python` with `python3` and `pip` with `pip3` in the instructions
|
||||
below on some systems. You may also want to add the `--user` flag for `pip` to install in your user
|
||||
space only.
|
||||
|
||||
### Install from PyPi
|
||||
|
||||
novelWriter is available on [pypi.org](https://pypi.org/project/novelWriter/), and can be installed
|
||||
with:
|
||||
```bash
|
||||
pip install novelwriter
|
||||
```
|
||||
Dependencies should be installed automatically, but can also be installed directly with:
|
||||
```bash
|
||||
pip install pyqt5 lxml pyenchant
|
||||
```
|
||||
|
||||
### Setup on Linux
|
||||
|
||||
If you're running from source, the following commands will set up novelWriter on Linux:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
python setup.py install
|
||||
python setup.py xdg-install
|
||||
```
|
||||
|
||||
### Setup on macOS
|
||||
|
||||
If you're running from source, the following commands will set up novelWriter on macOS:
|
||||
```bash
|
||||
brew install enchant
|
||||
pip3 install --user -r requirements.txt
|
||||
pip3 install --user pyobjc
|
||||
```
|
||||
|
||||
### Setup on Windows
|
||||
|
||||
For Windows, you can either install via PyPi, or use the Windows installer available from the
|
||||
[releases](https://github.com/vkbo/novelWriter/releases) page.
|
||||
|
||||
## Debugging
|
||||
|
||||
If you need to debug novelWriter, you must run it from command line. It takes a few parameters,
|
||||
which can be listed with the switch `--help`. The `--info`, `--debug` or `--verbose` flags are
|
||||
particularly useful for increasing logging output for debugging.
|
||||
Contributions to this project are welcome. However, please read the
|
||||
[Contributing Guide](https://github.com/vkbo/novelWriter/blob/main/CONTRIBUTING.md) before
|
||||
submitting larger additions ot changes to this project.
|
||||
|
||||
|
||||
# Key Features
|
||||
|
||||
Some features of novelWriter are listed below. Consult the
|
||||
[documentation](https://novelwriter.readthedocs.io/) for more information.
|
||||
[documentation](https://novelwriter.readthedocs.io) for more information.
|
||||
|
||||
### Markdown Flavour
|
||||
|
||||
novelWriter is _not_ a full-feature Markdown editor. It allows for a minimal set of formatting
|
||||
needed for writing text documents for novels. These are currently limited to:
|
||||
novelWriter is _not_ a full-feature Markdown editor. It is a plain text editor that uses
|
||||
Markdown-like syntax for adding a minimal set of formatting that is useful for the specific task
|
||||
of writing novels. The formatting is currently limited to:
|
||||
|
||||
* Headings level 1 to 4 using the `#` syntax only.
|
||||
* Headings levels 1 to 4 using the `#` syntax only.
|
||||
* Emphasised and strongly emphasised text. These are rendered as italicised and bold text.
|
||||
* Strikethrough text.
|
||||
* Hard line breaks using two or more spaces at the end of a line.
|
||||
@@ -116,20 +66,19 @@ In addition, novelWriter adds the following, which is otherwise not supported by
|
||||
the comment is indexed and treated as the synopsis for the section of text under the same header.
|
||||
These synopsis comments can be used to build an outline and exported to external documents.
|
||||
* A set of meta data keyword/values starting with the character `@`. This is used for tagging
|
||||
and inter-linking documents, and can be used to generate a project outline.
|
||||
* Non-breaking spaces are supported as long as your system is using at least Qt 5.9. For earlier
|
||||
version, non-breaking spaces are converted to normal spaces when saving the document. This is
|
||||
done by the Qt library.
|
||||
* Thin spaces are also supported, as well as non-breaking thin spaces, with the same library
|
||||
version restriction as above.
|
||||
* Tabs can be used in the text, and should be properly aligned. The width of a tab in pixels can be
|
||||
changed in Preferences. Note that for the HTML format, most browsers will treat a tab as a space,
|
||||
so it may not show up like expected. If you import the HTML file to Libre Office, for instance,
|
||||
they should appear as expected.
|
||||
and inter-linking documents, and can also be included when generate a project outline.
|
||||
* A variety of thin and non-breaking spaces are supported. Some of them depend on the system
|
||||
running at least Qt 5.9. Earlier versions of Qt will unfortunately strip them out when saving.
|
||||
* Tabs can be used in the text, and should be properly aligned in both editor and viewer. This can
|
||||
be used to make simple tables and lists. Full Markdown tables and lists are not supported. Note
|
||||
that for HTML exports, most browsers will treat a tab as a space, so it may not show up like
|
||||
expected. If you import the HTML file to Libre Office, for instance, they should appear as
|
||||
expected.
|
||||
|
||||
The core export format of novelWriter is HTML5. You can also export the entire project as a single
|
||||
novelWriter Markdown-flavour document. In addition, other exports to Open Document, PDF, and plain
|
||||
text is offered through the Qt library, although with limitations to formatting.
|
||||
novelWriter Markdown-flavour document. These can later be imported again into novelWriter. In
|
||||
addition, export to Open Document, PDF, and plain text is offered through the Qt library, although
|
||||
with limitations to formatting.
|
||||
|
||||
The HTML format is well suited for file conversion tools and import into other text editors.
|
||||
|
||||
@@ -137,20 +86,16 @@ The HTML format is well suited for file conversion tools and import into other t
|
||||
### Colour Themes
|
||||
|
||||
The editor has syntax highlighting for the features it supports, and includes a set of different
|
||||
syntax highlighting themes. The GUI also has an optional dark theme in addition to the default
|
||||
system theme.
|
||||
|
||||
New themes can easily be added to the `nw/assets/themes` folder. Have a look in the existing
|
||||
folders for examples of how to define the colours.
|
||||
syntax highlighting themes. Optional GUI themes are also available, including dark themes.
|
||||
|
||||
|
||||
### Easy Organising of Project Files
|
||||
|
||||
The structure of the project is shown on the left hand side of the main GUI. Project files are
|
||||
The structure of the project is shown on the left hand side of the main window. Project files are
|
||||
organised into root folders, indicating what class of file they are. The most important root folder
|
||||
is the Novel folder, which contains all of the files that makes up the finished novel. Each root
|
||||
folder can have subfolders. Folders have no impact on the final project structure, they are purely
|
||||
tools for organising the files in whatever way the user needs.
|
||||
is the `Novel` folder, which contains all of the files that make up the finished novel. Each root
|
||||
folder can have subfolders. Subfolders have no impact on the final project structure, they are
|
||||
there for you to organise your files in whatever way you want.
|
||||
|
||||
The editor supports four levels of headings, which determines what level the following text belongs
|
||||
to. Headings of level one signify a book or partition title. Headings of level two signify the
|
||||
@@ -159,10 +104,11 @@ four can be used internally in each scene to separate sections.
|
||||
|
||||
Each novel file can be assigned a layout format, which shows up as a flag next to the item in the
|
||||
project tree. These are mostly to help the user track what they contain, but they also have some
|
||||
impact on the format of the exported document. See the documentation for further details.
|
||||
impact on the format of the exported document. See the
|
||||
[documentation](https://novelwriter.readthedocs.io) for further details.
|
||||
|
||||
|
||||
#### Project Notes
|
||||
### Project Notes
|
||||
|
||||
Supporting note files can be added for the story plot, characters, locations, story timeline, etc.
|
||||
These have their separate root folders. These are optional files.
|
||||
@@ -177,7 +123,87 @@ are clickable in the document view pane, and control-clickable in the editor. Th
|
||||
to quickly navigate between the documents while editing.
|
||||
|
||||
|
||||
## Licenses
|
||||
# Installing and Running
|
||||
|
||||
For install instructions, please check the [documentation](https://novelwriter.readthedocs.io/) in
|
||||
the [Getting Started](https://novelwriter.readthedocs.io/en/latest/int_started.html) section.
|
||||
|
||||
|
||||
## TLDR Instructions
|
||||
|
||||
If you want to run novelWriter directly from the source code, you must run the `novelWriter.py`
|
||||
file from command line. For installations on Linux, macOS or Windows, see below.
|
||||
|
||||
**Note:** You may need to replace `python` with `python3` and `pip` with `pip3` in the instructions
|
||||
below on some systems. You may also want to add the `--user` flag for `pip` to install in your user
|
||||
space only.
|
||||
|
||||
|
||||
### Install from PyPi
|
||||
|
||||
novelWriter is available on [pypi.org](https://pypi.org/project/novelWriter/), and can be installed
|
||||
with:
|
||||
```bash
|
||||
pip install novelwriter
|
||||
```
|
||||
Dependencies should be installed automatically, but can also be installed directly with:
|
||||
```bash
|
||||
pip install pyqt5 lxml pyenchant
|
||||
```
|
||||
When installing via pip, novelWriter can be launched from command line with:
|
||||
```bash
|
||||
novelWriter
|
||||
```
|
||||
|
||||
Make sure the install location for pip is in your PATH variable. This is not always the case by
|
||||
default.
|
||||
|
||||
|
||||
### Setup on Linux
|
||||
|
||||
If you're installing from source, the following commands will set up novelWriter on Linux:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
python setup.py install
|
||||
python setup.py xdg-install
|
||||
```
|
||||
|
||||
This should make novelWriter available as a regular application on your system, with a launceher
|
||||
icon, and file association with novelWriter project files.
|
||||
|
||||
|
||||
### Setup on macOS
|
||||
|
||||
If you're installing from source, the following commands will set up novelWriter on macOS:
|
||||
```bash
|
||||
brew install enchant
|
||||
pip3 install --user -r requirements.txt
|
||||
pip3 install --user pyobjc
|
||||
python3 setup.py install
|
||||
```
|
||||
|
||||
At present, novelWriter isn't further integrated into the OS, so you must launch it from command
|
||||
line with:
|
||||
```bash
|
||||
novelWriter
|
||||
```
|
||||
|
||||
|
||||
### Setup on Windows
|
||||
|
||||
For Windows, you can either install via PyPi, or use the Windows installer available from the
|
||||
[releases](https://github.com/vkbo/novelWriter/releases) page. This should add the necessary icons
|
||||
to your desktop and start menu.
|
||||
|
||||
|
||||
## Debugging
|
||||
|
||||
If you need to debug novelWriter, you must run it from command line. It takes a few parameters,
|
||||
which can be listed with the switch `--help`. The `--info`, `--debug` or `--verbose` flags are
|
||||
particularly useful for increasing logging output for debugging.
|
||||
|
||||
|
||||
# Licenses
|
||||
|
||||
This is Open Source software, and novelWriter is licensed under GPLv3. See the
|
||||
[GNU General Public License website](https://www.gnu.org/licenses/gpl-3.0.en.html) for more
|
||||
@@ -204,7 +230,7 @@ Bundled assets have the following licenses:
|
||||
[sdras/night-owl-vscode-theme](https://github.com/sdras/night-owl-vscode-theme).
|
||||
|
||||
|
||||
## Screenshot
|
||||
# Screenshot
|
||||
|
||||
**novelWriter with default system theme:**
|
||||

|
||||
|
||||
@@ -32,7 +32,8 @@ in principle work fine on other operating systems as long as dependencies are me
|
||||
|
||||
You can download the latest version of novelWriter from the source repository on GitHub_.
|
||||
novelWriter is also hosted on PyPi_, and can be installed on all operating systems that support Qt5
|
||||
and Python 3. It is regularly tested on Linux, Windows and macOS.
|
||||
and Python 3. It is regularly tested on Linux, Windows and macOS. The latest version of novelWriter
|
||||
is |release|.
|
||||
|
||||
To install from PyPi you must first have the ``python`` and ``pip`` commands available on your
|
||||
system. If you don't, see specific instructions for your operating system later in this document.
|
||||
@@ -48,7 +49,14 @@ To upgrade an existing installation, use:
|
||||
|
||||
pip install --upgrade novelwriter
|
||||
|
||||
The latest version of novelWriter is |release|.
|
||||
When installing via pip, novelWriter can be launched from command line with:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
novelWriter
|
||||
|
||||
Make sure the install location for pip is in your PATH variable. This is not always the case by
|
||||
default.
|
||||
|
||||
.. _GitHub: https://github.com/vkbo/novelWriter/releases
|
||||
.. _PyPi: https://pypi.org/project/novelWriter/
|
||||
@@ -138,6 +146,9 @@ By default, these commands install novelWriter and its icons for the current use
|
||||
for all users, run the script with the ``sudo`` command. Other options are also available. Run
|
||||
``python setup.py help`` for a full list of install options.
|
||||
|
||||
This should install novelWriter to either ``~/.local/bin/novelWriter`` if installed for local user
|
||||
only, or to ``/usr/local/bin/novelWriter`` if installed for all users.
|
||||
|
||||
|
||||
.. _a_started_macos:
|
||||
|
||||
@@ -160,6 +171,19 @@ dictionaries.
|
||||
|
||||
brew install enchant
|
||||
|
||||
With the dependencies in place, you can either launch the ``novelWriter.py`` script directly, or
|
||||
run the install command:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
python setup.py install
|
||||
|
||||
After this, you should be able to launch novelWriter by running ``novelWriter`` in a command line
|
||||
window.
|
||||
|
||||
Right now there isn't a better integration with macOS available. Contributions from someone more
|
||||
familiar with macOS would be very much appreciated.
|
||||
|
||||
.. _brew docs: https://docs.brew.sh/Homebrew-and-Python
|
||||
|
||||
|
||||
@@ -215,8 +239,8 @@ run:
|
||||
your python executable followed by ``novelWriter.py``. It should look something like this:
|
||||
``C:\...\AppData\Local\Programs\Python\Python38\python.exe novelWriter.py``
|
||||
|
||||
You can also run the ``make.py`` script to generate a single executable, or an installer.
|
||||
See `Build and Install novelWriter`_ for more details or run: ``python make.py help``.
|
||||
You can also run the ``setup.py`` script to generate a single executable, or an installer.
|
||||
See `Build and Install novelWriter`_ for more details or run: ``python setup.py help``.
|
||||
|
||||
.. _python.org: https://www.python.org/downloads/windows/
|
||||
.. _Build and Install novelWriter: https://github.com/vkbo/novelWriter/blob/main/setup/README.md
|
||||
|
||||
@@ -1,333 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This make script is intended for building distributable packages of
|
||||
novelWriter. These are either:
|
||||
|
||||
* A single file executable named dist/novelWriter(.exe). This is a
|
||||
quite slow option, and the file is fairly big.
|
||||
* A single directory named dist/novelWriter with a novelWriter(.exe),
|
||||
and all dependecies included.
|
||||
* The latter can be combined with a build stage of a setup.exe file if
|
||||
on Windows. This requires Inno Setup to be installed and in path.
|
||||
|
||||
In addition, providing the pip otion will cause the script to try to
|
||||
install all dependencies needed for runing the build, and for running
|
||||
novelWriter itself.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
OS_NONE = 0
|
||||
OS_LINUX = 1
|
||||
OS_WIN = 2
|
||||
OS_DARWIN = 3
|
||||
|
||||
# =============================================================================================== #
|
||||
# Package Installer
|
||||
# =============================================================================================== #
|
||||
|
||||
def installPackages(hostOS):
|
||||
"""Install package dependencies both for this script and for running
|
||||
novelWriter itself.
|
||||
"""
|
||||
print("")
|
||||
print("Installing Dependencies")
|
||||
print("#######################")
|
||||
print("")
|
||||
|
||||
installQueue = ["pip", "pyinstaller", "-r requirements.txt"]
|
||||
if hostOS == OS_DARWIN:
|
||||
installQueue.append("pyobjc")
|
||||
|
||||
pyCmd = [sys.executable, "-m"]
|
||||
pipCmd = ["pip", "install", "--user", "--upgrade"]
|
||||
for stepCmd in installQueue:
|
||||
pkgCmd = stepCmd.split(" ")
|
||||
try:
|
||||
subprocess.call(pyCmd + pipCmd + pkgCmd)
|
||||
except Exception as e:
|
||||
print("Failed with error:")
|
||||
print(str(e))
|
||||
sys.exit(1)
|
||||
|
||||
return
|
||||
|
||||
# =============================================================================================== #
|
||||
# Run PyInstaller on Package
|
||||
# =============================================================================================== #
|
||||
|
||||
def freezePackage(buildWindowed, oneFile, makeSetup, hostOS):
|
||||
"""Run PyInstaller to freeze the packages. This assumes all
|
||||
dependencies are already in place.
|
||||
"""
|
||||
import PyInstaller.__main__ # noqa: E402
|
||||
|
||||
print("")
|
||||
print("Running PyInstaller")
|
||||
print("###################")
|
||||
print("")
|
||||
|
||||
if hostOS == OS_WIN:
|
||||
dotDot = ";"
|
||||
else:
|
||||
dotDot = ":"
|
||||
|
||||
sys.modules["FixTk"] = None
|
||||
instOpt = [
|
||||
"--name=novelWriter",
|
||||
"--clean",
|
||||
"--add-data=%s%s%s" % (os.path.join("nw", "assets"), dotDot, "assets"),
|
||||
"--icon=%s" % os.path.join("nw", "assets", "icons", "novelwriter.ico"),
|
||||
"--exclude-module=PyQt5.QtQml",
|
||||
"--exclude-module=PyQt5.QtBluetooth",
|
||||
"--exclude-module=PyQt5.QtDBus",
|
||||
"--exclude-module=PyQt5.QtMultimedia",
|
||||
"--exclude-module=PyQt5.QtMultimediaWidgets",
|
||||
"--exclude-module=PyQt5.QtNetwork",
|
||||
"--exclude-module=PyQt5.QtNetworkAuth",
|
||||
"--exclude-module=PyQt5.QtNfc",
|
||||
"--exclude-module=PyQt5.QtQuick",
|
||||
"--exclude-module=PyQt5.QtQuickWidgets",
|
||||
"--exclude-module=PyQt5.QtRemoteObjects",
|
||||
"--exclude-module=PyQt5.QtSensors",
|
||||
"--exclude-module=PyQt5.QtSerialPort",
|
||||
"--exclude-module=PyQt5.QtSql",
|
||||
"--exclude-module=FixTk",
|
||||
"--exclude-module=tcl",
|
||||
"--exclude-module=tk",
|
||||
"--exclude-module=_tkinter",
|
||||
"--exclude-module=tkinter",
|
||||
"--exclude-module=Tkinter",
|
||||
]
|
||||
|
||||
if buildWindowed:
|
||||
instOpt.append("--windowed")
|
||||
|
||||
if oneFile and not makeSetup:
|
||||
instOpt.append("--onefile")
|
||||
else:
|
||||
instOpt.append("--onedir")
|
||||
|
||||
instOpt.append("novelWriter.py")
|
||||
|
||||
# Make sample.zip first
|
||||
try:
|
||||
subprocess.call([sys.executable, "setup.py", "sample"])
|
||||
except Exception as e:
|
||||
print("Failed with error:")
|
||||
print(str(e))
|
||||
sys.exit(1)
|
||||
|
||||
PyInstaller.__main__.run(instOpt)
|
||||
|
||||
if not oneFile:
|
||||
# These files are not needed, and take up a fair bit of space.
|
||||
delFiles = []
|
||||
if hostOS == OS_WIN:
|
||||
delFiles = [
|
||||
"Qt5DBus.dll",
|
||||
"Qt5Network.dll",
|
||||
"Qt5Qml.dll",
|
||||
"Qt5QmlModels.dll",
|
||||
"Qt5Quick.dll",
|
||||
"Qt5Quick3D.dll",
|
||||
"Qt5Quick3DAssetImport.dll",
|
||||
"Qt5Quick3DRender.dll",
|
||||
"Qt5Quick3DRuntimeRender.dll",
|
||||
"Qt5Quick3DUtils.dll",
|
||||
"Qt5Sql.dll"
|
||||
]
|
||||
elif hostOS == OS_LINUX:
|
||||
delFiles = [
|
||||
"libQt5DBus.so.5",
|
||||
"libQt5Network.so.5",
|
||||
"libQt5Qml.so.5",
|
||||
"libQt5QmlModels.so.5",
|
||||
"libQt5Quick.so.5",
|
||||
"libQt5Quick3D.so.5",
|
||||
"libQt5Quick3DAssetImport.so.5",
|
||||
"libQt5Quick3DRender.so.5",
|
||||
"libQt5Quick3DRuntimeRender.so.5",
|
||||
"libQt5Quick3DUtils.so.5",
|
||||
"libQt5Sql.so.5"
|
||||
]
|
||||
distDir = os.path.join(os.getcwd(), "dist", "novelWriter")
|
||||
for delFile in delFiles:
|
||||
delPath = os.path.join(distDir, delFile)
|
||||
if os.path.isfile(delPath):
|
||||
print("Deleting file: %s" % delPath)
|
||||
os.unlink(delPath)
|
||||
|
||||
print("")
|
||||
print("Build Finished")
|
||||
print("")
|
||||
print("The novelWriter executable should be in the folder named 'dist'")
|
||||
print("")
|
||||
|
||||
return
|
||||
|
||||
# =============================================================================================== #
|
||||
# Inno Setup Builder
|
||||
# =============================================================================================== #
|
||||
|
||||
def innoSetup():
|
||||
"""Run the Inno Setup tool to build a setup.exe file for Windows.
|
||||
"""
|
||||
print("")
|
||||
print("Running Inno Setup")
|
||||
print("##################")
|
||||
print("")
|
||||
|
||||
# Read the iss template
|
||||
issData = ""
|
||||
with open(os.path.join("setup", "win_setup.iss"), mode="r") as inFile:
|
||||
issData = inFile.read()
|
||||
|
||||
import nw # noqa: E402
|
||||
issData = issData.replace(r"%%version%%", nw.__version__)
|
||||
issData = issData.replace(r"%%dir%%", os.getcwd())
|
||||
|
||||
with open("setup.iss", mode="w+") as outFile:
|
||||
outFile.write(issData)
|
||||
|
||||
try:
|
||||
subprocess.call(["iscc", "setup.iss"])
|
||||
except Exception as e:
|
||||
print("Inno Setup failed with error:")
|
||||
print(str(e))
|
||||
sys.exit(1)
|
||||
|
||||
return
|
||||
|
||||
# =============================================================================================== #
|
||||
# Clean Build and Dist Folders
|
||||
# =============================================================================================== #
|
||||
|
||||
def cleanInstall():
|
||||
"""Recursively delete the 'build' and 'dist' folders.
|
||||
"""
|
||||
print("")
|
||||
print("Cleaning up build environment ...")
|
||||
|
||||
buildDir = os.path.join(os.getcwd(), "build")
|
||||
if os.path.isdir(buildDir):
|
||||
try:
|
||||
shutil.rmtree(buildDir)
|
||||
print("Deleted folder 'build'")
|
||||
except Exception as e:
|
||||
print("Error: Cannot delete 'build' folder.")
|
||||
print(str(e))
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("Folder 'build' not found")
|
||||
|
||||
distDir = os.path.join(os.getcwd(), "dist")
|
||||
if os.path.isdir(distDir):
|
||||
try:
|
||||
shutil.rmtree(distDir)
|
||||
print("Deleted folder 'dist'")
|
||||
except Exception as e:
|
||||
print("Error: Cannot delete 'dist' folder.")
|
||||
print(str(e))
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("Folder 'dist' not found")
|
||||
|
||||
print("")
|
||||
|
||||
return
|
||||
|
||||
# =============================================================================================== #
|
||||
# Process Build Steps
|
||||
# =============================================================================================== #
|
||||
|
||||
if __name__ == "__main__":
|
||||
"""Parse command line options and run the commands.
|
||||
"""
|
||||
# Detect OS
|
||||
if sys.platform.startswith("linux"):
|
||||
hostOS = OS_LINUX
|
||||
elif sys.platform.startswith("darwin"):
|
||||
hostOS = OS_DARWIN
|
||||
elif sys.platform.startswith("win32"):
|
||||
hostOS = OS_WIN
|
||||
elif sys.platform.startswith("cygwin"):
|
||||
hostOS = OS_WIN
|
||||
else:
|
||||
hostOS = OS_NONE
|
||||
|
||||
# Flags and Variables
|
||||
buildWindowed = True
|
||||
oneFile = False
|
||||
makeSetup = False
|
||||
doFreeze = False
|
||||
|
||||
helpMsg = (
|
||||
"\n"
|
||||
"novelWriter Make Tool\n"
|
||||
"=====================\n"
|
||||
"This tool provides build commands for distibuting novelWriter as a\n"
|
||||
"package. The available options are as follows:\n"
|
||||
"\n"
|
||||
"help Print the help message.\n"
|
||||
"freeze Freeze the package and produces a folder of all\n"
|
||||
" dependencies using pyinstaller.\n"
|
||||
"onefile Build a standalone executable with all dependencies\n"
|
||||
" bundled. Implies 'freeze', cannot be used with 'setup'.\n"
|
||||
"pip Run pip to install all package dependencies for\n"
|
||||
" novelWriter and this build tool.\n"
|
||||
"setup Build a setup.exe installer for Windows. This option\n"
|
||||
" automaticall disables the 'onefile' option.\n"
|
||||
"clean This will attempt to delete the 'build' and 'dist'\n"
|
||||
" folders in the current folder.\n"
|
||||
)
|
||||
|
||||
if "help" in sys.argv or len(sys.argv) <= 1:
|
||||
print(helpMsg)
|
||||
sys.exit(0)
|
||||
|
||||
if not os.path.isfile(os.path.join(os.getcwd(), "novelWriter.py")):
|
||||
print("Error: This script must be run in the root folder of novelWriter.")
|
||||
sys.exit(1)
|
||||
|
||||
if not os.path.isdir(os.path.join(os.getcwd(), "nw")):
|
||||
print("Error: This script must be run in the root folder of novelWriter.")
|
||||
sys.exit(1)
|
||||
|
||||
if "clean" in sys.argv:
|
||||
sys.argv.remove("clean")
|
||||
cleanInstall()
|
||||
|
||||
if "pip" in sys.argv:
|
||||
sys.argv.remove("pip")
|
||||
installPackages(hostOS)
|
||||
|
||||
if "freeze" in sys.argv:
|
||||
sys.argv.remove("freeze")
|
||||
doFreeze = True
|
||||
|
||||
if "onefile" in sys.argv:
|
||||
sys.argv.remove("onefile")
|
||||
doFreeze = True
|
||||
oneFile = True
|
||||
|
||||
if "setup" in sys.argv:
|
||||
sys.argv.remove("setup")
|
||||
if hostOS == OS_WIN:
|
||||
oneFile = False
|
||||
makeSetup = True
|
||||
else:
|
||||
print("Error: Argument 'setup' for Inno Setup is Windows only.")
|
||||
sys.exit(1)
|
||||
|
||||
if doFreeze:
|
||||
freezePackage(buildWindowed, oneFile, makeSetup, hostOS)
|
||||
|
||||
if makeSetup:
|
||||
innoSetup()
|
||||
|
||||
# END Main
|
||||
@@ -0,0 +1,28 @@
|
||||
[Main]
|
||||
name = Solarized Dark
|
||||
author = nullbasis
|
||||
credit = Ethan Schoonover
|
||||
url = https://ethanschoonover.com/solarized/
|
||||
license = MIT
|
||||
licenseurl = https://github.com/altercation/solarized/blob/master/LICENSE
|
||||
|
||||
[Palette]
|
||||
window = 0, 43, 54
|
||||
windowtext = 253, 246, 227
|
||||
base = 7, 54, 66
|
||||
alternatebase = 67, 67, 67
|
||||
text = 253, 246, 227
|
||||
tooltipbase = 133, 153, 0
|
||||
tooltiptext = 0, 43, 54
|
||||
button = 7, 54, 66
|
||||
buttontext = 253, 246, 227
|
||||
brighttext = 253, 246, 227
|
||||
highlight = 42, 161, 152
|
||||
highlightedtext = 0, 43, 54
|
||||
link = 38, 139, 210
|
||||
linkvisited = 38, 139, 210
|
||||
|
||||
[GUI]
|
||||
statusnone = 88, 110, 117
|
||||
statussaved = 42, 161, 152
|
||||
statusunsaved = 203, 75, 22
|
||||
@@ -0,0 +1,28 @@
|
||||
[Main]
|
||||
name = Solarized Light
|
||||
author = nullbasis
|
||||
credit = Ethan Schoonover
|
||||
url = https://ethanschoonover.com/solarized/
|
||||
license = MIT
|
||||
licenseurl = https://github.com/altercation/solarized/blob/master/LICENSE
|
||||
|
||||
[Palette]
|
||||
window = 238, 232, 213
|
||||
windowtext = 0, 43, 54
|
||||
base = 253, 246, 227
|
||||
alternatebase = 238, 232, 213
|
||||
text = 0, 43, 54
|
||||
tooltipbase = 133, 153, 0
|
||||
tooltiptext = 0, 43, 54
|
||||
button = 238, 232, 213
|
||||
buttontext = 0, 43, 54
|
||||
brighttext = 0, 43, 54
|
||||
highlight = 42, 161, 152
|
||||
highlightedtext = 253, 246, 227
|
||||
link = 38, 139, 210
|
||||
linkvisited = 38, 139, 210
|
||||
|
||||
[GUI]
|
||||
statusnone = 88, 110, 117
|
||||
statussaved = 42, 161, 152
|
||||
statusunsaved = 203, 75, 22
|
||||
@@ -0,0 +1,25 @@
|
||||
[Main]
|
||||
name = Solarized Dark
|
||||
author = nullbasis
|
||||
credit = Ethan Schoonover
|
||||
url = https://ethanschoonover.com/solarized/
|
||||
license = MIT
|
||||
licenseurl = https://github.com/altercation/solarized/blob/master/LICENSE
|
||||
|
||||
[Syntax]
|
||||
background = 7, 54, 66
|
||||
text = 253, 246, 227
|
||||
link = 38, 139, 210
|
||||
headertext = 147, 161, 161
|
||||
headertag = 42, 161, 152
|
||||
emphasis = 38, 139, 210
|
||||
straightquotes = 211, 54, 130
|
||||
doublequotes = 42, 161, 152
|
||||
singlequotes = 42, 161, 152
|
||||
hidden = 147, 161, 161
|
||||
keyword = 133, 153, 0
|
||||
value = 203, 75, 22
|
||||
spellcheckline = 203, 75, 22
|
||||
tagerror = 220, 50, 47
|
||||
replacetag = 133, 153, 0
|
||||
modifier = 181, 137, 0
|
||||
@@ -0,0 +1,25 @@
|
||||
[Main]
|
||||
name = Solarized Light
|
||||
author = nullbasis
|
||||
credit = Ethan Schoonover
|
||||
url = https://ethanschoonover.com/solarized/
|
||||
license = MIT
|
||||
licenseurl = https://github.com/altercation/solarized/blob/master/LICENSE
|
||||
|
||||
[Syntax]
|
||||
background = 253, 246, 227
|
||||
text = 0, 43, 54
|
||||
link = 38, 139, 210
|
||||
headertext = 88, 110, 117
|
||||
headertag = 42, 161, 152
|
||||
emphasis = 38, 139, 210
|
||||
straightquotes = 211, 54, 130
|
||||
doublequotes = 42, 161, 152
|
||||
singlequotes = 42, 161, 152
|
||||
hidden = 88, 110, 117
|
||||
keyword = 133, 153, 0
|
||||
value = 203, 75, 22
|
||||
spellcheckline = 203, 75, 22
|
||||
tagerror = 220, 50, 47
|
||||
replacetag = 133, 153, 0
|
||||
modifier = 181, 137, 0
|
||||
+10
-4
@@ -268,10 +268,16 @@ class Config:
|
||||
logger.verbose("Config path: %s" % self.confPath)
|
||||
logger.verbose("Data path: %s" % self.dataPath)
|
||||
|
||||
self.confFile = self.appHandle+".conf"
|
||||
self.lastPath = os.path.expanduser("~")
|
||||
self.appPath = getattr(sys, "_MEIPASS", os.path.abspath(os.path.dirname(__file__)))
|
||||
self.appRoot = os.path.join(self.appPath, os.path.pardir)
|
||||
self.confFile = self.appHandle+".conf"
|
||||
self.lastPath = os.path.expanduser("~")
|
||||
self.appPath = getattr(sys, "_MEIPASS", os.path.abspath(os.path.dirname(__file__)))
|
||||
self.appRoot = os.path.abspath(os.path.join(self.appPath, os.path.pardir))
|
||||
|
||||
if self.appRoot.endswith(".pyz"):
|
||||
self.appRoot = os.path.abspath(os.path.join(self.appRoot, os.path.pardir))
|
||||
self.appPath = self.appRoot
|
||||
|
||||
# Assets
|
||||
self.assetPath = os.path.join(self.appPath, "assets")
|
||||
self.themeRoot = os.path.join(self.assetPath, "themes")
|
||||
self.dictPath = os.path.join(self.assetPath, "dict")
|
||||
|
||||
+2
-2
@@ -454,7 +454,7 @@ class GuiDocEditor(QTextEdit):
|
||||
docChanged = self.docChanged
|
||||
if self.mainConf.scrollPastEnd:
|
||||
docFrame = self.qDocument.rootFrame().frameFormat()
|
||||
docFrame.setBottomMargin(max(0, 0.6*(wH - uM - lM - 4*tB)))
|
||||
docFrame.setBottomMargin(max(0, 0.9*(wH - uM - lM - 4*tB)))
|
||||
self.qDocument.rootFrame().setFrameFormat(docFrame)
|
||||
|
||||
# This is needed as the setFrameFormat function itself will
|
||||
@@ -846,7 +846,7 @@ class GuiDocEditor(QTextEdit):
|
||||
if okMod and okKey:
|
||||
cNew = self.cursorRect().center().y()
|
||||
cMov = cNew - cOld
|
||||
mPos = self.mainConf.autoScrollPos * self.viewport().height() * 0.01
|
||||
mPos = self.mainConf.autoScrollPos*0.01 * self.viewport().height()
|
||||
if abs(cMov) > 0 and cOld > mPos:
|
||||
# Move the scroll bar
|
||||
vBar = self.verticalScrollBar()
|
||||
|
||||
+14
-5
@@ -179,11 +179,14 @@ class GuiTheme:
|
||||
def loadFonts(self):
|
||||
"""Add the fonts in the assets fonts folder to the app.
|
||||
"""
|
||||
logger.debug("Loading additional fonts")
|
||||
|
||||
ttfList = []
|
||||
fontAssets = os.path.join(self.mainConf.assetPath, self.fontPath)
|
||||
for fontFam in os.listdir(fontAssets):
|
||||
fontDir = os.path.join(fontAssets, fontFam)
|
||||
if os.path.isdir(fontDir):
|
||||
logger.verbose("Found font: %s" % fontFam)
|
||||
if fontFam not in self.guiFontDB.families():
|
||||
for fontFile in os.listdir(fontDir):
|
||||
ttfFile = os.path.join(fontDir, fontFile)
|
||||
@@ -191,10 +194,11 @@ class GuiTheme:
|
||||
ttfList.append(ttfFile)
|
||||
|
||||
for ttfFile in ttfList:
|
||||
logger.verbose("Font asset: %s" % os.path.relpath(ttfFile))
|
||||
relPath = os.path.relpath(ttfFile, fontAssets)
|
||||
logger.verbose("Adding font: %s" % relPath)
|
||||
fontID = self.guiFontDB.addApplicationFont(ttfFile)
|
||||
if fontID < 0:
|
||||
logger.error("Failed to add font: %s" % os.path.relpath(ttfFile))
|
||||
logger.error("Failed to add font: %s" % relPath)
|
||||
|
||||
return
|
||||
|
||||
@@ -250,7 +254,8 @@ class GuiTheme:
|
||||
return True
|
||||
|
||||
def loadTheme(self):
|
||||
|
||||
"""Load the currently specified GUI theme.
|
||||
"""
|
||||
logger.debug("Loading theme files")
|
||||
logger.debug("System icon theme is '%s'" % str(QIcon.themeName()))
|
||||
|
||||
@@ -320,6 +325,9 @@ class GuiTheme:
|
||||
return True
|
||||
|
||||
def loadSyntax(self):
|
||||
"""Load the currently specified syntax highlighter theme.
|
||||
"""
|
||||
logger.debug("Loading syntax theme files")
|
||||
|
||||
confParser = configparser.ConfigParser()
|
||||
try:
|
||||
@@ -366,7 +374,7 @@ class GuiTheme:
|
||||
return True
|
||||
|
||||
def listThemes(self):
|
||||
"""Scan the gui themes folder and list all themes.
|
||||
"""Scan the GUI themes folder and list all themes.
|
||||
"""
|
||||
if self.themeList:
|
||||
return self.themeList
|
||||
@@ -765,7 +773,8 @@ class GuiIcons:
|
||||
# Otherwise, we start looking for it
|
||||
# First in the theme folder
|
||||
if iconKey in self.themeMap:
|
||||
logger.verbose("Loading: %s" % os.path.relpath(self.themeMap[iconKey]))
|
||||
relPath = os.path.relpath(self.themeMap[iconKey], self.mainConf.iconPath)
|
||||
logger.verbose("Loading: %s" % relPath)
|
||||
return QIcon(self.themeMap[iconKey])
|
||||
|
||||
# Next, we try to load the Qt style icons
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
The main setup script for novelWeiter.
|
||||
The main setup script for novelWriter.
|
||||
|
||||
It runs the standard setuptool.setup() with all options taken from the
|
||||
setup.cfg file.
|
||||
|
||||
In addtion, a few speicalised commands are available. These are
|
||||
In addition, a few specialised commands are available. These are
|
||||
described in the help text in the main section.
|
||||
"""
|
||||
|
||||
@@ -20,20 +20,89 @@ OS_WIN = 2
|
||||
OS_DARWIN = 3
|
||||
|
||||
# =============================================================================================== #
|
||||
# Qt Assistant Documentation Builder
|
||||
# General
|
||||
# =============================================================================================== #
|
||||
|
||||
##
|
||||
# Package Installer (pip)
|
||||
##
|
||||
|
||||
def installPackages(hostOS):
|
||||
"""Install package dependencies both for this script and for running
|
||||
novelWriter itself.
|
||||
"""
|
||||
print("")
|
||||
print("Installing Dependencies")
|
||||
print("#######################")
|
||||
print("")
|
||||
|
||||
installQueue = ["pip", "-r requirements.txt"]
|
||||
if hostOS == OS_DARWIN:
|
||||
installQueue.append("pyobjc")
|
||||
|
||||
pyCmd = [sys.executable, "-m"]
|
||||
pipCmd = ["pip", "install", "--user", "--upgrade"]
|
||||
for stepCmd in installQueue:
|
||||
pkgCmd = stepCmd.split(" ")
|
||||
try:
|
||||
subprocess.call(pyCmd + pipCmd + pkgCmd)
|
||||
except Exception as e:
|
||||
print("Failed with error:")
|
||||
print(str(e))
|
||||
sys.exit(1)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Clean Build and Dist Folders (clean)
|
||||
##
|
||||
|
||||
def cleanInstall():
|
||||
"""Recursively delete the 'build' and 'dist' folders.
|
||||
"""
|
||||
print("")
|
||||
print("Cleaning up build environment ...")
|
||||
|
||||
buildDir = os.path.join(os.getcwd(), "build")
|
||||
if os.path.isdir(buildDir):
|
||||
try:
|
||||
shutil.rmtree(buildDir)
|
||||
print("Deleted folder 'build'")
|
||||
except Exception as e:
|
||||
print("Error: Cannot delete 'build' folder.")
|
||||
print(str(e))
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("Folder 'build' not found")
|
||||
|
||||
distDir = os.path.join(os.getcwd(), "dist")
|
||||
if os.path.isdir(distDir):
|
||||
try:
|
||||
shutil.rmtree(distDir)
|
||||
print("Deleted folder 'dist'")
|
||||
except Exception as e:
|
||||
print("Error: Cannot delete 'dist' folder.")
|
||||
print(str(e))
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("Folder 'dist' not found")
|
||||
|
||||
print("")
|
||||
|
||||
return
|
||||
|
||||
# =============================================================================================== #
|
||||
# Additional Buiilds
|
||||
# =============================================================================================== #
|
||||
|
||||
##
|
||||
# Qt Assistant Documentation Builder (qthelp)
|
||||
##
|
||||
|
||||
def buildQtDocs():
|
||||
"""This function will build the documentation as a Qt help file. The
|
||||
file is then copied into the nw/assets/help directory and can be
|
||||
included in builds.
|
||||
|
||||
Depends on packages:
|
||||
* pip install sphinx
|
||||
* pip install sphinx-rtd-theme
|
||||
* pip install sphinxcontrib-qthelp
|
||||
|
||||
It also requires the qhelpgenerator to be available on the system.
|
||||
"""
|
||||
buildDir = os.path.join("docs", "build", "qthelp")
|
||||
helpDir = os.path.join("nw", "assets", "help")
|
||||
@@ -85,6 +154,13 @@ def buildQtDocs():
|
||||
print("")
|
||||
if buildFail:
|
||||
print("Documentation build: FAILED")
|
||||
print("")
|
||||
print("Dependencies:")
|
||||
print(" * pip install sphinx")
|
||||
print(" * pip install sphinx-rtd-theme")
|
||||
print(" * pip install sphinxcontrib-qthelp")
|
||||
print("")
|
||||
print("It also requires the qhelpgenerator to be available on the system.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("Documentation build: OK")
|
||||
@@ -92,9 +168,9 @@ def buildQtDocs():
|
||||
|
||||
return
|
||||
|
||||
# =============================================================================================== #
|
||||
# Sample Project ZIP File Builder
|
||||
# =============================================================================================== #
|
||||
##
|
||||
# Sample Project ZIP File Builder (sample)
|
||||
##
|
||||
|
||||
def buildSampleZip():
|
||||
"""Bundle the sample project into a single zip file to be saved into
|
||||
@@ -133,9 +209,313 @@ def buildSampleZip():
|
||||
return
|
||||
|
||||
# =============================================================================================== #
|
||||
# Create Launcher
|
||||
# Python Packaging
|
||||
# =============================================================================================== #
|
||||
|
||||
##
|
||||
# Make Simple Package (winpack)
|
||||
##
|
||||
|
||||
def makeSimplePackage(embedPython):
|
||||
"""Run zipapp to freeze the packages. This assumes zipapp and pip
|
||||
are already installed.
|
||||
"""
|
||||
import urllib.request
|
||||
import zipfile
|
||||
import zipapp
|
||||
|
||||
# Set Up Folder
|
||||
# =============
|
||||
|
||||
if not os.path.isdir("dist"):
|
||||
os.mkdir("dist")
|
||||
|
||||
outDir = os.path.join("dist", "novelWriter")
|
||||
zipDir = os.path.join("dist", "zipapp_temp")
|
||||
libDir = os.path.join(outDir, "lib")
|
||||
if os.path.isdir(zipDir):
|
||||
shutil.rmtree(zipDir)
|
||||
if os.path.isdir(outDir):
|
||||
shutil.rmtree(outDir)
|
||||
|
||||
os.mkdir(outDir)
|
||||
os.mkdir(libDir)
|
||||
|
||||
# Download Python Embeddable
|
||||
# ==========================
|
||||
|
||||
if embedPython:
|
||||
print("")
|
||||
print("# Adding Python Embeddable")
|
||||
print("# ========================")
|
||||
print("")
|
||||
|
||||
pyVers = "%d.%d.%d" % (sys.version_info[:3])
|
||||
zipFile = "python-%s-embed-amd64.zip" % pyVers
|
||||
pyZip = os.path.join("dist", zipFile)
|
||||
if not os.path.isfile(pyZip):
|
||||
pyUrl = f"https://www.python.org/ftp/python/{pyVers}/{zipFile}"
|
||||
print("Downloading: %s" % pyUrl)
|
||||
urllib.request.urlretrieve(pyUrl, pyZip)
|
||||
|
||||
print("Extracting ...")
|
||||
with zipfile.ZipFile(pyZip, "r") as inFile:
|
||||
inFile.extractall(outDir)
|
||||
|
||||
print("Done")
|
||||
print("")
|
||||
|
||||
# Make sample.zip
|
||||
# ===============
|
||||
|
||||
try:
|
||||
buildSampleZip()
|
||||
except Exception as e:
|
||||
print("Failed with error:")
|
||||
print(str(e))
|
||||
sys.exit(1)
|
||||
|
||||
# Copy Package Files
|
||||
# ==================
|
||||
|
||||
print("")
|
||||
print("# Copying Package Files")
|
||||
print("# =====================")
|
||||
print("")
|
||||
|
||||
copyList = ["CHANGELOG.md", "LICENSE.md", "requirements.txt"]
|
||||
iconList = ["novelwriter.ico", "x-novelwriter-project.ico"]
|
||||
cpIgnore = shutil.ignore_patterns("__pycache__")
|
||||
|
||||
print("Copying: nw")
|
||||
shutil.copytree("nw", os.path.join(zipDir, "nw"), ignore=cpIgnore)
|
||||
for copyFile in copyList:
|
||||
print("Copying: %s" % copyFile)
|
||||
shutil.copy2(copyFile, os.path.join(outDir, copyFile))
|
||||
for iconFile in iconList:
|
||||
print("Copying: %s" % iconFile)
|
||||
shutil.copy2(os.path.join("setup", "icons", iconFile), os.path.join(outDir, iconFile))
|
||||
|
||||
# Move assets to outDir as it should not be packed with the rest
|
||||
print("Copying: assets")
|
||||
os.rename(os.path.join(zipDir, "nw", "assets"), os.path.join(outDir, "assets"))
|
||||
|
||||
print("Writing: __main__.py")
|
||||
with open(os.path.join(zipDir, "__main__.py"), mode="w") as outFile:
|
||||
outFile.write(
|
||||
"#!\"pythonw.exe\"\n"
|
||||
"\n"
|
||||
"import os\n"
|
||||
"import sys\n"
|
||||
"\n"
|
||||
"sys.path.insert(\n"
|
||||
" 0, os.path.abspath(\n"
|
||||
" os.path.join(os.path.dirname(__file__), os.path.pardir, \"lib\")\n"
|
||||
" )\n"
|
||||
")\n\n"
|
||||
"if __name__ == \"__main__\":\n"
|
||||
" import nw\n"
|
||||
" nw.main()\n"
|
||||
)
|
||||
print("")
|
||||
|
||||
pyzFile = os.path.join(outDir, "novelWriter.pyz")
|
||||
zipapp.create_archive(zipDir, target=pyzFile, interpreter="python3")
|
||||
|
||||
# Install Dependencies
|
||||
# ====================
|
||||
|
||||
print("")
|
||||
print("# Installing Dependencies")
|
||||
print("# =======================")
|
||||
print("")
|
||||
|
||||
sysCmd = [sys.executable]
|
||||
sysCmd += "-m pip install -r requirements.txt --target".split()
|
||||
sysCmd += [libDir]
|
||||
try:
|
||||
subprocess.call(sysCmd)
|
||||
except Exception as e:
|
||||
print("Failed with error:")
|
||||
print(str(e))
|
||||
sys.exit(1)
|
||||
|
||||
for subDir in os.listdir(libDir):
|
||||
chkDir = os.path.join(libDir, subDir)
|
||||
if os.path.isdir(chkDir) and chkDir.endswith(".dist-info"):
|
||||
shutil.rmtree(chkDir)
|
||||
|
||||
print("")
|
||||
|
||||
# Remove Unneeded Library Files
|
||||
# =============================
|
||||
|
||||
delQtLibs = [
|
||||
"opengl32sw.dll",
|
||||
"Qt5DBus.dll",
|
||||
"Qt5Designer.dll",
|
||||
"Qt5Network.dll",
|
||||
"Qt5OpenGL.dll",
|
||||
"Qt5Qml.dll",
|
||||
"Qt5QmlModels.dll",
|
||||
"Qt5QmlWorkerScript.dll",
|
||||
"Qt5Quick.dll",
|
||||
"Qt5Quick3D.dll",
|
||||
"Qt5Quick3DAssetImport.dll",
|
||||
"Qt5Quick3DRender.dll",
|
||||
"Qt5Quick3DRuntimeRender.dll",
|
||||
"Qt5Quick3DUtils.dll",
|
||||
"Qt5QuickControls2.dll",
|
||||
"Qt5QuickParticles.dll",
|
||||
"Qt5QuickShapes.dll",
|
||||
"Qt5QuickTemplates2.dll",
|
||||
"Qt5QuickTest.dll",
|
||||
"Qt5QuickWidgets.dll",
|
||||
"Qt5Sql.dll",
|
||||
]
|
||||
qtLibDir = os.path.join(libDir, "PyQt5", "Qt", "bin")
|
||||
for libName in delQtLibs:
|
||||
delFile = os.path.join(qtLibDir, libName)
|
||||
if os.path.isfile(delFile):
|
||||
print("Deleting: %s" % delFile)
|
||||
os.unlink(delFile)
|
||||
|
||||
qmlDir = os.path.join(libDir, "PyQt5", "Qt", "qml")
|
||||
if os.path.isdir(qmlDir):
|
||||
shutil.rmtree(qmlDir)
|
||||
|
||||
print("")
|
||||
print("Done!")
|
||||
print("")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Run PyInstaller on Package (freeze, onefile)
|
||||
##
|
||||
|
||||
def freezePackage(buildWindowed, oneFile, makeSetup, hostOS):
|
||||
"""Run PyInstaller to freeze the packages. This assumes all
|
||||
dependencies are already in place.
|
||||
"""
|
||||
try:
|
||||
import PyInstaller.__main__ # noqa: E402
|
||||
except Exception:
|
||||
print("ERROR: Package 'pyinstaller' is missing on this system")
|
||||
sys.exit(1)
|
||||
|
||||
print("")
|
||||
print("Running PyInstaller")
|
||||
print("###################")
|
||||
print("")
|
||||
|
||||
if hostOS == OS_WIN:
|
||||
dotDot = ";"
|
||||
else:
|
||||
dotDot = ":"
|
||||
|
||||
sys.modules["FixTk"] = None
|
||||
instOpt = [
|
||||
"--name=novelWriter",
|
||||
"--clean",
|
||||
"--add-data=%s%s%s" % (os.path.join("nw", "assets"), dotDot, "assets"),
|
||||
"--icon=%s" % os.path.join("nw", "assets", "icons", "novelwriter.ico"),
|
||||
"--exclude-module=PyQt5.QtQml",
|
||||
"--exclude-module=PyQt5.QtBluetooth",
|
||||
"--exclude-module=PyQt5.QtDBus",
|
||||
"--exclude-module=PyQt5.QtMultimedia",
|
||||
"--exclude-module=PyQt5.QtMultimediaWidgets",
|
||||
"--exclude-module=PyQt5.QtNetwork",
|
||||
"--exclude-module=PyQt5.QtNetworkAuth",
|
||||
"--exclude-module=PyQt5.QtNfc",
|
||||
"--exclude-module=PyQt5.QtQuick",
|
||||
"--exclude-module=PyQt5.QtQuickWidgets",
|
||||
"--exclude-module=PyQt5.QtRemoteObjects",
|
||||
"--exclude-module=PyQt5.QtSensors",
|
||||
"--exclude-module=PyQt5.QtSerialPort",
|
||||
"--exclude-module=PyQt5.QtSql",
|
||||
"--exclude-module=FixTk",
|
||||
"--exclude-module=tcl",
|
||||
"--exclude-module=tk",
|
||||
"--exclude-module=_tkinter",
|
||||
"--exclude-module=tkinter",
|
||||
"--exclude-module=Tkinter",
|
||||
]
|
||||
|
||||
if buildWindowed:
|
||||
instOpt.append("--windowed")
|
||||
|
||||
if oneFile and not makeSetup:
|
||||
instOpt.append("--onefile")
|
||||
else:
|
||||
instOpt.append("--onedir")
|
||||
|
||||
instOpt.append("novelWriter.py")
|
||||
|
||||
# Make sample.zip first
|
||||
try:
|
||||
buildSampleZip()
|
||||
except Exception as e:
|
||||
print("Failed with error:")
|
||||
print(str(e))
|
||||
sys.exit(1)
|
||||
|
||||
PyInstaller.__main__.run(instOpt)
|
||||
|
||||
if not oneFile:
|
||||
# These files are not needed, and take up a fair bit of space.
|
||||
delFiles = []
|
||||
if hostOS == OS_WIN:
|
||||
delFiles = [
|
||||
"Qt5DBus.dll",
|
||||
"Qt5Network.dll",
|
||||
"Qt5Qml.dll",
|
||||
"Qt5QmlModels.dll",
|
||||
"Qt5Quick.dll",
|
||||
"Qt5Quick3D.dll",
|
||||
"Qt5Quick3DAssetImport.dll",
|
||||
"Qt5Quick3DRender.dll",
|
||||
"Qt5Quick3DRuntimeRender.dll",
|
||||
"Qt5Quick3DUtils.dll",
|
||||
"Qt5Sql.dll"
|
||||
]
|
||||
elif hostOS == OS_LINUX:
|
||||
delFiles = [
|
||||
"libQt5DBus.so.5",
|
||||
"libQt5Network.so.5",
|
||||
"libQt5Qml.so.5",
|
||||
"libQt5QmlModels.so.5",
|
||||
"libQt5Quick.so.5",
|
||||
"libQt5Quick3D.so.5",
|
||||
"libQt5Quick3DAssetImport.so.5",
|
||||
"libQt5Quick3DRender.so.5",
|
||||
"libQt5Quick3DRuntimeRender.so.5",
|
||||
"libQt5Quick3DUtils.so.5",
|
||||
"libQt5Sql.so.5"
|
||||
]
|
||||
distDir = os.path.join(os.getcwd(), "dist", "novelWriter")
|
||||
for delFile in delFiles:
|
||||
delPath = os.path.join(distDir, delFile)
|
||||
if os.path.isfile(delPath):
|
||||
print("Deleting file: %s" % delPath)
|
||||
os.unlink(delPath)
|
||||
|
||||
print("")
|
||||
print("Build Finished")
|
||||
print("")
|
||||
print("The novelWriter executable should be in the folder named 'dist'")
|
||||
print("")
|
||||
|
||||
return
|
||||
|
||||
# =============================================================================================== #
|
||||
# General Installers
|
||||
# =============================================================================================== #
|
||||
|
||||
##
|
||||
# XDG Installation (xdg-install, launcher)
|
||||
##
|
||||
|
||||
def xdgInstall():
|
||||
"""Will attempt to install icons and make a launcher.
|
||||
"""
|
||||
@@ -278,12 +658,51 @@ def xdgInstall():
|
||||
return
|
||||
|
||||
# =============================================================================================== #
|
||||
# Process Jobs
|
||||
# Windows Installers
|
||||
# =============================================================================================== #
|
||||
|
||||
##
|
||||
# Inno Setup Builder (setup-exe, setup-pyz)
|
||||
##
|
||||
|
||||
def innoSetup(setupType):
|
||||
"""Run the Inno Setup tool to build a setup.exe file for Windows based on either a pyinstaller
|
||||
freeze package (exe) or a zipapp package (pyz).
|
||||
"""
|
||||
print("")
|
||||
print("Running Inno Setup")
|
||||
print("##################")
|
||||
print("")
|
||||
|
||||
# Read the iss template
|
||||
issData = ""
|
||||
with open(os.path.join("setup", "win_setup_%s.iss" % setupType), mode="r") as inFile:
|
||||
issData = inFile.read()
|
||||
|
||||
import nw # noqa: E402
|
||||
issData = issData.replace(r"%%version%%", nw.__version__)
|
||||
issData = issData.replace(r"%%dir%%", os.getcwd())
|
||||
|
||||
with open("setup.iss", mode="w+") as outFile:
|
||||
outFile.write(issData)
|
||||
|
||||
try:
|
||||
subprocess.call(["iscc", "setup.iss"])
|
||||
except Exception as e:
|
||||
print("Inno Setup failed with error:")
|
||||
print(str(e))
|
||||
sys.exit(1)
|
||||
|
||||
return
|
||||
|
||||
# =============================================================================================== #
|
||||
# Process Command Line
|
||||
# =============================================================================================== #
|
||||
|
||||
if __name__ == "__main__":
|
||||
"""Parse command line options and run the commands.
|
||||
"""
|
||||
|
||||
# Detect OS
|
||||
if sys.platform.startswith("linux"):
|
||||
hostOS = OS_LINUX
|
||||
@@ -300,30 +719,75 @@ if __name__ == "__main__":
|
||||
"\n"
|
||||
"novelWriter Setup Tool\n"
|
||||
"======================\n"
|
||||
"This tool provides some additional setup commands for novelWriter.\n"
|
||||
"\n"
|
||||
"help Print this help message.\n"
|
||||
"qthelp Build the help documentation for use with the Qt Assistant.\n"
|
||||
" Run before install to enable in the the installed version.\n"
|
||||
"sample Build the sample project as a zip file.\n"
|
||||
" Run before install to enable creating sample projects.\n"
|
||||
"install Installs novelWriter to the system's Python install location.\n"
|
||||
" Run as root or with sudo for system-wide install, or as\n"
|
||||
" user for single user install.\n"
|
||||
"xdg-install Install launcher and icons for freedesktop systems.\n"
|
||||
" Run as root or with sudo for system-wide install, or as\n"
|
||||
" user for single user install.\n"
|
||||
"This tool provides setup and build commands for installing or distibuting novelWriter\n"
|
||||
"as a package on Linux, Mac and Windows. The available options are as follows:\n"
|
||||
"\n"
|
||||
"General:\n"
|
||||
"\n"
|
||||
" help Print the help message.\n"
|
||||
" pip Install all package dependencies for novelWriter using pip.\n"
|
||||
" clean Will attempt to delete the 'build' and 'dist' folders.\n"
|
||||
"\n"
|
||||
"Additional Builds:\n"
|
||||
"\n"
|
||||
" qthelp Build the help documentation for use with the Qt Assistant. Run before\n"
|
||||
" install to have local help enable in the the installed version.\n"
|
||||
" sample Build the sample project as a zip file. Run before install to enable\n"
|
||||
" creating sample projects in the in-app New Project Wizard.\n"
|
||||
"\n"
|
||||
"Python Packaging:\n"
|
||||
"\n"
|
||||
" pack-pyz Creates a pyz package in a folder with all dependencies using the\n"
|
||||
" zipapp tool. On Windows, python embeddable is added to the folder.\n"
|
||||
" freeze Freeze the package and produces a folder with all dependencies using\n"
|
||||
" the pyinstaller tool. This option is not designed for a specific OS.\n"
|
||||
" onefile Build a standalone executable with all dependencies bundled using the\n"
|
||||
" pyinstaller tool. Implies 'freeze', cannot be used with 'setup-exe'.\n"
|
||||
"\n"
|
||||
"General Installers:\n"
|
||||
"\n"
|
||||
" install Installs novelWriter to the system's Python install location.\n"
|
||||
" Run as root or with sudo for system-wide install, or as\n"
|
||||
" user for single user install.\n"
|
||||
" xdg-install Install launcher and icons for freedesktop systems.\n"
|
||||
" Run as root or with sudo for system-wide install, or as\n"
|
||||
" user for single user install.\n"
|
||||
"\n"
|
||||
"Windows Installers:\n"
|
||||
"\n"
|
||||
" setup-exe Build a Windows installer from a pyinstaller freeze package using Inno\n"
|
||||
" Setup. This option automatically disables 'onefile'.\n"
|
||||
" setup-pyz Build a Windows installer from a zipapp package using Inno Setup.\n"
|
||||
)
|
||||
|
||||
# Flags and Variables
|
||||
buildWindowed = True
|
||||
oneFile = False
|
||||
makeSetupExe = False
|
||||
makeSetupPyz = False
|
||||
doFreeze = False
|
||||
simplePack = False
|
||||
embedPython = False
|
||||
|
||||
# General
|
||||
# =======
|
||||
|
||||
if "help" in sys.argv:
|
||||
sys.argv.remove("help")
|
||||
print(helpMsg)
|
||||
sys.exit(0)
|
||||
|
||||
if "launcher" in sys.argv:
|
||||
sys.argv.remove("launcher")
|
||||
print("The 'launcher' option has been replaced by 'xdg-install'.")
|
||||
sys.exit(1)
|
||||
if "pip" in sys.argv:
|
||||
sys.argv.remove("pip")
|
||||
installPackages(hostOS)
|
||||
|
||||
if "clean" in sys.argv:
|
||||
sys.argv.remove("clean")
|
||||
cleanInstall()
|
||||
|
||||
# Additional Builds
|
||||
# =================
|
||||
|
||||
if "qthelp" in sys.argv:
|
||||
sys.argv.remove("qthelp")
|
||||
@@ -333,14 +797,79 @@ if __name__ == "__main__":
|
||||
sys.argv.remove("sample")
|
||||
buildSampleZip()
|
||||
|
||||
# Python Packaging
|
||||
# ================
|
||||
|
||||
if "pack-pyz" in sys.argv:
|
||||
sys.argv.remove("pack-pyz")
|
||||
simplePack = True
|
||||
if hostOS == OS_WIN:
|
||||
embedPython = True
|
||||
|
||||
if "freeze" in sys.argv:
|
||||
sys.argv.remove("freeze")
|
||||
doFreeze = True
|
||||
|
||||
if "onefile" in sys.argv:
|
||||
sys.argv.remove("onefile")
|
||||
doFreeze = True
|
||||
oneFile = True
|
||||
|
||||
# General Installers
|
||||
# ==================
|
||||
|
||||
if "launcher" in sys.argv:
|
||||
sys.argv.remove("launcher")
|
||||
print("The 'launcher' command has been replaced by 'xdg-install'.")
|
||||
sys.exit(1)
|
||||
|
||||
if "xdg-install" in sys.argv:
|
||||
sys.argv.remove("xdg-install")
|
||||
if hostOS == OS_WIN:
|
||||
print("ERROR: xdg-install cannot be used on Windows")
|
||||
print("ERROR: Command 'xdg-install' cannot be used on Windows")
|
||||
sys.exit(1)
|
||||
else:
|
||||
xdgInstall()
|
||||
|
||||
# Windows Installers
|
||||
# ==================
|
||||
|
||||
if "setup-exe" in sys.argv:
|
||||
sys.argv.remove("setup-exe")
|
||||
if hostOS == OS_WIN:
|
||||
oneFile = False
|
||||
makeSetupExe = True
|
||||
makeSetupPyz = False
|
||||
else:
|
||||
print("Error: Command 'setup-exe' for Inno Setup is Windows only.")
|
||||
sys.exit(1)
|
||||
|
||||
if "setup-pyz" in sys.argv:
|
||||
sys.argv.remove("setup-pyz")
|
||||
if hostOS == OS_WIN:
|
||||
makeSetupExe = False
|
||||
makeSetupPyz = True
|
||||
else:
|
||||
print("Error: Command 'setup-pyz' for Inno Setup is Windows only.")
|
||||
sys.exit(1)
|
||||
|
||||
# Actions
|
||||
# =======
|
||||
# For functions that are controlled by multiple flags, or need to be
|
||||
# run in a specific order.
|
||||
|
||||
if simplePack:
|
||||
makeSimplePackage(embedPython)
|
||||
|
||||
if doFreeze:
|
||||
freezePackage(buildWindowed, oneFile, makeSetupExe, hostOS)
|
||||
|
||||
if makeSetupExe:
|
||||
innoSetup("exe")
|
||||
|
||||
if makeSetupPyz:
|
||||
innoSetup("pyz")
|
||||
|
||||
if len(sys.argv) <= 1:
|
||||
# Nothing more to do
|
||||
sys.exit(0)
|
||||
|
||||
+37
-33
@@ -5,44 +5,48 @@ The root folder of the repository contains two scripts for setup and install:
|
||||
|
||||
## Script `setup.py`
|
||||
|
||||
The `setup.py` is a standard Python setup script with a couple of additional options:
|
||||
The `setup.py` is a standard Python setup script with a couple of additional
|
||||
options:
|
||||
|
||||
* `qthelp`: Will attempt to build a single file QtAssistand documentation file.
|
||||
This requires the Qt tools to be installed on the local system, as well as the sphinx build tools
|
||||
for the documentation.
|
||||
* `sample`: Will create a `sample.zip` file in the `nw/assets` folder.
|
||||
This is the file the New Project Wizard uses to generate an example project.
|
||||
If novelWriter is run from source, this file is not needed.
|
||||
* `xdg-install`: Will install novelWriter icons, mimetype, and desktop and menu launcher on Linux desktops.
|
||||
the application. This should work on standard Linux desktops.
|
||||
By default, this is installed for the current user. Run with `sudo` to install system-wide.
|
||||
### General
|
||||
|
||||
To install novelWriter as a local Python package, run:
|
||||
```bash
|
||||
sudo python setup.py install
|
||||
```
|
||||
`help` – Print the help message
|
||||
|
||||
## Script `make.py`
|
||||
`pip` – Install all package dependencies for novelWriter using pip.
|
||||
|
||||
The `make.py` script provides a number of convenient options for building packages if novelWriter.
|
||||
`clean` – Will attempt to delete the `build` and `dist` folders.
|
||||
|
||||
Usage:
|
||||
```bash
|
||||
python make.py [command]
|
||||
```
|
||||
### Additional Builds
|
||||
|
||||
It currently accept the following commands:
|
||||
`qthelp` – Build the help documentation for use with the Qt Assistant. Run
|
||||
before install to have local help enable in the the installed version
|
||||
|
||||
* `help`: Print the help message.
|
||||
* `freeze`: Freeze the package and produces a folder of all dependencies using pyinstaller.
|
||||
* `onefile`: Build a standalone executable with all dependencies bundled.
|
||||
Implies `freeze`, cannot be used with `setup`.
|
||||
* `pip`: Run pip to install all package dependencies for novelWriter and this build tool.
|
||||
* `setup`: Build a setup.exe installer for Windows.
|
||||
This option automaticall disables the `onefile` option.
|
||||
* `clean`: This will attempt to delete the `build` and `dist` folders in the current folder.
|
||||
`sample` – Build the sample project as a zip file. Run before install to enable
|
||||
creating sample projects in the in-app New Project Wizard.
|
||||
|
||||
For instance, to create a Windows installer, run:
|
||||
```bash
|
||||
python make.py freeze setup
|
||||
```
|
||||
### Python Packaging
|
||||
|
||||
`pack-pyz` – Creates a pyz package in a folder with all dependencies using the
|
||||
zipapp tool. This option is intended for Windows deployment.
|
||||
|
||||
`freeze` – Freeze the package and produces a folder with all dependencies using
|
||||
the pyinstaller tool. This option is not designed for a specific OS.
|
||||
|
||||
`onefile` – Build a standalone executable with all dependencies bundled using
|
||||
the pyinstaller tool. Implies `freeze`, cannot be used with `setup-exe`
|
||||
|
||||
### General Installers
|
||||
|
||||
`install` – Installs novelWriter to the system's Python install location. Run
|
||||
as root or with sudo for system-wide install, or as user for single user
|
||||
install.
|
||||
|
||||
`xdg-install` – Install launcher and icons for freedesktop systems. Run as root
|
||||
or with sudo for system-wide install, or as user for single user install.
|
||||
|
||||
### Windows Installers
|
||||
|
||||
`setup-exe` – Build a Windows installer from a pyinstaller freeze package using
|
||||
Inno Setup. This option automatically disables `onefile`.
|
||||
|
||||
`setup-pyz` – Build a Windows installer from a zipapp package using Inno Setup.
|
||||
|
||||
@@ -47,7 +47,6 @@ Source: "{#nwAppDir}\novelWriter\*"; DestDir: "{app}"; Flags: ignoreversion recu
|
||||
[Icons]
|
||||
Name: "{autoprograms}\{#nwAppName}"; Filename: "{app}\{#nwAppExeName}"
|
||||
Name: "{autodesktop}\{#nwAppName}"; Filename: "{app}\{#nwAppExeName}"; Tasks: desktopicon
|
||||
Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#nwAppName}"; Filename: "{app}\{#nwAppExeName}"; Tasks: quicklaunchicon
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\{#nwAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(nwAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
||||
@@ -56,5 +55,5 @@ Filename: "{app}\{#nwAppExeName}"; Description: "{cm:LaunchProgram,{#StringChang
|
||||
Root: HKA; Subkey: "Software\Classes\.nwx\OpenWithProgids"; ValueType: string; ValueName: "novelWriterProject.nwx"; ValueData: ""; Flags: uninsdeletevalue
|
||||
Root: HKA; Subkey: "Software\Classes\novelWriterProject.nwx"; ValueType: string; ValueName: ""; ValueData: "novelWriter Project File"; Flags: uninsdeletekey
|
||||
Root: HKA; Subkey: "Software\Classes\novelWriterProject.nwx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\assets\icons\x-novelwriter-project.ico"
|
||||
Root: HKA; Subkey: "Software\Classes\novelWriterProject.nwx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\novelWriter.exe"" ""%1"""
|
||||
Root: HKA; Subkey: "Software\Classes\Applications\novelWriter.exe\SupportedTypes"; ValueType: string; ValueName: ".nwx"; ValueData: ""
|
||||
Root: HKA; Subkey: "Software\Classes\novelWriterProject.nwx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#nwAppExeName}"" ""%1"""
|
||||
Root: HKA; Subkey: "Software\Classes\Applications\{#nwAppExeName}\SupportedTypes"; ValueType: string; ValueName: ".nwx"; ValueData: ""
|
||||
@@ -0,0 +1,59 @@
|
||||
; Script generated by the Inno Setup Script Wizard.
|
||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||
|
||||
#define nwAppDir "%%dir%%\dist"
|
||||
#define nwAppName "novelWriter"
|
||||
#define nwAppVersion "%%version%%"
|
||||
#define nwAppPublisher "novelWriter"
|
||||
#define nwAppURL "http://novelWriter.io"
|
||||
#define nwAppExeName "novelWriter.pyz"
|
||||
|
||||
[Setup]
|
||||
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
|
||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||
AppId={{459A75D0-951F-4932-9809-6002EC8E733E}
|
||||
AppName={#nwAppName}
|
||||
AppVersion={#nwAppVersion}
|
||||
AppVerName={#nwAppName} {#nwAppVersion}
|
||||
AppPublisher={#nwAppPublisher}
|
||||
AppPublisherURL={#nwAppURL}
|
||||
AppSupportURL={#nwAppURL}
|
||||
AppUpdatesURL={#nwAppURL}
|
||||
DefaultDirName={autopf}\{#nwAppName}
|
||||
DisableProgramGroupPage=yes
|
||||
; The [Icons] "quicklaunchicon" entry uses {userappdata} but its [Tasks] entry has a proper IsAdminInstallMode Check.
|
||||
UsedUserAreasWarning=no
|
||||
; Uncomment the following line to run in non administrative install mode (install for current user only.)
|
||||
;PrivilegesRequired=lowest
|
||||
PrivilegesRequiredOverridesAllowed=dialog
|
||||
OutputDir={#nwAppDir}
|
||||
OutputBaseFilename=novelwriter-{#nwAppVersion}-win10-amd64-pyz-setup
|
||||
Compression=lzma
|
||||
SolidCompression=yes
|
||||
WizardStyle=modern
|
||||
ArchitecturesInstallIn64BitMode=x64
|
||||
ChangesAssociations=yes
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode
|
||||
|
||||
[Files]
|
||||
Source: "{#nwAppDir}\novelWriter\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
|
||||
[Icons]
|
||||
Name: "{autoprograms}\{#nwAppName}"; Filename: "{app}\pythonw.exe"; Parameters: "{#nwAppExeName}"; IconFilename: "{app}\novelwriter.ico"
|
||||
Name: "{autodesktop}\{#nwAppName}"; Filename: "{app}\pythonw.exe"; Parameters: "{#nwAppExeName}"; IconFilename: "{app}\novelwriter.ico"; Tasks: desktopicon;
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\pythonw.exe"; Parameters: "{#nwAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(nwAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
||||
|
||||
[Registry]
|
||||
Root: HKA; Subkey: "Software\Classes\.nwx\OpenWithProgids"; ValueType: string; ValueName: "novelWriterProject.nwx"; ValueData: ""; Flags: uninsdeletevalue
|
||||
Root: HKA; Subkey: "Software\Classes\novelWriterProject.nwx"; ValueType: string; ValueName: ""; ValueData: "novelWriter Project File"; Flags: uninsdeletekey
|
||||
Root: HKA; Subkey: "Software\Classes\novelWriterProject.nwx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\assets\icons\x-novelwriter-project.ico"
|
||||
Root: HKA; Subkey: "Software\Classes\novelWriterProject.nwx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\pythonw.exe"" ""{app}\{#nwAppExeName}"" ""%1"""
|
||||
Root: HKA; Subkey: "Software\Classes\Applications\{#nwAppExeName}\SupportedTypes"; ValueType: string; ValueName: ".nwx"; ValueData: ""
|
||||
Reference in New Issue
Block a user