diff --git a/.gitignore b/.gitignore index 42c75bfc8..b9a975c0a 100644 --- a/.gitignore +++ b/.gitignore @@ -20,8 +20,8 @@ pip-wheel-metadata # Coverage .coverage -# Tox -.tox +# Editors +.idea # Flatpak flatpak/build @@ -34,4 +34,7 @@ macOS/ windows/ android/ linux/ -django/ \ No newline at end of file +django/ + +# Windows Install +win-installer/_build_root \ No newline at end of file diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 000000000..43e2d3067 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,7 @@ +version: 2 +formats: all +sphinx: + configuration: docs/conf.py +python: + install: + - requirements: docs/requirements.txt diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6e6f78ca6..f4f3dbdc2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,49 +1,131 @@ -Feel free to hack Gaphor. Patches are welcome. +# Contributing -Fetching Development Dependencies -================================= +### We :heart: our Contributors! -Gaphor uses pip to manage in easy way its dependencies. +First off, thank you for considering contributing to Gaphor. It's people like +you that make Gaphor such a great modeling tool. -To fetch and install dependencies as non-root user: -In Linux recommend using a virtual environment for installation. Since pyGTK doesn't work well with a virtualenv, -install it outside the virtual environment and then start your the virtualenv using the --system-site-packages option: - $ virtualenv --system-site-packages venv - $ source venv/bin/activate - $ pip install gaphor +### Why a Guideline? -Running Tests -============= -To run tests on Unix machine +Following these guidelines helps to communicate that you respect the time of +the developers managing and developing this open source project. In return, +they should reciprocate that respect in addressing your issue, assessing +changes, and helping you finalize your pull requests. - Xvfb :2.0 & - DISPLAY=:2.0 nosetests gaphor/ 2>&1 | tee tests.log +### What we are Looking For + +Gaphor is an open source project and we love to receive contributions from our +community — you! There are many ways to contribute, from writing tutorials or +blog posts, improving the documentation, submitting bug reports and feature +requests or writing code which can be incorporated into Gaphor itself. + +### What we are not Looking For + +Please, don't use the issue tracker for support questions. Check whether the +your question can be answered on the +[Gaphor Gitter Channel](https://gitter.im/gaphor/Lobby). + +# Ground Rules +### Responsibilities + + * Ensure cross-platform compatibility for every change that's accepted. + Windows, Mac, Debian & Ubuntu Linux. + * Ensure that code that goes into core meets all requirements in this + [PR Review Checklist](https://gist.github.com/audreyr/4feef90445b9680475f2). + * Create issues for any major changes and enhancements that you wish to make. + * Discuss things transparently and get community feedback. + * Don't add any classes to the codebase unless absolutely needed. Err on the side of using + functions. + * Keep feature versions as small as possible, preferably one new feature per + version. + * Be welcoming to newcomers and encourage diverse new contributors from all + backgrounds. See the + [Python Community Code of Conduct](https://www.python.org/psf/codeofconduct/). + +# Your First Contribution + +Unsure where to begin contributing to Gaphor? You can start by looking through +these `first-timers-only` and `up-for-grabs` issues: + + * [First-timers-only issues](https://github.com/gaphor/gaphor/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Afirst-timers-only) - + issues which should only require a few lines of code, and a test or two. + * [Up-for-grabs issues](https://github.com/gaphor/gaphor/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Aup-for-grabs) - + issues which should be a bit more involved than `first-timers-only` issues. + +### Is This Your First Open Source Contribution? + +Working on your first Pull Request? You can learn how from this *free* series, +[How to Contribute to an Open Source Project on +GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github). + +At this point, you're ready to make your changes! Feel free to ask for help; +everyone is a beginner at first :smile_cat: + +If a maintainer asks you to "rebase" your PR, they're saying that a lot of code +has changed, and that you need to update your branch so it's easier to merge. + +# Getting Started + +For something that is bigger than a one or two line fix: + +1. Create your own fork of the code +2. Install all development dependencies using: +`$ poetry install` +`$ pre-commit install` +If you haven't used poetry before, just run `pip install poetry`, and then run the commands above, it will do the correct thing. +3. Add tests for your changes, run the tests with `pytest`. +4. Do the changes in your fork. +5. If you like the change and think the project could use it: + * Be sure you have the pre-commit hook installed above, it will ensure that + [Black](https://github.com/ambv/black) is automatically run on any changes for + consistent code formatting. + * [Sign](https://help.github.com/articles/signing-commits/) your commits. + * Note the Gaphor Code of Conduct. + * Create a pull request. -Structure -========= +Small contributions such as fixing spelling errors, where the content is small +enough to not be considered intellectual property, can be submitted by a +contributor as a patch, without signing your commit. -Gaphor contains the following modules: +As a rule of thumb, changes are obvious fixes if they do not introduce any new +functionality or creative thinking. As long as the change does not affect +functionality, some likely examples include the following: +* Spelling / grammar fixes +* Typo correction, white space and formatting changes +* Comment clean up +* Bug fixes that change default return values or error codes stored in constants +* Adding logging messages or debugging output +* Changes to ‘metadata’ files like pyproject.toml, .gitignore, build scripts, etc. +* Moving source files from one directory or package to another -UML ---- -The UML module contains the UML 2.0 data model. This part is -quite stable and it is unlikely that code has to be changed -here. +# How to Report a Bug +If you find a security vulnerability, do NOT open an issue. Email dan@yeaw.me instead. - NOTE: The code is generated from a Gaphor model: uml2.gaphor. This - file can be loaded in gaphor. +When filing an issue, make sure to answer the questions in the issue template. -diagram -------- -The diagram module contains items that can be placed in diagrams. -In most cases the classes NamedItem and Relationship can serve -as bases for your class. +1. What version are you using? +2. What operating system are you using? +3. What did you do? +4. What did you expect to see? +5. What did you see instead? -ui --- -The user interface. This is where most of the work is to be done. +# How to Suggest a Feature or Enhancement +If you find yourself wishing for a feature that doesn't exist in Gaphor, +you are probably not alone. There are bound to be others out there with similar +needs. Many of the features that Gaphor has today have been added +because our users saw the need. Open an issue on our issues list on GitHub +which describes the feature you would like to see, why you need it, and how it +should work. + +# Code review process + +The core team looks at Pull Requests on a regular basis, you should expect a +response within a week. After feedback has been given we expect responses +within two weeks. After two weeks we may close the pull request if it isn't +showing any activity. + + +# Community +You can chat with the Gaphor community on gitter: https://gitter.im/Gaphor/Lobby. -misc ----- -Some utility stuff, such as Actions and aspects are put in here. diff --git a/README.md b/README.md index fdbdff30b..421764e61 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build state](https://travis-ci.com/gaphor/gaphor.svg?branch=master)](https://travis-ci.com/gaphor/gaphor) ![Docs build state](https://readthedocs.org/projects/gaphor/badge/?version=latest) -[![Coverage](https://coveralls.io/repos/github/gaphor/gaphor/badge.svg?branch=master)](https://coveralls.io/github/gaphor/gaphor?branch=master) +[![Coverage Status](https://coveralls.io/repos/github/gaphor/gaphor/badge.svg?branch=master)](https://coveralls.io/github/gaphor/gaphor?branch=master) [![PyPI](https://img.shields.io/pypi/v/gaphor.svg)](https://pypi.org/project/gaphor) [![Downloads](https://pepy.tech/badge/gaphor)](https://pepy.tech/project/gaphor) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) diff --git a/docs/Makefile b/docs/Makefile index ea67a8f18..36ad5d4e1 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,5 +1,6 @@ # Makefile for Sphinx documentation # +Python ?= python3 # You can set these variables from the command line. SPHINXOPTS = diff --git a/docs/actions.txt b/docs/actions.rst similarity index 100% rename from docs/actions.txt rename to docs/actions.rst diff --git a/docs/conf.py b/docs/conf.py index 23093e343..b88023335 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,222 +1,187 @@ # -*- coding: utf-8 -*- # -# This file is execfile()d with the current directory set to its containing dir. +# Configuration file for the Sphinx documentation builder. # -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config -import sys, os +# -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys # sys.path.insert(0, os.path.abspath('.')) +from pathlib import Path -# -- General configuration ----------------------------------------------------- +from tomlkit import parse + +# -- Project information ----------------------------------------------------- + +project = "Gaphor" +copyright = "2019, Arjan J. Molenaar" +author = "Arjan J. Molenaar" + +# The short X.Y version +version = "" +project_dir = Path(__file__).resolve().parent.parent +f = project_dir.joinpath("pyproject.toml") +release = parse(f.read_text())["tool"]["poetry"]["version"] + +# -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. +# # needs_sphinx = '1.0' -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.doctest", - "sphinx.ext.todo", + "sphinx.ext.intersphinx", "sphinx.ext.coverage", + "sphinx.ext.viewcode", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] -# The suffix of source filenames. -source_suffix = ".txt" - -# The encoding of source files. -# source_encoding = 'utf-8-sig' +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = ".rst" # The master toctree document. -master_doc = "contents" - -# General information about the project. -project = u"Gaphor" -copyright = u"2012, Gaphor development team" - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = "0.17.1" -# The full version, including alpha/beta/rc tags. -release = "0.17.1" +master_doc = "index" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -# language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# today = '' -# Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ["_build"] - -# The reST default role (used for this markup: `text`) to use for all documents. -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# show_authors = False +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] +pygments_style = None -# -- Options for HTML output --------------------------------------------------- +# -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = "default" +# +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. +# # html_theme_options = {} -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -# html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# html_favicon = None - # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] -# If not '', a 'Last updated on:' timestamp is inserted at the bottom of every page, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# # html_sidebars = {} -# Additional templates that should be rendered to pages, maps page names to -# template names. -# html_additional_pages = {} -# If false, no module index is generated. -# html_domain_indices = True - -# If false, no index is generated. -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None +# -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = "Gaphordoc" -# -- Options for LaTeX output -------------------------------------------------- +# -- Options for LaTeX output ------------------------------------------------ -# The paper size ('letter' or 'a4'). -# latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -# latex_font_size = '10pt' +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} # Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). latex_documents = [ - ( - "contents", - "Gaphor.tex", - u"Gaphor Documentation", - u"Gaphor development team", - "manual", - ) + (master_doc, "Gaphor.tex", "Gaphor Documentation", "Arjan J. Molenaar", "manual") ] -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# latex_logo = None -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# latex_use_parts = False - -# If true, show page references after internal links. -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# latex_show_urls = False - -# Additional stuff for the LaTeX preamble. -# latex_preamble = '' - -# Documents to append as an appendix to all manuals. -# latex_appendices = [] - -# If false, no module index is generated. -# latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- +# -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - ("contents", "gaphor", u"Gaphor Documentation", [u"Gaphor development team"], 1) +man_pages = [(master_doc, "gaphor", "Gaphor Documentation", [author], 1)] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + master_doc, + "Gaphor", + "Gaphor Documentation", + author, + "Gaphor", + "One line description of project.", + "Miscellaneous", + ) ] + + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ["search.html"] + + +# -- Extension configuration ------------------------------------------------- + +# -- Options for intersphinx extension --------------------------------------- + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {"https://docs.python.org/": None} diff --git a/docs/connect.txt b/docs/connect.rst similarity index 100% rename from docs/connect.txt rename to docs/connect.rst diff --git a/docs/custominstall.txt b/docs/custominstall.rst similarity index 100% rename from docs/custominstall.txt rename to docs/custominstall.rst diff --git a/docs/datamodel.txt b/docs/datamodel.rst similarity index 100% rename from docs/datamodel.txt rename to docs/datamodel.rst diff --git a/docs/framework.txt b/docs/framework.rst similarity index 100% rename from docs/framework.txt rename to docs/framework.rst diff --git a/docs/index.txt b/docs/index.rst similarity index 75% rename from docs/index.txt rename to docs/index.rst index e6ec746bc..0e474eb4d 100644 --- a/docs/index.txt +++ b/docs/index.rst @@ -1,17 +1,14 @@ -###################### -Documentation contents -###################### +Welcome to Gaphor's documentation! +================================== - *The source is the documentation.* - -Well, not for us. - -Currently, there's not a lot of documentation though. For those of you interested in Gaphor there's: +Some highlights of the documentation: * A :doc:`manual `. It outlines some of the ideas in and behind Gaphor. * The :ref:`tech-section` contains some interesting articles about the technology that drives Gaphor and Gaphas, Gaphor's canvas widget. -If you're into writing plug-ins for Gaphor you should have a look at our fabulous `Hello world `_ plug-in. +If you're into writing plug-ins for Gaphor you should have a look at our +fabulous `Hello world `_ +plug-in. .. toctree:: :maxdepth: 2 @@ -23,8 +20,9 @@ Running Gaphor on different platforms: .. toctree:: :maxdepth: 1 + windows linux - win32 + macos custominstall @@ -62,5 +60,3 @@ External links * The `official UML metamodel specification `_. This ''is'' our data model. -.. - # vim:syntax=rst diff --git a/docs/items.txt b/docs/items.rst similarity index 100% rename from docs/items.txt rename to docs/items.rst diff --git a/docs/linux.txt b/docs/linux.rst similarity index 100% rename from docs/linux.txt rename to docs/linux.rst diff --git a/docs/macos.rst b/docs/macos.rst new file mode 100644 index 000000000..888b86b77 --- /dev/null +++ b/docs/macos.rst @@ -0,0 +1,3 @@ +Gaphor on macOS +=============== + diff --git a/docs/manual/index.txt b/docs/manual/index.rst similarity index 100% rename from docs/manual/index.txt rename to docs/manual/index.rst diff --git a/docs/manual/introduction.txt b/docs/manual/introduction.rst similarity index 73% rename from docs/manual/introduction.txt rename to docs/manual/introduction.rst index 3579035e4..c2199c4ca 100644 --- a/docs/manual/introduction.txt +++ b/docs/manual/introduction.rst @@ -12,11 +12,11 @@ highly complex models. Gaphor is designed around the following principles: - Simplicity - The application should be easy to use. Only some basic knowledge of UML is required. + The application should be easy to use. Only some basic knowledge of UML is required. - Consistency - UML is a graphical modeling language, so all modeling is done in a diagram [#f1]_ . + UML is a graphical modeling language, so all modeling is done in a diagram [#f1]_ . - Workability - The application should not bother the user every time they do something non-UML-ish. + The application should not bother the user every time they do something non-UML-ish. This manual serves as a reference for all Gaphor has to offer. So, you may read it from start to finish, or jump to a section that interests you. diff --git a/docs/manual/modeling.txt b/docs/manual/modeling.rst similarity index 100% rename from docs/manual/modeling.txt rename to docs/manual/modeling.rst diff --git a/docs/manual/plugins.txt b/docs/manual/plugins.rst similarity index 100% rename from docs/manual/plugins.txt rename to docs/manual/plugins.rst diff --git a/docs/manual/stereotypes.txt b/docs/manual/stereotypes.rst similarity index 100% rename from docs/manual/stereotypes.txt rename to docs/manual/stereotypes.rst diff --git a/docs/manual/working.txt b/docs/manual/working.rst similarity index 100% rename from docs/manual/working.txt rename to docs/manual/working.rst diff --git a/docs/model.txt b/docs/model.rst similarity index 100% rename from docs/model.txt rename to docs/model.rst diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..445d77834 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,5 @@ +docutils==0.14 +Pygments==2.3.1 +sphinx-rtd-theme==0.4.3 +sphinxcontrib-websupport==1.1.0 +tomlkit==0.5.3 diff --git a/docs/services.txt b/docs/services.rst similarity index 97% rename from docs/services.txt rename to docs/services.rst index 451253b11..80c596fbf 100644 --- a/docs/services.txt +++ b/docs/services.rst @@ -53,5 +53,5 @@ information along with its service definition. .. seealso:: - Writing plugins :ref:`` + :doc:`Writing plugins ` diff --git a/docs/so.txt b/docs/so.rst similarity index 100% rename from docs/so.txt rename to docs/so.rst diff --git a/docs/stereotypes.txt b/docs/stereotypes.rst similarity index 100% rename from docs/stereotypes.txt rename to docs/stereotypes.rst diff --git a/docs/storage.txt b/docs/storage.rst similarity index 81% rename from docs/storage.txt rename to docs/storage.rst index 1c6d723de..2c682663f 100644 --- a/docs/storage.txt +++ b/docs/storage.rst @@ -30,14 +30,14 @@ All elements inside a canvas have a tag ``Item``. + grid_color="0x80ff" grid_int_x="10.0" grid_int_y="10.0" + grid_ofs_x="0.0" grid_ofs_y="0.0" snap_to_grid="0" + static_extents="0" affine="(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)" + id="DCE:xxxx"> + height="78.0" subject="3" type="ActorItem" width="38.0"/> + height="26.0" subject="5" type="CommentItem" width="100.0"/> @@ -46,7 +46,7 @@ All elements inside a canvas have a tag ``Item``. - diff --git a/docs/undo.txt b/docs/undo.rst similarity index 100% rename from docs/undo.txt rename to docs/undo.rst diff --git a/docs/windows.rst b/docs/windows.rst new file mode 100644 index 000000000..e36b10636 --- /dev/null +++ b/docs/windows.rst @@ -0,0 +1,3 @@ +Gaphor on Windows +================= + diff --git a/docs/xml-format.txt b/docs/xml-format.rst similarity index 100% rename from docs/xml-format.txt rename to docs/xml-format.rst diff --git a/gaphor/UML/element.py b/gaphor/UML/element.py index 8341af78d..e038a87b6 100644 --- a/gaphor/UML/element.py +++ b/gaphor/UML/element.py @@ -105,7 +105,7 @@ class Element(object): def isKindOf(self, class_): """ - Returns true if the object is an instance of class_. + Returns true if the object is an instance of `class_`. """ return isinstance(self, class_) diff --git a/gaphor/UML/elementfactory.py b/gaphor/UML/elementfactory.py index 76386b773..1d8a524bc 100644 --- a/gaphor/UML/elementfactory.py +++ b/gaphor/UML/elementfactory.py @@ -1,7 +1,4 @@ -# vim: sw=4 -""" -Factory for and registration of model elements. -""" +"""Factory for and registration of model elements.""" import uuid from zope import component @@ -27,13 +24,12 @@ class ElementFactory(object): The ElementFactory is used to create elements and do lookups to elements. - Notifications are send with as arguments (name, element, *user_data). + Notifications are sent as arguments (name, element, `*user_data`). The following names are used: create - a new model element is created (element is newly created element) remove - a model element is removed (element is to be removed element) - model - a new model has been loaded (element is None) - flush - model is flushed: all element are removed from the factory - (element is None) + model - a new model has been loaded (element is None) flush - model is + flushed: all element are removed from the factory (element is None) """ def __init__(self): diff --git a/pytest.ini b/pytest.ini index 9fbeea05b..cc6d28fce 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,5 @@ [pytest] testpaths = gaphor tests docs -doctest-extension=.txt +doctest-extension=.rst python_files = test_*.py +addopts = --doctest-modules diff --git a/setup.py b/setup.py index 517dcf132..387acd0b0 100644 --- a/setup.py +++ b/setup.py @@ -94,6 +94,7 @@ setup( "PyGObject >= 3.30.0", "gaphas >= 0.7.2", "zope.component >= 3.4.0", + "zope.interface >= 4.6.0", ], zip_safe=False, entry_points={ diff --git a/win-installer/README.md b/win-installer/README.md new file mode 100644 index 000000000..10bed8442 --- /dev/null +++ b/win-installer/README.md @@ -0,0 +1,45 @@ +# Windows Installer Build Scripts +We use msys2 for creating the Windows installer and development on Windows +because that is what PyGObject currently supports. + +## Development +For developing on Windows you have two choices. + +#### 1. Use an Existing Gaphor Installation Plus a Git Checkout: +- Clone the git repo with some git client +- Download and install the latest [installer +build](https://github.com/gaphor/gaphor/releases/download/latest/gaphor-latest-installer.exe) +- Go to setup.py in the git checkout and run C:\Program +Files\Gaphor\bin\python.exe setup.py run. + +#### 2. Use a Full MSYS2 Environment +- Download msys2 64-bit from https://msys2.org +- Follow instructions on https://msys2.org +- Execute C:\msys64\mingw64.exe +- Run `pacman -Syu` to update packages +- Run `pacman -S git` to install git +- Run git clone https://github.com/gaphor/gaphor.git +- Run cd gaphor/win_installer to end up where this README exists. +- Execute `./bootstrap.sh` to install all the needed dependencies. +- Now go to the source code `cd ../` +- To run Gaphor execute `python3 setup.py run` + +## Creating an Installer +Simply run `./build.sh` and both the installer and the portable +installer should appear in this directory. + +You can also run the build using a specific git version: +1. Pass a git tag with: `./build.sh release-1.0.0` +1. Pass a git commit hash with: `./build.sh a5d3e53406fadd1fe089aa995b650949256d4981` +1. Pass nothing to build master + +Note: `build.sh` clones from the local repository and not from GitHub so any +commits present locally will be cloned as well. + +## Updating an Existing Installer +We directly follow msys2 upstream so building the installer two weeks later +might result in newer versions of dependencies being used. To reduce the risk of +stable release breakage you can use an existing installer and just install a +newer Gaphor version into it and then repack it: + +`./rebuild.sh gaphor-1.0.0-installer.exe [git tag / commit]` \ No newline at end of file diff --git a/win-installer/_base.sh b/win-installer/_base.sh new file mode 100644 index 000000000..e1290116d --- /dev/null +++ b/win-installer/_base.sh @@ -0,0 +1,318 @@ +#!/usr/bin/env bash + +# Copyright 2016 Christoph Reiter, 2019 Dan Yeaw +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +set -e +DIR="$( cd "$( dirname "$0" )" && pwd )" +cd "${DIR}" + +# CONFIG START + +ARCH="x86_64" +BUILD_VERSION="0" + +# CONFIG END + +MISC="${DIR}"/misc +if [ "${ARCH}" = "x86_64" ]; then + MINGW="mingw64" +else + MINGW="mingw32" +fi + +VERSION="0.0.0" +VERSION_DESC="UNKNOWN" + +function set_build_root { + BUILD_ROOT="$1" + REPO_CLONE="${BUILD_ROOT}"/gaphor + MINGW_ROOT="${BUILD_ROOT}/${MINGW}" +} + +set_build_root "${DIR}/_build_root" + +function build_pacman { + pacman --cachedir "/var/cache/pacman/pkg" --root "${BUILD_ROOT}" "$@" +} + +function build_pip { + "${BUILD_ROOT}"/"${MINGW}"/bin/pip3.exe "$@" +} + +function build_python { + "${BUILD_ROOT}"/"${MINGW}"/bin/python3.exe "$@" +} + +function build_compileall_pyconly { + MSYSTEM= build_python -m compileall --invalidation-mode unchecked-hash -b "$@" +} + +function build_compileall { + MSYSTEM= build_python -m compileall --invalidation-mode unchecked-hash "$@" +} + +function install_pre_deps { + pacman -S --needed --noconfirm p7zip git dos2unix \ + mingw-w64-"${ARCH}"-nsis mingw-w64-"${ARCH}"-wget mingw-w64-"${ARCH}"-toolchain +} + +function create_root { + mkdir -p "${BUILD_ROOT}" + + mkdir -p "${BUILD_ROOT}"/var/lib/pacman + mkdir -p "${BUILD_ROOT}"/var/log + mkdir -p "${BUILD_ROOT}"/tmp + + build_pacman --noconfirm -Syu + build_pacman --noconfirm -S base +} + +function extract_installer { + [ -z "$1" ] && (echo "Missing arg"; exit 1) + + mkdir -p "$BUILD_ROOT" + 7z x -o"$BUILD_ROOT"/"$MINGW" "$1" + rm -rf "$MINGW_ROOT"/'$PLUGINSDIR' "$MINGW_ROOT"/*.txt "$MINGW_ROOT"/*.nsi +} + +function install_deps { + build_pacman --noconfirm -S \ + mingw-w64-"${ARCH}"-gtk3 \ + mingw-w64-"${ARCH}"-python3 \ + mingw-w64-"${ARCH}"-python3-gobject \ + mingw-w64-"${ARCH}"-python3-cairo \ + mingw-w64-"${ARCH}"-python3-pip \ + mingw-w64-"${ARCH}"-python3-setuptools \ + mingw-w64-"${ARCH}"-python3-zope.interface + + # First time installing pip has post-install errors, reinstall to fix + # sed: can't read mingw64/bin/pip3-script.py: No such file or directory + # sed: can't read mingw64/bin/pip3.7-script.py: No such file or directory + # error: command (/usr/bin/bash /usr/bin/bash -c . /tmp/alpm_omSbWr/.INSTALL; post_install 19.0.1-1 ) failed to execute correctly + build_pacman --noconfirm -S mingw-w64-"${ARCH}"-python3-pip + + PIP_REQUIREMENTS="\ + pycairo==1.18.0 + PyGObject==3.30.4 + gaphas==1.0.0 + zope.component==4.5 + tomlkit==0.5.3 + " + + build_pip install $(echo "$PIP_REQUIREMENTS" | tr ["\\n"] [" "]) + +} + +function get_version { + python3 - <> "$BUILDPY" + echo "BUILD_VERSION = $BUILD_VERSION" >> "$BUILDPY" + (cd "$REPO_CLONE" && echo "BUILD_INFO = u\"$(git rev-parse --short HEAD)\"" >> "$BUILDPY") + build_compileall -d "" -q -f "$BUILDPY" + + cp misc/gaphor.ico "${BUILD_ROOT}" + (cd "${MINGW_ROOT}" && makensis -NOCD -DVERSION="$VERSION_DESC" "${MISC}"/win_installer.nsi) + + mv "${MINGW_ROOT}/gaphor-LATEST.exe" "$DIR/gaphor-$VERSION_DESC-installer.exe" +} + +function build_portable_installer { + cp "${REPO_CLONE}"/win-installer/build.py "$BUILDPY" + echo 'BUILD_TYPE = u"windows-portable"' >> "$BUILDPY" + echo "BUILD_VERSION = $BUILD_VERSION" >> "$BUILDPY" + (cd "$REPO_CLONE" && echo "BUILD_INFO = u\"$(git rev-parse --short HEAD)\"" >> "$BUILDPY") + build_compileall -d "" -q -f "$BUILDPY" + + local PORTABLE="$DIR/gaphor-$VERSION_DESC-portable" + + rm -rf "$PORTABLE" + mkdir "$PORTABLE" + cp "$MISC"/gaphor.lnk "$PORTABLE" + cp "$MISC"/README-PORTABLE.txt "$PORTABLE"/README.txt + unix2dos "$PORTABLE"/README.txt + mkdir "$PORTABLE"/config + cp -RT "${MINGW_ROOT}" "$PORTABLE"/data + + rm -Rf 7zout 7z1900-x64.exe + 7z a payload.7z "$PORTABLE" + wget.exe -P "$DIR" -c https://www.7-zip.org/a/7z1900-x64.exe + 7z x -o7zout 7z1900-x64.exe + cat 7zout/7z.sfx payload.7z > "$PORTABLE".exe + rm -Rf 7zout 7z1900-x64.exe payload.7z "$PORTABLE" +} diff --git a/win-installer/bootstrap.py b/win-installer/bootstrap.py new file mode 100644 index 000000000..5113dc6c0 --- /dev/null +++ b/win-installer/bootstrap.py @@ -0,0 +1,21 @@ +import subprocess + + +# Update pacman packages +subprocess.run("pacman -Suy") + +# Install PyGObject Development Environment +subprocess.run( + "pacman -S --needed --noconfirm" + "base-devel" + "mingw-w64-x68_64-toolchain" + "git" + "mingw-w64-x68_64-python3" + "mingw-w64-x68_64-python3-cairo" + "mingw-w64-x68_64-gobject-gobject" + "mingw-w64-x68_64-python3-pip" + "mingw-w64-x68_64-setuptools" +) + +# Install Gaphor dependencies +subprocess.run("pip3 install --user -U" "gaphas" "zope.component" "pycairo" "PyGObject") diff --git a/win-installer/build.py b/win-installer/build.py new file mode 100644 index 000000000..0f4ed5903 --- /dev/null +++ b/win-installer/build.py @@ -0,0 +1,17 @@ +# Copyright 2016 Christoph Reiter, 2019 Dan Yeaw +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +"""This file gets edited at build time to add build specific data""" + +BUILD_TYPE = u"default" +"""Either 'windows', 'windows-portable', 'osx-gaphor', or 'default'""" + +BUILD_INFO = u"" +"""Additional build info like git revision etc""" + +BUILD_VERSION = 0 +"""1.2.3 with a BUILD_VERSION of 1 results in 1.2.3.1""" diff --git a/win-installer/build.sh b/win-installer/build.sh new file mode 100644 index 000000000..d2a39dcac --- /dev/null +++ b/win-installer/build.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Copyright 2016 Christoph Reiter, 2019 Dan Yeaw +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +DIR="$( cd "$( dirname "$0" )" && pwd )" +source "$DIR"/_base.sh + +function main { + local GIT_TAG=${1:-"master"} + + if [[ -d "${BUILD_ROOT}" ]]; then + echo "Removing ${BUILD_ROOT}" + rm -rf "${BUILD_ROOT}" + fi + + # started from the wrong env -> switch + if [ $(echo "$MSYSTEM" | tr '[A-Z]' '[a-z]') != "$MINGW" ]; then + "/${MINGW}.exe" "$0" + exit $? + fi + + echo "install pre-dependencies" + install_pre_deps + echo "create root" + create_root + echo "install dependencies" + install_deps + echo "cleanup before installing gaphor" + cleanup_before + echo "install gaphor" + install_gaphor "$GIT_TAG" + echo "cleanup" + cleanup_after + echo "build installer" + build_installer + echo "build portable installer" + build_portable_installer +} + +main "$@"; diff --git a/win-installer/misc/README-PORTABLE.txt b/win-installer/misc/README-PORTABLE.txt new file mode 100644 index 000000000..702361ecd --- /dev/null +++ b/win-installer/misc/README-PORTABLE.txt @@ -0,0 +1,17 @@ +=============== +Gaphor Portable +=============== + +Content +------- + +* 'config' contains all user configuration +* 'data' contains the program + + +How to update to a newer version? +--------------------------------- + +1) Download and extract the new version +2) Replace the 'config' folder in the new version with the 'config' folder + of the older version. diff --git a/win-installer/misc/build-ico.sh b/win-installer/misc/build-ico.sh new file mode 100644 index 000000000..8a4618521 --- /dev/null +++ b/win-installer/misc/build-ico.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -e + +../_build_root/mingw64/bin/magick convert ../../gaphor/ui/pixmaps/gaphor-48x48.png gaphor2.ico diff --git a/win-installer/misc/create-launcher.py b/win-installer/misc/create-launcher.py new file mode 100644 index 000000000..0c8ffad8a --- /dev/null +++ b/win-installer/misc/create-launcher.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright 2016 Christoph Reiter, 2019 Dan Yeaw +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +"""Creates simple Python .exe launchers for gui and cli apps + +./create-launcher.py "3.8.0" +""" + +import os +import re +import sys +import subprocess +import shlex +import tempfile +import shutil +import struct + + +def build_resource(rc_path, out_path): + """Raises subprocess.CalledProcessError""" + + def is_64bit(): + return struct.calcsize("P") == 8 + + subprocess.check_call( + [ + "windres", + "-O", + "coff", + "-F", + "pe-x86-64" if is_64bit() else "pe-i386", + rc_path, + "-o", + out_path, + ] + ) + + +def get_build_args(): + python_name = os.path.splitext(os.path.basename(sys.executable))[0] + python_config = os.path.join( + os.path.dirname(sys.executable), python_name + "-config" + ) + + cflags = subprocess.check_output(["sh", python_config, "--cflags"]).strip() + libs = subprocess.check_output(["sh", python_config, "--libs"]).strip() + + cflags = os.fsdecode(cflags) + libs = os.fsdecode(libs) + return shlex.split(cflags) + shlex.split(libs) + + +def build_exe(source_path, resource_path, is_gui, out_path): + args = ["gcc", "-s"] + if is_gui: + args.append("-mwindows") + args.append("-municode") + args.extend(["-o", out_path, source_path, resource_path]) + args.extend(get_build_args()) + subprocess.check_call(args) + + +def get_launcher_code(entry_point): + module, func = entry_point.split(":", 1) + + template = """\ +#include "Python.h" +#define WIN32_LEAN_AND_MEAN +#include + +int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + PWSTR lpCmdLine, int nCmdShow) +{ + int result; + + Py_NoUserSiteDirectory = 1; + Py_IgnoreEnvironmentFlag = 1; + Py_DontWriteBytecodeFlag = 1; + Py_Initialize(); + PySys_SetArgvEx(__argc, __wargv, 0); + result = PyRun_SimpleString("%s"); + Py_Finalize(); + return result; +} + """ + + launch_code = "import sys; from %s import %s; sys.exit(%s())" % (module, func, func) + return template % launch_code + + +def get_resouce_code( + filename, + file_version, + file_desc, + icon_path, + product_name, + product_version, + company_name, +): + + template = """\ +1 ICON "%(icon_path)s" +1 VERSIONINFO +FILEVERSION %(file_version_list)s +PRODUCTVERSION %(product_version_list)s +FILEOS 0x4 +FILETYPE 0x1 +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "CompanyName", "%(company_name)s" + VALUE "FileDescription", "%(file_desc)s" + VALUE "FileVersion", "%(file_version)s" + VALUE "InternalName", "%(internal_name)s" + VALUE "OriginalFilename", "%(filename)s" + VALUE "ProductName", "%(product_name)s" + VALUE "ProductVersion", "%(product_version)s" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END +""" + + def to_ver_list(v): + v = re.sub(r"rc\d", "", v) + return ",".join(map(str, (list(map(int, v.split("."))) + [0] * 4)[:4])) + + file_version_list = to_ver_list(file_version) + product_version_list = to_ver_list(product_version) + + return template % { + "icon_path": icon_path, + "file_version_list": file_version_list, + "product_version_list": product_version_list, + "file_version": file_version, + "product_version": product_version, + "company_name": company_name, + "filename": filename, + "internal_name": os.path.splitext(filename)[0], + "product_name": product_name, + "file_desc": file_desc, + } + + +def build_launcher( + out_path, + icon_path, + file_desc, + product_name, + product_version, + company_name, + entry_point, + is_gui, +): + + src_ico = os.path.abspath(icon_path) + target = os.path.abspath(out_path) + + file_version = product_version + + dir_ = os.getcwd() + temp = tempfile.mkdtemp() + try: + os.chdir(temp) + with open("launcher.c", "w") as h: + h.write(get_launcher_code(entry_point)) + shutil.copyfile(src_ico, "launcher.ico") + with open("launcher.rc", "w") as h: + h.write( + get_resouce_code( + os.path.basename(target), + file_version, + file_desc, + "launcher.ico", + product_name, + product_version, + company_name, + ) + ) + + build_resource("launcher.rc", "launcher.res") + build_exe("launcher.c", "launcher.res", is_gui, target) + finally: + os.chdir(dir_) + shutil.rmtree(temp) + + +def main(): + argv = sys.argv + + version = argv[1] + target = argv[2] + + company_name = "Gaphor Community" + misc = os.path.dirname(os.path.realpath(__file__)) + + build_launcher( + os.path.join(target, "gaphor.exe"), + os.path.join(misc, "gaphor.ico"), + "Gaphor", + "Gaphor", + version, + company_name, + "gaphor:main", + True, + ) + + build_launcher( + os.path.join(target, "gaphor-cmd.exe"), + os.path.join(misc, "gaphor.ico"), + "Gaphor", + "Gaphor", + version, + company_name, + "gaphor:main", + False, + ) + + +if __name__ == "__main__": + main() diff --git a/win-installer/misc/depcheck.py b/win-installer/misc/depcheck.py new file mode 100644 index 000000000..9984fe95d --- /dev/null +++ b/win-installer/misc/depcheck.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright 2016,2017 Christoph Reiter +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +""" +Deletes unneeded DLLs and checks DLL dependencies. +Execute with the build python, will figure out the rest. +""" + +import subprocess +import os +import sys +from multiprocessing import Process, Queue + +import gi + +gi.require_version("GIRepository", "2.0") +from gi.repository import GIRepository + + +def _get_shared_libraries(q, namespace, version): + repo = GIRepository.Repository() + repo.require(namespace, version, 0) + lib = repo.get_shared_library(namespace) + q.put(lib) + + +def get_shared_libraries(namespace, version): + # we have to start a new process because multiple versions can't be loaded + # in the same process + q = Queue() + p = Process(target=_get_shared_libraries, args=(q, namespace, version)) + p.start() + result = q.get() + p.join() + return result + + +def get_required_by_typelibs(): + deps = set() + repo = GIRepository.Repository() + for tl in os.listdir(repo.get_search_path()[0]): + namespace, version = os.path.splitext(tl)[0].split("-", 1) + lib = get_shared_libraries(namespace, version) + if lib: + libs = lib.lower().split(",") + else: + libs = [] + for lib in libs: + deps.add((namespace, version, lib)) + return deps + + +def get_dependencies(filename): + deps = [] + try: + data = subprocess.check_output( + ["objdump", "-p", filename], stderr=subprocess.STDOUT + ) + except subprocess.CalledProcessError: + # can happen with wrong arch binaries + return [] + data = data.decode("utf-8") + for line in data.splitlines(): + line = line.strip() + if line.startswith("DLL Name:"): + deps.append(line.split(":", 1)[-1].strip().lower()) + return deps + + +def find_lib(root, name): + system_search_path = os.path.join("C:", os.sep, "Windows", "System32") + if get_lib_path(root, name): + return True + elif os.path.exists(os.path.join(system_search_path, name)): + return True + elif name in ["gdiplus.dll"]: + return True + elif name.startswith("msvcr"): + return True + return False + + +def get_lib_path(root, name): + search_path = os.path.join(root, "bin") + if os.path.exists(os.path.join(search_path, name)): + return os.path.join(search_path, name) + + +def get_things_to_delete(root): + extensions = [".exe", ".pyd", ".dll"] + + all_libs = set() + needed = set() + for base, dirs, files in os.walk(root): + for f in files: + lib = f.lower() + path = os.path.join(base, f) + ext_lower = os.path.splitext(f)[-1].lower() + if ext_lower in extensions: + if ext_lower == ".exe": + # we use .exe as dependency root + needed.add(lib) + all_libs.add(f.lower()) + for lib in get_dependencies(path): + all_libs.add(lib) + needed.add(lib) + if not find_lib(root, lib): + print("MISSING:", path, lib) + + for namespace, version, lib in get_required_by_typelibs(): + all_libs.add(lib) + needed.add(lib) + if not find_lib(root, lib): + print("MISSING:", namespace, version, lib) + + to_delete = [] + for not_depended_on in all_libs - needed: + path = get_lib_path(root, not_depended_on) + if path: + to_delete.append(path) + + return to_delete + + +def main(argv): + libs = get_things_to_delete(sys.prefix) + + if "--delete" in argv[1:]: + while libs: + for l in libs: + print("DELETE:", l) + os.unlink(l) + libs = get_things_to_delete(sys.prefix) + + +if __name__ == "__main__": + main(sys.argv) diff --git a/win-installer/misc/gaphor.ico b/win-installer/misc/gaphor.ico new file mode 100644 index 000000000..52e5039e5 Binary files /dev/null and b/win-installer/misc/gaphor.ico differ diff --git a/win-installer/misc/gaphor.lnk b/win-installer/misc/gaphor.lnk new file mode 100644 index 000000000..7e4afb3ca Binary files /dev/null and b/win-installer/misc/gaphor.lnk differ diff --git a/win-installer/misc/win_installer.nsi b/win-installer/misc/win_installer.nsi new file mode 100644 index 000000000..2a19b22c9 --- /dev/null +++ b/win-installer/misc/win_installer.nsi @@ -0,0 +1,185 @@ +; Copyright 2016 Christoph Reiter, 2019 Dan Yeaw +; +; This program is free software; you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation; either version 2 of the License, or +; (at your option) any later version. + +Unicode true + +!define NAME "Gaphor" +!define ID "gaphor" +!define DESC "Modeling Tool" + +!define WEBSITE "https://gaphor.readthedocs.io" + +!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${NAME}" +!define INSTDIR_KEY "Software\${NAME}" +!define INSTDIR_VALUENAME "InstDir" + +!define MUI_CUSTOMFUNCTION_GUIINIT custom_gui_init +!include "MUI2.nsh" +!include "FileFunc.nsh" + +Name "${NAME} (${VERSION})" +OutFile "gaphor-LATEST.exe" +SetCompressor /SOLID /FINAL lzma +SetCompressorDictSize 32 +InstallDir "$PROGRAMFILES\${NAME}" +RequestExecutionLevel admin + +Var INST_BIN +Var UNINST_BIN + +!define MUI_ABORTWARNING +!define MUI_ICON "..\gaphor.ico" + +!insertmacro MUI_PAGE_LICENSE "..\gaphor\LICENSE.txt" +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES + +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES + +!insertmacro MUI_LANGUAGE "English" +!insertmacro MUI_LANGUAGE "Catalan" +!insertmacro MUI_LANGUAGE "Dutch" +!insertmacro MUI_LANGUAGE "French" +!insertmacro MUI_LANGUAGE "PortugueseBR" +!insertmacro MUI_LANGUAGE "Russian" +!insertmacro MUI_LANGUAGE "Spanish" +!insertmacro MUI_LANGUAGE "Swedish" + + +Section "Install" + SetShellVarContext all + + ; Use this to make things faster for testing installer changes + ;~ SetOutPath "$INSTDIR\bin" + ;~ File /r "mingw32\bin\*.exe" + + SetOutPath "$INSTDIR" + File /r "*.*" + + StrCpy $INST_BIN "$INSTDIR\bin\gaphor.exe" + StrCpy $UNINST_BIN "$INSTDIR\uninstall.exe" + + ; Store installation folder + WriteRegStr HKLM "${INSTDIR_KEY}" "${INSTDIR_VALUENAME}" $INSTDIR + + ; Set up an entry for the uninstaller + WriteRegStr HKLM "${UNINST_KEY}" \ + "DisplayName" "${NAME} - ${DESC}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$\"$INST_BIN$\"" + WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" \ + "$\"$UNINST_BIN$\"" + WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" \ + "$\"$UNINST_BIN$\" /S" + WriteRegStr HKLM "${UNINST_KEY}" "InstallLocation" "$INSTDIR" + WriteRegStr HKLM "${UNINST_KEY}" "HelpLink" "${WEBSITE}" + WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${NAME} Community" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${VERSION}" + WriteRegDWORD HKLM "${UNINST_KEY}" "NoModify" 0x1 + WriteRegDWORD HKLM "${UNINST_KEY}" "NoRepair" 0x1 + ; Installation size + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" + + ; Register a default entry for file extension + WriteRegStr HKLM "Software\Classes\${NAME}.ModelFile\open\command" "" "$\"$INST_BIN$\"" + WriteRegStr HKLM "Software\Classes\${NAME}.ModelFile\DefaultIcon" "" "$\"$INST_BIN$\"" + + ; Register supported file extension + WriteRegStr HKLM "Software\Classes\.${ID}" ".gaphor" "${NAME}.ModelFile" + + ; Add application entry + WriteRegStr HKLM "Software\${NAME}\${ID}\Capabilities" "ApplicationDescription" "${DESC}" + WriteRegStr HKLM "Software\${NAME}\${ID}\Capabilities" "ApplicationName" "${NAME}" + + ; Register application entry + WriteRegStr HKLM "Software\RegisteredApplications" "${NAME}" "Software\${NAME}\${ID}\Capabilities" + + ; Register app paths + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\App Paths\gaphor.exe" "" "$INST_BIN" + + ; Create uninstaller + WriteUninstaller "$UNINST_BIN" + + ; Create start menu shortcuts + CreateDirectory "$SMPROGRAMS\${NAME}" + CreateShortCut "$SMPROGRAMS\${NAME}\${NAME}.lnk" "$INST_BIN" +SectionEnd + +Function custom_gui_init + BringToFront + + ; Read the install dir and set it + Var /GLOBAL instdir_temp + Var /GLOBAL uninst_bin_temp + + SetRegView 32 + ReadRegStr $instdir_temp HKLM "${INSTDIR_KEY}" "${INSTDIR_VALUENAME}" + SetRegView lastused + StrCmp $instdir_temp "" skip 0 + StrCpy $INSTDIR $instdir_temp + skip: + + SetRegView 64 + ReadRegStr $instdir_temp HKLM "${INSTDIR_KEY}" "${INSTDIR_VALUENAME}" + SetRegView lastused + StrCmp $instdir_temp "" skip2 0 + StrCpy $INSTDIR $instdir_temp + skip2: + + StrCpy $uninst_bin_temp "$INSTDIR\uninstall.exe" + + ; try to un-install existing installations first + IfFileExists "$INSTDIR" do_uninst do_continue + do_uninst: + ; instdir exists + IfFileExists "$uninst_bin_temp" exec_uninst rm_instdir + exec_uninst: + ; uninstall.exe exists, execute it and + ; if it returns success proceede, otherwise abort the + ; installer (uninstall aborted by user for example) + ExecWait '"$uninst_bin_temp" _?=$INSTDIR' $R1 + ; uninstall suceeded, since the uninstall.exe is still there + ; goto rm_instdir as well + StrCmp $R1 0 rm_instdir + ; uninstall failed + Abort + rm_instdir: + ; either the uninstaller was sucessfull or + ; the uninstaller.exe wasn't found + RMDir /r "$INSTDIR" + do_continue: + ; the instdir shouldn't exist from here on + + BringToFront +FunctionEnd + +Section "Uninstall" + SetShellVarContext all + SetAutoClose true + + ; Remove start menu entries + Delete "$SMPROGRAMS\${NAME}\${NAME}.lnk" + RMDir "$SMPROGRAMS\${NAME}" + + ; Remove application registration and file assocs + DeleteRegKey HKLM "Software\Classes\${NAME}.ModelFile" + DeleteRegKey HKLM "Software\.${ID}" + DeleteRegKey HKLM "Software\${NAME}" + DeleteRegValue HKLM "Software\RegisteredApplications" "${NAME}" + + ; Remove app paths + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\App Paths\gaphor.exe" + + ; Delete installation related keys + DeleteRegKey HKLM "${UNINST_KEY}" + DeleteRegKey HKLM "${INSTDIR_KEY}" + + ; Delete files + RMDir /r "$INSTDIR" +SectionEnd diff --git a/win-installer/rebuild.sh b/win-installer/rebuild.sh new file mode 100644 index 000000000..2d0db9a87 --- /dev/null +++ b/win-installer/rebuild.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Copyright 2016 Christoph Reiter, 2019 Dan Yeaw +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +DIR="$( cd "$( dirname "$0" )" && pwd )" +source "$DIR"/_base.sh + +set_build_root "${DIR}/_rebuild_root" + +function main { + local INSTALLER_PATH=${1} + local GIT_TAG=${2:-"master"} + + [[ -d "${BUILD_ROOT}" ]] && (echo "${BUILD_ROOT} already exists"; exit 1) + + # started from the wrong env -> switch + if [ $(echo "$MSYSTEM" | tr '[A-Z]' '[a-z]') != "$MINGW" ]; then + "/${MINGW}.exe" "$0" + exit $? + fi + + echo "install pre-dependencies" + install_pre_deps + echo "create root" + create_root + echo "extract installer" + extract_installer "$INSTALLER_PATH" + echo "cleanup before installing gaphor" + cleanup_before + echo "install gaphor" + install_gaphor "$GIT_TAG" + echo "cleanup" + cleanup_after + echo "build installer" + build_installer + echo "build portable installer" + build_portable_installer +} + +main "$@";