From 24ef24cc1414866ed946096db2f0d707eb7acb33 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Thu, 15 Sep 2022 09:54:42 -0700 Subject: [PATCH 01/43] Create experimental macOS package installer. Adapted from napari/napari-packaging --- installers-exp/build_installers.py | 479 ++++++++++++++++++ installers-exp/menuinst_config.json | 32 ++ installers-exp/post-install.sh | 54 ++ installers-exp/pre-install.sh | 36 ++ installers-exp/resources/bundle_license.rtf | 31 ++ installers-exp/resources/bundle_license.txt | 33 ++ installers-exp/resources/bundle_readme.md | 57 +++ .../resources/osx_pkg_welcome.rtf.tmpl | 15 + installers-exp/spy-inst-env.yml | 8 + installers-exp/spyder-app/bld.bat | 14 + installers-exp/spyder-app/build.sh | 7 + installers-exp/spyder-app/meta.yaml | 119 +++++ installers-exp/spyder-app/osx-zmq.patch | 14 + installers-exp/spyder-menu/meta.yaml | 41 ++ 14 files changed, 940 insertions(+) create mode 100644 installers-exp/build_installers.py create mode 100644 installers-exp/menuinst_config.json create mode 100755 installers-exp/post-install.sh create mode 100755 installers-exp/pre-install.sh create mode 100644 installers-exp/resources/bundle_license.rtf create mode 100644 installers-exp/resources/bundle_license.txt create mode 100644 installers-exp/resources/bundle_readme.md create mode 100644 installers-exp/resources/osx_pkg_welcome.rtf.tmpl create mode 100644 installers-exp/spy-inst-env.yml create mode 100644 installers-exp/spyder-app/bld.bat create mode 100644 installers-exp/spyder-app/build.sh create mode 100644 installers-exp/spyder-app/meta.yaml create mode 100644 installers-exp/spyder-app/osx-zmq.patch create mode 100644 installers-exp/spyder-menu/meta.yaml diff --git a/installers-exp/build_installers.py b/installers-exp/build_installers.py new file mode 100644 index 00000000000..99bbf2883e0 --- /dev/null +++ b/installers-exp/build_installers.py @@ -0,0 +1,479 @@ +""" +Create Spyder installers using `constructor`. + +It creates a `construct.yaml` file with the needed settings +and then runs `constructor`. + +Some environment variables we use: + +CONSTRUCTOR_APP_NAME: + in case you want to build a non-default distribution that is not + named `spyder` +CONSTRUCTOR_INSTALLER_DEFAULT_PATH_STEM: + The last component of the default installation path. Defaults to + {CONSTRUCTOR_APP_NAME}-{_version()} +CONSTRUCTOR_INSTALLER_VERSION: + Version for the installer, separate from the app being installed. + This will have an effect on the default install locations in future + releases. +CONSTRUCTOR_TARGET_PLATFORM: + conda-style platform (as in `platform` in `conda info -a` output) +CONSTRUCTOR_USE_LOCAL: + whether to use the local channel (populated by `conda-build` actions) +CONSTRUCTOR_CONDA_EXE: + when the target platform is not the same as the host, constructor + needs a path to a conda-standalone (or micromamba) executable for + that platform. needs to be provided in this env var in that case! +CONSTRUCTOR_SIGNING_IDENTITY: + Apple ID Installer Certificate identity (common name) that should + be use to productsign the resulting PKG (macOS only) +CONSTRUCTOR_NOTARIZATION_IDENTITY: + Apple ID Developer Certificate identity (common name) that should + be use to codesign some binaries bundled in the pkg (macOS only) +CONSTRUCTOR_SIGNING_CERTIFICATE: + Path to PFX certificate to sign the EXE installer on Windows +CONSTRUCTOR_PFX_CERTIFICATE_PASSWORD: + Password to unlock the PFX certificate. This is not used here but + it might be needed by constructor. +""" + +import json +import os +import platform +import sys +import zipfile +from argparse import ArgumentParser +from distutils.spawn import find_executable +from pathlib import Path +from tempfile import NamedTemporaryFile +from textwrap import dedent, indent +from functools import lru_cache, partial +from subprocess import check_call, check_output + +from ruamel.yaml import YAML + +# import spyder + +yaml = YAML() +yaml.indent(mapping=2) +indent4 = partial(indent, prefix=" ") + +APP = os.environ.get("CONSTRUCTOR_APP_NAME", "Spyder") +# bump this when something in the installer infrastructure changes +# note that this will affect the default installation path across platforms! +INSTALLER_VERSION = os.environ.get("CONSTRUCTOR_INSTALLER_VERSION", "0.1") +SPYREPO = Path(__file__).parent.parent +WINDOWS = os.name == "nt" +MACOS = sys.platform == "darwin" +LINUX = sys.platform.startswith("linux") +if os.environ.get("CONSTRUCTOR_TARGET_PLATFORM") == "osx-arm64": + ARCH = "arm64" +else: + ARCH = (platform.machine() or "generic").lower().replace("amd64", "x86_64") +TARGET_PLATFORM = os.environ.get("CONSTRUCTOR_TARGET_PLATFORM") +PY_VER = f"{sys.version_info.major}.{sys.version_info.minor}" +if WINDOWS: + EXT, OS = "exe", "Windows" +elif LINUX: + EXT, OS = "sh", "Linux" +elif MACOS: + EXT, OS = "pkg", "macOS" +else: + raise RuntimeError(f"Unrecognized OS: {sys.platform}") + + +def _use_local(): + """ + Detect whether we need to build Spyder locally + (dev snapshots). This env var is set in the GHA workflow. + """ + return os.environ.get("CONSTRUCTOR_USE_LOCAL") + + +@lru_cache +def _version(): + from importlib.util import spec_from_file_location, module_from_spec + spec = spec_from_file_location("spyder", SPYREPO / "spyder" / "__init__.py") + mod = module_from_spec(spec) + spec.loader.exec_module(mod) + return mod.__version__ + + +OUTPUT_FILENAME = f"{APP}-{_version()}-{OS}-{ARCH}.{EXT}" +INSTALLER_DEFAULT_PATH_STEM = os.environ.get( + "CONSTRUCTOR_INSTALLER_DEFAULT_PATH_STEM", f"{APP}-{_version()}" +) +clean_these_files = [] + + +def _generate_background_images(installer_type, outpath="resources"): + """Requires pillow""" + if installer_type == "sh": + # shell installers are text-based, no graphics + return + + from PIL import Image + + logo_path = SPYREPO / "img_src" / "spyder.png" + logo = Image.open(logo_path, "r") + + global clean_these_files + + if installer_type in ("exe", "all"): + sidebar = Image.new("RGBA", (164, 314), (0, 0, 0, 0)) + sidebar.paste(logo.resize((101, 101)), (32, 180)) + output = Path(outpath, "spyder_164x314.png") + sidebar.save(output, format="png") + clean_these_files.append(output) + + banner = Image.new("RGBA", (150, 57), (0, 0, 0, 0)) + banner.paste(logo.resize((44, 44)), (8, 6)) + output = Path(outpath, "spyder_150x57.png") + banner.save(output, format="png") + clean_these_files.append(output) + + if installer_type in ("pkg", "all"): + background = Image.new("RGBA", (1227, 600), (0, 0, 0, 0)) + background.paste(logo.resize((148, 148)), (95, 418)) + output = Path(outpath, "spyder_1227x600.png") + background.save(output, format="png") + clean_these_files.append(output) + + +def _get_condarc(): + # we need defaults for tensorflow and others on windows only + defaults = "- defaults" if WINDOWS else "" + prompt = "[spyder]({default_env}) " + contents = dedent( + f""" + channels: #!final + - spyder-ide + - conda-forge + {defaults} + repodata_fns: #!final + - repodata.json + auto_update_conda: false #!final + notify_outdated_conda: false #!final + channel_priority: strict #!final + env_prompt: '{prompt}' #! final + """ + ) + # the undocumented #!final comment is explained here + # https://www.anaconda.com/blog/conda-configuration-engine-power-users + with NamedTemporaryFile(delete=False, mode="w+") as f: + f.write(contents) + return f.name + + +def _base_env(python_version=PY_VER): + return { + "name": "base", + "channels": [ + "napari/label/bundle_tools", + "spyder-ide", + "conda-forge", + ], + "specs": [ + "python", + "conda", + "mamba", + "pip", + ], + } + + +def _spyder_env( + python_version=PY_VER, + spyder_version=_version(), + extra_specs=None, +): + return { + "name": f"spyder-{spyder_version}", + # "channels": same as _base_env(), omit to inherit :) + "specs": [ + f"spyder={spyder_version}", + f"spyder-menu={spyder_version}", + # "conda", + # "mamba", + # "pip", + ] + + (extra_specs or []), + # "exclude": exclude, # TODO: not supported yet in constructor + } + + +def _definitions(version=_version(), extra_specs=None, spy_repo=SPYREPO): + resources = spy_repo / "installers-exp" / "resources" + base_env = _base_env() + spyder_env = _spyder_env(spyder_version=version, extra_specs=extra_specs) + condarc = _get_condarc() + definitions = { + "name": APP, + "company": "Spyder-IDE", + "reverse_domain_identifier": "org.spyder-ide.Spyder", + "version": version.replace("+", "_"), + "channels": base_env["channels"], + "conda_default_channels": ["conda-forge"], + "installer_filename": OUTPUT_FILENAME, + "initialize_by_default": False, + "license_file": str(resources / "bundle_license.rtf"), + "specs": base_env["specs"], + "extra_envs": { + spyder_env["name"]: { + "specs": spyder_env["specs"], + }, + }, + "menu_packages": [ + "spyder-menu", + ], + "extra_files": { + str(resources / "bundle_readme.md"): "README.txt", + condarc: ".condarc", + }, + } + if _use_local(): + definitions["channels"].insert(0, "local") + if LINUX: + definitions["default_prefix"] = os.path.join( + "$HOME", ".local", INSTALLER_DEFAULT_PATH_STEM + ) + definitions["license_file"] = str(resources / "bundle_license.txt") + definitions["installer_type"] = "sh" + + if MACOS: + # These two options control the default install location: + # ~// + definitions["pkg_name"] = INSTALLER_DEFAULT_PATH_STEM + definitions["default_location_pkg"] = "Library" + definitions["installer_type"] = "pkg" + definitions["welcome_image"] = str(resources / "spyder_1227x600.png") + welcome_text_tmpl = (resources / "osx_pkg_welcome.rtf.tmpl").read_text() + welcome_file = resources / "osx_pkg_welcome.rtf" + clean_these_files.append(welcome_file) + welcome_file.write_text(welcome_text_tmpl.replace("__VERSION__", version)) + definitions["welcome_file"] = str(welcome_file) + definitions["conclusion_text"] = "" + definitions["readme_text"] = "" + definitions["post_install"] = str(spy_repo / "installers-exp" / "post-install.sh") + signing_identity = os.environ.get("CONSTRUCTOR_SIGNING_IDENTITY") + if signing_identity: + definitions["signing_identity_name"] = signing_identity + notarization_identity = os.environ.get("CONSTRUCTOR_NOTARIZATION_IDENTITY") + if notarization_identity: + definitions["notarization_identity_name"] = notarization_identity + + if WINDOWS: + definitions["conda_default_channels"].append("defaults") + definitions.update( + { + "welcome_image": str(resources / "spyder_164x314.png"), + "header_image": str(resources / "spyder_150x57.png"), + "icon_image": str(spy_repo / "napari" / "resources" / "icon.ico"), + "register_python_default": False, + "default_prefix": os.path.join( + "%LOCALAPPDATA%", INSTALLER_DEFAULT_PATH_STEM + ), + "default_prefix_domain_user": os.path.join( + "%LOCALAPPDATA%", INSTALLER_DEFAULT_PATH_STEM + ), + "default_prefix_all_users": os.path.join( + "%ALLUSERSPROFILE%", INSTALLER_DEFAULT_PATH_STEM + ), + "check_path_length": False, + "installer_type": "exe", + } + ) + signing_certificate = os.environ.get("CONSTRUCTOR_SIGNING_CERTIFICATE") + if signing_certificate: + definitions["signing_certificate"] = signing_certificate + + if definitions.get("welcome_image") or definitions.get("header_image"): + _generate_background_images( + definitions.get("installer_type", "all"), + outpath=resources, + ) + + clean_these_files.append("construct.yaml") + clean_these_files.append(condarc) + + return definitions + + +def _constructor(version=_version(), extra_specs=None, spy_repo=SPYREPO): + """ + Create a temporary `construct.yaml` input file and + run `constructor`. + + Parameters + ---------- + version: str + Version of `napari` to be built. Defaults to the + one detected by `importlib` from the source code. + extra_specs: list of str + Additional packages to be included in the installer. + A list of conda spec strings (`numpy`, `python=3`, etc) + is expected. + spy_repo: str + location where the spyder-ide/spyder repository was cloned + """ + constructor = find_executable("constructor") + if not constructor: + raise RuntimeError("Constructor must be installed and in PATH.") + + # TODO: temporarily patching password - remove block when the secret has been fixed + # (I think it contains an ending newline or something like that, copypaste artifact?) + pfx_password = os.environ.get("CONSTRUCTOR_PFX_CERTIFICATE_PASSWORD") + if pfx_password: + os.environ["CONSTRUCTOR_PFX_CERTIFICATE_PASSWORD"] = pfx_password.strip() + + definitions = _definitions( + version=version, extra_specs=extra_specs, spy_repo=spy_repo + ) + + args = [constructor, "-v", "--debug", "."] + conda_exe = os.environ.get("CONSTRUCTOR_CONDA_EXE") + if TARGET_PLATFORM and conda_exe: + args += ["--platform", TARGET_PLATFORM, "--conda-exe", conda_exe] + env = os.environ.copy() + env["CONDA_CHANNEL_PRIORITY"] = "strict" + + print("+++++++++++++++++") + print("Command:", " ".join(args)) + print("Configuration:") + yaml.dump(definitions, sys.stdout, transform=indent4) + print("\nConda config:\n") + print( + indent4(check_output(["conda", "config", "--show-sources"], text=True, env=env)) + ) + print("Conda info:") + print(indent4(check_output(["conda", "info"], text=True, env=env))) + print("+++++++++++++++++") + + with open("construct.yaml", "w") as fin: + yaml.dump(definitions, fin) + + check_call(args, env=env) + + return OUTPUT_FILENAME + + +def licenses(): + info_path = Path("dist") / "info.json" + try: + with open(info_path) as f: + info = json.load(f) + except FileNotFoundError: + print( + "!! Use `constructor --debug` to write info.json and get licenses", + file=sys.stderr, + ) + raise + + zipname = Path("dist") / f"licenses.{OS}-{ARCH}.zip" + output_zip = zipfile.ZipFile(zipname, mode="w", compression=zipfile.ZIP_DEFLATED) + output_zip.write(info_path) + for package_id, license_info in info["_licenses"].items(): + package_name = package_id.split("::", 1)[1] + for license_type, license_files in license_info.items(): + for i, license_file in enumerate(license_files, 1): + arcname = f"{package_name}.{license_type.replace(' ', '_')}.{i}.txt" + output_zip.write(license_file, arcname=arcname) + output_zip.close() + return zipname.resolve() + + +def main(extra_specs=None, spy_repo=SPYREPO): + try: + cwd = os.getcwd() + workdir = spy_repo / "installers-exp" / "dist" + workdir.mkdir(exist_ok=True) + os.chdir(workdir) + _constructor(extra_specs=extra_specs, spy_repo=spy_repo) + assert Path(OUTPUT_FILENAME).exists(), f"{OUTPUT_FILENAME} was not created!" + finally: + for path in clean_these_files: + try: + os.unlink(path) + except OSError: + print("!! Could not remove", path, file=sys.stderr) + os.chdir(cwd) + return workdir / OUTPUT_FILENAME + + +def cli(argv=None): + p = ArgumentParser(argv) + p.add_argument( + "--version", + action="store_true", + help="Print local Spyder version and exit.", + ) + p.add_argument( + "--installer-version", + action="store_true", + help="Print installer version and exit.", + ) + p.add_argument( + "--arch", + action="store_true", + help="Print machine architecture tag and exit.", + ) + p.add_argument( + "--ext", + action="store_true", + help="Print installer extension for this platform and exit.", + ) + p.add_argument( + "--artifact-name", + action="store_true", + help="Print computed artifact name and exit.", + ) + p.add_argument( + "--extra-specs", + nargs="+", + help="One or more extra conda specs to add to the installer", + ) + p.add_argument( + "--licenses", + action="store_true", + help="Post-process licenses AFTER having built the installer. " + "This must be run as a separate step.", + ) + p.add_argument( + "--images", + action="store_true", + help="Generate background images from the logo (test only)", + ) + p.add_argument( + "--location", + default=SPYREPO, + help="Path to spyder source repository", + type=os.path.abspath, + ) + return p.parse_args() + + +if __name__ == "__main__": + args = cli() + if args.version: + print(_version()) + sys.exit() + if args.installer_version: + print(INSTALLER_VERSION) + sys.exit() + if args.arch: + print(ARCH) + sys.exit() + if args.ext: + print(EXT) + sys.exit() + if args.artifact_name: + print(OUTPUT_FILENAME) + sys.exit() + if args.licenses: + print(licenses()) + sys.exit() + if args.images: + _generate_background_images(spy_repo=args.location) + sys.exit() + + print("Created", main(extra_specs=args.extra_specs, spy_repo=args.location)) diff --git a/installers-exp/menuinst_config.json b/installers-exp/menuinst_config.json new file mode 100644 index 00000000000..befd8373403 --- /dev/null +++ b/installers-exp/menuinst_config.json @@ -0,0 +1,32 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "$id": "https://schemas.conda.io/menuinst-1.schema.json", + "menu_name": "spyder", + "menu_items": [ + { + "name": "Spyder", + "description": "Spyder description", + "icon": "{{ MENU_DIR }}/spyder.{{ ICON_EXT }}", + "command": ["spyder", "$@"], + "activate": true, + "terminal": false, + "platforms": { + "win": { + "desktop": true + }, + "linux": { + "Categories": [ + "Graphics", + "Science" + ] + }, + "osx": { + "CFBundleName": "Spyder", + "CFBundleDisplayName": "Spyder", + "CFBundleIdentifier": "org.spyder-ide.Spyder", + "CFBundleVersion": "__PKG_VERSION__" + } + } + } + ] +} diff --git a/installers-exp/post-install.sh b/installers-exp/post-install.sh new file mode 100755 index 00000000000..a40a996dfca --- /dev/null +++ b/installers-exp/post-install.sh @@ -0,0 +1,54 @@ +#!/bin/bash +set -e + +echo "*** Starting post install script for __NAME__.app" + +cat < $app_path/Contents/MacOS/__NAME__ +#!/bin/bash +eval "\$(/bin/bash -l -c "declare -x")" +eval "\$("$ROOT_PREFIX/_conda.exe" shell.bash activate "$PREFIX")" +export SPYDER_APP=0 +\$(dirname \$BASH_SOURCE)/python $PREFIX/bin/spyder "\$@" + +EOF +else + echo "$app_path does not exist" +fi + +echo "*** Post install script for __NAME__.app complete" diff --git a/installers-exp/pre-install.sh b/installers-exp/pre-install.sh new file mode 100755 index 00000000000..e6e5bc9612e --- /dev/null +++ b/installers-exp/pre-install.sh @@ -0,0 +1,36 @@ +#!/bin/bash +set -e + +echo "*** Starting pre install script for Spyder installer" + +echo "Args = $@" +echo "$(declare -p)" + +# quit the application +echo "Quitting Spyder.app..." +osascript -e 'quit app "Spyder.app"' 2> /dev/null + +PREFIX=$(cd "$2/__NAME_LOWER__"; pwd) +ROOT_PREFIX=$(cd "$PREFIX/../../"; pwd) + +if [[ "$PREFIX" == "$HOME"* ]]; then + # Installed for user + app_path="$HOME/Applications/Spyder.app" +else + # Installed for all users + app_path="/Applications/Spyder.app" +fi + +# Delete the application +if [[ -e "$app_path" ]]; then + echo "Removing $app_path..." + rm -r "$app_path" +fi + +# Delete the environment +if [[ -e "$ROOT_PREFIX" ]]; then + echo "Removing $ROOT_PREFIX" + rm -r "$ROOT_PREFIX" +fi + +echo "*** Pre install script for Spyder installer complete" diff --git a/installers-exp/resources/bundle_license.rtf b/installers-exp/resources/bundle_license.rtf new file mode 100644 index 00000000000..3abe77306e3 --- /dev/null +++ b/installers-exp/resources/bundle_license.rtf @@ -0,0 +1,31 @@ +{\rtf1\ansi\ansicpg1252\cocoartf2580 +\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset0 LucidaGrande;} +{\colortbl;\red255\green255\blue255;} +{\*\expandedcolortbl;;} +{\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{square\}}{\leveltext\leveltemplateid1\'01\uc0\u9642 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid1}} +{\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}} +\margl1440\margr1440\vieww28300\viewh16080\viewkind0 +\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 + +\f0\fs24 \cf0 BSD 3-Clause License\ +\ +Copyright (c) 2018, Napari\ +All rights reserved.\ +\ +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\ +\ +\pard\tx220\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\li720\fi-720\pardirnatural\partightenfactor0 +\ls1\ilvl0\cf0 {\listtext +\f1 \uc0\u9642 +\f0 }Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\ +{\listtext +\f1 \uc0\u9642 +\f0 }Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\ +{\listtext +\f1 \uc0\u9642 +\f0 }Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\ +\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 +\cf0 \ +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\ +\ +This installer bundles other packages, which are distributed under their own license terms. {\field{\*\fldinst{HYPERLINK "https://github.com/napari/napari/blob/latest/EULA.md"}}{\fldrslt Check the full list here}}.} \ No newline at end of file diff --git a/installers-exp/resources/bundle_license.txt b/installers-exp/resources/bundle_license.txt new file mode 100644 index 00000000000..eb5a32d8dc7 --- /dev/null +++ b/installers-exp/resources/bundle_license.txt @@ -0,0 +1,33 @@ +BSD 3-Clause License + +Copyright (c) 2018, Napari +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +This installer bundles other packages, which are distributed under their own +license terms. Check the full list here: +https://github.com/napari/napari/blob/latest/EULA.md diff --git a/installers-exp/resources/bundle_readme.md b/installers-exp/resources/bundle_readme.md new file mode 100644 index 00000000000..b87a8c8b02b --- /dev/null +++ b/installers-exp/resources/bundle_readme.md @@ -0,0 +1,57 @@ +Welcome to the napari installation contents +------------------------------------------- + +This is the base installation of napari, a fast n-dimensional image viewer written in Python. + +## How do I run napari? + +In most cases, you would run it through the platform-specific shortcut we created for your +convenience. In other words, _not_ through this directory! You should be able to see a +`napari (x.y.z)` menu item, where `x.y.z` is the installed version. + +* Linux: check your desktop launcher. +* MacOS: check `~/Applications` or the Launchpad. +* Windows: check the Start Menu or the Desktop. + +We generally recommend using the shortcut because it will pre-activate the `conda` environment for +you! That said, you can also execute the `napari` executable directly from these locations: + +* Linux and macOS: find it under `bin`, next to this file. +* Windows: navigate to `Scripts`, next to this file. + +In unmodified installations, this _should_ be enough to launch `napari`, but sometimes you will +need to activate the `conda` environment to ensure all dependencies are importable. + +## What does `conda` have to do with `napari`? + +The `napari` installer uses `conda` packages to bundle all its dependencies (Python, qt, etc). +This directory is actually a full `conda` installation! If you have used `conda` before, this +is equivalent to what you usually call the `base` environment. + +## Can I modify the `napari` installation? + +Yes. In practice, you can consider it a `conda` environment. You can even activate it as usual, +provided you specify the full path to the location, instead of the _name_. + +``` +# macOS +$ conda activate ~/Library/napari-x.y.z +# Linux +$ conda activate ~/.local/napari-x.y.z +# Windows +$ conda activate %LOCALAPPDATA%/napari-x.y.z +``` + +Then you will be able to run `conda` and `pip` as usual. That said, we advise against this advanced +manipulation. It can render `napari` unusable if not done carefully! You might need to reinstall it +in that case. + +## What is `_conda.exe`? + +This executable is a full `conda` installation, condensed in a single file. It allows us to handle +the installation in a more robust way. It also provides a way to restore destructive changes without +reinstalling anything. Again, consider this an advanced tool only meant for expert debugging. + +## More information + +Check our online documentation at https://napari.org/ diff --git a/installers-exp/resources/osx_pkg_welcome.rtf.tmpl b/installers-exp/resources/osx_pkg_welcome.rtf.tmpl new file mode 100644 index 00000000000..078a165fbc7 --- /dev/null +++ b/installers-exp/resources/osx_pkg_welcome.rtf.tmpl @@ -0,0 +1,15 @@ +{\rtf1\ansi\ansicpg1252\cocoartf2580 +\cocoascreenfonts1\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset0 LucidaGrande;} +{\colortbl;\red255\green255\blue255;\red60\green64\blue68;\red255\green255\blue255;} +{\*\expandedcolortbl;;\cssrgb\c30196\c31765\c33725;\cssrgb\c100000\c100000\c100000;} +\margl1440\margr1440\vieww12040\viewh13780\viewkind0 +\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 + +\f0\fs28 \cf0 Thanks for choosing napari v__VERSION__!\ +\ +{\field{\*\fldinst{HYPERLINK "https://napari.org"}}{\fldrslt napari}} is a fast, interactive, multi-dimensional image viewer for Python. It's designed for browsing, annotating, and analyzing large multi-dimensional images.\ +\ +The installation will begin shortly.\ +\ +If at any point an error is shown, please save the logs (\uc0\u8984+L) before closing the installer and submit the resulting file along with your report in {\field{\*\fldinst{HYPERLINK "https://github.com/napari/napari/issues"}}{\fldrslt our issue tracker}}. Thank you!\ +} \ No newline at end of file diff --git a/installers-exp/spy-inst-env.yml b/installers-exp/spy-inst-env.yml new file mode 100644 index 00000000000..6bf18561b1c --- /dev/null +++ b/installers-exp/spy-inst-env.yml @@ -0,0 +1,8 @@ +name: spy-inst +channels: + - napari/label/bundle_tools + - conda-forge +dependencies: + - boa + - conda-standalone + - constructor diff --git a/installers-exp/spyder-app/bld.bat b/installers-exp/spyder-app/bld.bat new file mode 100644 index 00000000000..6613c9f0a69 --- /dev/null +++ b/installers-exp/spyder-app/bld.bat @@ -0,0 +1,14 @@ +%PYTHON% -m pip install . --no-deps --ignore-installed --no-cache-dir -vvv +if errorlevel 1 exit 1 + +set MENU_DIR=%PREFIX%\Menu +IF NOT EXIST (%MENU_DIR%) mkdir %MENU_DIR% + +copy %SRC_DIR%\img_src\spyder.ico %MENU_DIR%\ +if errorlevel 1 exit 1 +copy %RECIPE_DIR%\menu-windows.json %MENU_DIR%\spyder_shortcut.json +if errorlevel 1 exit 1 + +del %SCRIPTS%\spyder_win_post_install.py +del %SCRIPTS%\spyder.bat +del %SCRIPTS%\spyder diff --git a/installers-exp/spyder-app/build.sh b/installers-exp/spyder-app/build.sh new file mode 100644 index 00000000000..c9370b79642 --- /dev/null +++ b/installers-exp/spyder-app/build.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +$PYTHON -m pip install . --no-deps --ignore-installed --no-cache-dir -vvv + +rm -rf $PREFIX/man +rm -f $PREFIX/bin/spyder_win_post_install.py +rm -rf $SP_DIR/Sphinx-* diff --git a/installers-exp/spyder-app/meta.yaml b/installers-exp/spyder-app/meta.yaml new file mode 100644 index 00000000000..928712eaea1 --- /dev/null +++ b/installers-exp/spyder-app/meta.yaml @@ -0,0 +1,119 @@ +{% set name = "spyder" %} +{% set version = "5.4.0.dev0" %} +{% set build = 0 %} + +package: + name: spyder + version: {{ version }} + +source: + path: /Users/rclary/Documents/Repos/Spyder-IDE/spyder + +patches: + # See spyder-ide/spyder#8316 + - osx-zmq.patch + +build: + number: {{ build }} + entry_points: + - spyder = spyder.app.start:main + +requirements: + build: + - python # [build_platform != target_platform] + - cross-python_{{ target_platform }} # [build_platform != target_platform] + host: + - python + - pip + - setuptools + run: + - python + - applaunchservices >=0.3.0 # [osx] + - atomicwrites >=1.2.0 + - chardet >=2.0.0 + - cloudpickle >=0.5.0 + - cookiecutter >=1.6.0 + - diff-match-patch >=20181111 + - intervaltree >=3.0.2 + - ipython >=7.31.1,<8.0.0 + - jedi >=0.17.2,<0.19.0 + - jellyfish >=0.7 + - jsonschema >=3.2.0 + - keyring >=17.0.0 + - nbconvert >=4.0 + - numpydoc >=0.6.0 + - paramiko >=2.4.0 # [win] + - parso >=0.7.0,<0.9.0 + - pexpect >=4.4.0 + - pickleshare >=0.4 + # This is here to work around a bug in mamba + - ptyprocess >=0.5 # [win] + - psutil >=5.3 + - pygments >=2.0 + - pylint >=2.5.0,<3.0 + - python-lsp-black >=1.2.0 + - pyls-spyder >=0.4.0 + - pyqt >=5.6,<5.16 + - pyqtwebengine <5.16 + - python.app # [osx] + - python-lsp-server >=1.5.0,<1.6.0 + - pyxdg >=0.26 # [linux] + - pyzmq >=22.1.0 + - qdarkstyle >=3.0.2,<3.1.0 + - qstylizer >=0.1.10 + - qtawesome >=1.0.2 + - qtconsole >=5.3.2,<5.4.0 + - qtpy >=2.1.0 + - rtree >=0.9.7 + - setuptools >=49.6.0 + - sphinx >=0.6.6 + - spyder-kernels >=2.3.3,<2.4.0 + - textdistance >=4.2.0 + - three-merge >=0.1.1 + - watchdog >=0.10.3 + # This is here to workaround an inconsistency with flake8 + # See: https://github.com/conda-forge/flake8-feedstock/issues/52 + - importlib-metadata <4.3 # [py < 38] + run_constrained: + - menuinst >=1.4.17 + +# test: +# requires: +# - pip +# commands: +# - USER=test spyder -h # [unix] +# - spyder -h # [win] +# # Pip fails when running but the package is installed correctly +# - python -m pip check # [not aarch64] +# imports: +# - spyder + +about: + home: https://www.spyder-ide.org/ + license: MIT + license_file: LICENSE.txt + summary: The Scientific Python Development Environment + description: | + Spyder is a powerful scientific environment written in Python, for Python, + and designed by and for scientists, engineers and data analysts. + It features a unique combination of the advanced editing, analysis, + debugging and profiling functionality of a comprehensive development tool + with the data exploration, interactive execution, deep inspection and + beautiful visualization capabilities of a scientific package.\n + Furthermore, Spyder offers built-in integration with many popular + scientific packages, including NumPy, SciPy, Pandas, IPython, QtConsole, + Matplotlib, SymPy, and more.\n + Beyond its many built-in features, Spyder can be extended even further via + third-party plugins.\n + Spyder can also be used as a PyQt5 extension library, allowing you to build + upon its functionality and embed its components, such as the interactive + console or advanced editor, in your own software. + doc_url: https://docs.spyder-ide.org/ + dev_url: https://github.com/spyder-ide/spyder + +extra: + recipe-maintainers: + - ccordoba12 + - dalthviz + - andfoy + - steff456 diff --git a/installers-exp/spyder-app/osx-zmq.patch b/installers-exp/spyder-app/osx-zmq.patch new file mode 100644 index 00000000000..83e7fc594b4 --- /dev/null +++ b/installers-exp/spyder-app/osx-zmq.patch @@ -0,0 +1,14 @@ +diff --git a/spyder/app/start.py b/spyder/app/start.py +index bbeac112a..4883c6c47 100644 +--- a/spyder/app/start.py ++++ b/spyder/app/start.py +@@ -18,7 +18,8 @@ import time + + # To prevent a race condition with ZMQ + # See spyder-ide/spyder#5324. +-import zmq ++if not sys.platform == 'darwin': ++ import zmq + + # Load GL library to prevent segmentation faults on some Linux systems + # See spyder-ide/spyder#3226 and spyder-ide/spyder#3332. diff --git a/installers-exp/spyder-menu/meta.yaml b/installers-exp/spyder-menu/meta.yaml new file mode 100644 index 00000000000..d510b0622a2 --- /dev/null +++ b/installers-exp/spyder-menu/meta.yaml @@ -0,0 +1,41 @@ +{% set name = "spyder" %} +{% set version = "5.4.0.dev0" %} +{% set build = 0 %} + +package: + name: spyder-menu + version: {{ version }} + +source: + path: /Users/rclary/Documents/Repos/Spyder-IDE/spyder + +build: + noarch: python + number: {{ build }} + script: + - mkdir -p "${PREFIX}/Menu" + - sed "s/__PKG_VERSION__/{{ PKG_VERSION }}/" "{{ SRC_DIR }}/installers-exp/menuinst_config.json" > "{{ PREFIX }}/Menu/spyder-menu.json" + - cp "{{ SRC_DIR }}/img_src/spyder.png" "{{ PREFIX }}/Menu/spyder.png" + - cp "{{ SRC_DIR }}/img_src/spyder.icns" "{{ PREFIX }}/Menu/spyder.icns" + - cp "{{ SRC_DIR }}/img_src/spyder.ico" "{{ PREFIX }}/Menu/spyder.ico" + +requirements: + build: + - sed # [unix] + run: + - spyder + +# test: +# imports: +# - spyder +# commands: +# - test -f ${CONDA_PREFIX}/Menu/spyder-menu.json + +about: + home: https://www.spyder-ide.org/ + license: MIT + license_file: LICENSE.txt + summary: provides menu icons for Spyder + description: provides menu icons for Spyder + doc_url: https://docs.spyder-ide.org/ + dev_url: https://github.com/spyder-ide/spyder From 0c30c8a2f05b30d11954ae4fcc01656a572f3559 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Thu, 15 Sep 2022 07:41:01 -0700 Subject: [PATCH 02/43] Use white background for pkg logo --- installers-exp/build_installers.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/installers-exp/build_installers.py b/installers-exp/build_installers.py index 99bbf2883e0..9ff39afc20e 100644 --- a/installers-exp/build_installers.py +++ b/installers-exp/build_installers.py @@ -133,8 +133,10 @@ def _generate_background_images(installer_type, outpath="resources"): clean_these_files.append(output) if installer_type in ("pkg", "all"): + _logo = Image.new("RGBA", logo.size, "WHITE") + _logo.paste(logo, mask=logo) background = Image.new("RGBA", (1227, 600), (0, 0, 0, 0)) - background.paste(logo.resize((148, 148)), (95, 418)) + background.paste(_logo.resize((148, 148)), (95, 418)) output = Path(outpath, "spyder_1227x600.png") background.save(output, format="png") clean_these_files.append(output) @@ -268,7 +270,7 @@ def _definitions(version=_version(), extra_specs=None, spy_repo=SPYREPO): { "welcome_image": str(resources / "spyder_164x314.png"), "header_image": str(resources / "spyder_150x57.png"), - "icon_image": str(spy_repo / "napari" / "resources" / "icon.ico"), + "icon_image": str(resources / "icon.ico"), "register_python_default": False, "default_prefix": os.path.join( "%LOCALAPPDATA%", INSTALLER_DEFAULT_PATH_STEM @@ -307,7 +309,7 @@ def _constructor(version=_version(), extra_specs=None, spy_repo=SPYREPO): Parameters ---------- version: str - Version of `napari` to be built. Defaults to the + Version of `spyder` to be built. Defaults to the one detected by `importlib` from the source code. extra_specs: list of str Additional packages to be included in the installer. From c65c5a46061ab85f03c6fd5034914a8918d5acd8 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Thu, 15 Sep 2022 08:57:34 -0700 Subject: [PATCH 03/43] Update installer licensing and branding --- installers-exp/build_installers.py | 2 +- installers-exp/resources/bundle_license.rtf | 30 ++++++----------- installers-exp/resources/bundle_license.txt | 33 ------------------- installers-exp/resources/bundle_readme.md | 29 ++++++++-------- .../resources/osx_pkg_welcome.rtf.tmpl | 6 ++-- 5 files changed, 28 insertions(+), 72 deletions(-) delete mode 100644 installers-exp/resources/bundle_license.txt diff --git a/installers-exp/build_installers.py b/installers-exp/build_installers.py index 9ff39afc20e..8bd3a8fbfad 100644 --- a/installers-exp/build_installers.py +++ b/installers-exp/build_installers.py @@ -239,7 +239,7 @@ def _definitions(version=_version(), extra_specs=None, spy_repo=SPYREPO): definitions["default_prefix"] = os.path.join( "$HOME", ".local", INSTALLER_DEFAULT_PATH_STEM ) - definitions["license_file"] = str(resources / "bundle_license.txt") + definitions["license_file"] = str(spy_repo / "LICENSE.txt") definitions["installer_type"] = "sh" if MACOS: diff --git a/installers-exp/resources/bundle_license.rtf b/installers-exp/resources/bundle_license.rtf index 3abe77306e3..6889a5c1111 100644 --- a/installers-exp/resources/bundle_license.rtf +++ b/installers-exp/resources/bundle_license.rtf @@ -1,31 +1,21 @@ -{\rtf1\ansi\ansicpg1252\cocoartf2580 -\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset0 LucidaGrande;} +{\rtf1\ansi\ansicpg1252\cocoartf2639 +\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} {\*\expandedcolortbl;;} {\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{square\}}{\leveltext\leveltemplateid1\'01\uc0\u9642 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid1}} {\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}} -\margl1440\margr1440\vieww28300\viewh16080\viewkind0 +\margl1440\margr1440\vieww19860\viewh17800\viewkind0 \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 -\f0\fs24 \cf0 BSD 3-Clause License\ +\f0\fs24 \cf0 MIT License\ \ -Copyright (c) 2018, Napari\ -All rights reserved.\ +Copyright (c) 2009- Spyder Project Contributors and others (see AUTHORS.txt); the spyder/images dir and some source files under other terms (see NOTICE.txt)\ \ -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\ +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\ \ +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\ \pard\tx220\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\li720\fi-720\pardirnatural\partightenfactor0 -\ls1\ilvl0\cf0 {\listtext -\f1 \uc0\u9642 -\f0 }Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\ -{\listtext -\f1 \uc0\u9642 -\f0 }Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\ -{\listtext -\f1 \uc0\u9642 -\f0 }Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\ +\ls1\ilvl0\cf0 \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 -\cf0 \ -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\ -\ -This installer bundles other packages, which are distributed under their own license terms. {\field{\*\fldinst{HYPERLINK "https://github.com/napari/napari/blob/latest/EULA.md"}}{\fldrslt Check the full list here}}.} \ No newline at end of file +\cf0 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\ +} \ No newline at end of file diff --git a/installers-exp/resources/bundle_license.txt b/installers-exp/resources/bundle_license.txt deleted file mode 100644 index eb5a32d8dc7..00000000000 --- a/installers-exp/resources/bundle_license.txt +++ /dev/null @@ -1,33 +0,0 @@ -BSD 3-Clause License - -Copyright (c) 2018, Napari -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -This installer bundles other packages, which are distributed under their own -license terms. Check the full list here: -https://github.com/napari/napari/blob/latest/EULA.md diff --git a/installers-exp/resources/bundle_readme.md b/installers-exp/resources/bundle_readme.md index b87a8c8b02b..0bc76757af3 100644 --- a/installers-exp/resources/bundle_readme.md +++ b/installers-exp/resources/bundle_readme.md @@ -1,49 +1,48 @@ -Welcome to the napari installation contents +Welcome to the Spyder installation contents ------------------------------------------- -This is the base installation of napari, a fast n-dimensional image viewer written in Python. +This is the base installation of Spyder, the Scientific Python Development Environment. -## How do I run napari? +## How do I run Spyder? In most cases, you would run it through the platform-specific shortcut we created for your -convenience. In other words, _not_ through this directory! You should be able to see a -`napari (x.y.z)` menu item, where `x.y.z` is the installed version. +convenience. In other words, _not_ through this directory! * Linux: check your desktop launcher. * MacOS: check `~/Applications` or the Launchpad. * Windows: check the Start Menu or the Desktop. We generally recommend using the shortcut because it will pre-activate the `conda` environment for -you! That said, you can also execute the `napari` executable directly from these locations: +you! That said, you can also execute the `spyder` executable directly from these locations: * Linux and macOS: find it under `bin`, next to this file. * Windows: navigate to `Scripts`, next to this file. -In unmodified installations, this _should_ be enough to launch `napari`, but sometimes you will +In unmodified installations, this _should_ be enough to launch `spyder`, but sometimes you will need to activate the `conda` environment to ensure all dependencies are importable. -## What does `conda` have to do with `napari`? +## What does `conda` have to do with `spyder`? -The `napari` installer uses `conda` packages to bundle all its dependencies (Python, qt, etc). +The Spyder installer uses `conda` packages to bundle all its dependencies (Python, qt, etc). This directory is actually a full `conda` installation! If you have used `conda` before, this is equivalent to what you usually call the `base` environment. -## Can I modify the `napari` installation? +## Can I modify the `spyder` installation? Yes. In practice, you can consider it a `conda` environment. You can even activate it as usual, provided you specify the full path to the location, instead of the _name_. ``` # macOS -$ conda activate ~/Library/napari-x.y.z +$ conda activate ~/Library/spyder-x.y.z # Linux -$ conda activate ~/.local/napari-x.y.z +$ conda activate ~/.local/spyder-x.y.z # Windows -$ conda activate %LOCALAPPDATA%/napari-x.y.z +$ conda activate %LOCALAPPDATA%/spyder-x.y.z ``` Then you will be able to run `conda` and `pip` as usual. That said, we advise against this advanced -manipulation. It can render `napari` unusable if not done carefully! You might need to reinstall it +manipulation. It can render `spyder` unusable if not done carefully! You might need to reinstall it in that case. ## What is `_conda.exe`? @@ -54,4 +53,4 @@ reinstalling anything. Again, consider this an advanced tool only meant for expe ## More information -Check our online documentation at https://napari.org/ +Check our online documentation at https://www.spyder-ide.org/ diff --git a/installers-exp/resources/osx_pkg_welcome.rtf.tmpl b/installers-exp/resources/osx_pkg_welcome.rtf.tmpl index 078a165fbc7..8ab63459689 100644 --- a/installers-exp/resources/osx_pkg_welcome.rtf.tmpl +++ b/installers-exp/resources/osx_pkg_welcome.rtf.tmpl @@ -5,11 +5,11 @@ \margl1440\margr1440\vieww12040\viewh13780\viewkind0 \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 -\f0\fs28 \cf0 Thanks for choosing napari v__VERSION__!\ +\f0\fs28 \cf0 Thanks for choosing Spyder v__VERSION__!\ \ -{\field{\*\fldinst{HYPERLINK "https://napari.org"}}{\fldrslt napari}} is a fast, interactive, multi-dimensional image viewer for Python. It's designed for browsing, annotating, and analyzing large multi-dimensional images.\ +{\field{\*\fldinst{HYPERLINK "https://www.spyder-ide.org"}}{\fldrslt Spyder}} is the Scientific Python Development Environment.\ \ The installation will begin shortly.\ \ -If at any point an error is shown, please save the logs (\uc0\u8984+L) before closing the installer and submit the resulting file along with your report in {\field{\*\fldinst{HYPERLINK "https://github.com/napari/napari/issues"}}{\fldrslt our issue tracker}}. Thank you!\ +If at any point an error is shown, please save the logs (\uc0\u8984+L) before closing the installer and submit the resulting file along with your report in {\field{\*\fldinst{HYPERLINK "https://github.com/spyder-ide/spyder/issues"}}{\fldrslt our issue tracker}}. Thank you!\ } \ No newline at end of file From 0d51ff1932f0496809f563093f09444f8c581888 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Fri, 16 Sep 2022 13:22:10 -0700 Subject: [PATCH 04/43] Eliminate spyder-menu feedstock. - Remove spyder-menu spec - Add commands for menuinst files to conda build script - Set menu_packages to spyder package name --- installers-exp/build_installers.py | 6 +--- installers-exp/spyder-app/build.sh | 6 ++++ installers-exp/spyder-menu/meta.yaml | 41 ---------------------------- 3 files changed, 7 insertions(+), 46 deletions(-) delete mode 100644 installers-exp/spyder-menu/meta.yaml diff --git a/installers-exp/build_installers.py b/installers-exp/build_installers.py index 8bd3a8fbfad..7ae05e0533b 100644 --- a/installers-exp/build_installers.py +++ b/installers-exp/build_installers.py @@ -194,10 +194,6 @@ def _spyder_env( # "channels": same as _base_env(), omit to inherit :) "specs": [ f"spyder={spyder_version}", - f"spyder-menu={spyder_version}", - # "conda", - # "mamba", - # "pip", ] + (extra_specs or []), # "exclude": exclude, # TODO: not supported yet in constructor @@ -226,7 +222,7 @@ def _definitions(version=_version(), extra_specs=None, spy_repo=SPYREPO): }, }, "menu_packages": [ - "spyder-menu", + "spyder", ], "extra_files": { str(resources / "bundle_readme.md"): "README.txt", diff --git a/installers-exp/spyder-app/build.sh b/installers-exp/spyder-app/build.sh index c9370b79642..6922fd07655 100644 --- a/installers-exp/spyder-app/build.sh +++ b/installers-exp/spyder-app/build.sh @@ -2,6 +2,12 @@ $PYTHON -m pip install . --no-deps --ignore-installed --no-cache-dir -vvv +mkdir -p "${PREFIX}/Menu" +sed "s/__PKG_VERSION__/${PKG_VERSION}/" "${SRC_DIR}/installers-exp/menuinst_config.json" > "${PREFIX}/Menu/spyder-menu.json" +cp "${SRC_DIR}/img_src/spyder.png" "${PREFIX}/Menu/spyder.png" +cp "${SRC_DIR}/img_src/spyder.icns" "${PREFIX}/Menu/spyder.icns" +cp "${SRC_DIR}/img_src/spyder.ico" "${PREFIX}/Menu/spyder.ico" + rm -rf $PREFIX/man rm -f $PREFIX/bin/spyder_win_post_install.py rm -rf $SP_DIR/Sphinx-* diff --git a/installers-exp/spyder-menu/meta.yaml b/installers-exp/spyder-menu/meta.yaml deleted file mode 100644 index d510b0622a2..00000000000 --- a/installers-exp/spyder-menu/meta.yaml +++ /dev/null @@ -1,41 +0,0 @@ -{% set name = "spyder" %} -{% set version = "5.4.0.dev0" %} -{% set build = 0 %} - -package: - name: spyder-menu - version: {{ version }} - -source: - path: /Users/rclary/Documents/Repos/Spyder-IDE/spyder - -build: - noarch: python - number: {{ build }} - script: - - mkdir -p "${PREFIX}/Menu" - - sed "s/__PKG_VERSION__/{{ PKG_VERSION }}/" "{{ SRC_DIR }}/installers-exp/menuinst_config.json" > "{{ PREFIX }}/Menu/spyder-menu.json" - - cp "{{ SRC_DIR }}/img_src/spyder.png" "{{ PREFIX }}/Menu/spyder.png" - - cp "{{ SRC_DIR }}/img_src/spyder.icns" "{{ PREFIX }}/Menu/spyder.icns" - - cp "{{ SRC_DIR }}/img_src/spyder.ico" "{{ PREFIX }}/Menu/spyder.ico" - -requirements: - build: - - sed # [unix] - run: - - spyder - -# test: -# imports: -# - spyder -# commands: -# - test -f ${CONDA_PREFIX}/Menu/spyder-menu.json - -about: - home: https://www.spyder-ide.org/ - license: MIT - license_file: LICENSE.txt - summary: provides menu icons for Spyder - description: provides menu icons for Spyder - doc_url: https://docs.spyder-ide.org/ - dev_url: https://github.com/spyder-ide/spyder From 59d6a001527c0c1eb840a8e70c3eaa0f61ca11f9 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Tue, 20 Sep 2022 13:21:07 -0700 Subject: [PATCH 05/43] Use conda-forge feedstock --- installers-exp/spy-inst-env.yml | 1 + installers-exp/spyder-app/bld.bat | 14 --- installers-exp/spyder-app/build.sh | 13 --- installers-exp/spyder-app/meta.yaml | 119 ------------------------ installers-exp/spyder-app/osx-zmq.patch | 14 --- 5 files changed, 1 insertion(+), 160 deletions(-) delete mode 100644 installers-exp/spyder-app/bld.bat delete mode 100644 installers-exp/spyder-app/build.sh delete mode 100644 installers-exp/spyder-app/meta.yaml delete mode 100644 installers-exp/spyder-app/osx-zmq.patch diff --git a/installers-exp/spy-inst-env.yml b/installers-exp/spy-inst-env.yml index 6bf18561b1c..40169c64e48 100644 --- a/installers-exp/spy-inst-env.yml +++ b/installers-exp/spy-inst-env.yml @@ -6,3 +6,4 @@ dependencies: - boa - conda-standalone - constructor + - ruamel.yaml.jinja2 diff --git a/installers-exp/spyder-app/bld.bat b/installers-exp/spyder-app/bld.bat deleted file mode 100644 index 6613c9f0a69..00000000000 --- a/installers-exp/spyder-app/bld.bat +++ /dev/null @@ -1,14 +0,0 @@ -%PYTHON% -m pip install . --no-deps --ignore-installed --no-cache-dir -vvv -if errorlevel 1 exit 1 - -set MENU_DIR=%PREFIX%\Menu -IF NOT EXIST (%MENU_DIR%) mkdir %MENU_DIR% - -copy %SRC_DIR%\img_src\spyder.ico %MENU_DIR%\ -if errorlevel 1 exit 1 -copy %RECIPE_DIR%\menu-windows.json %MENU_DIR%\spyder_shortcut.json -if errorlevel 1 exit 1 - -del %SCRIPTS%\spyder_win_post_install.py -del %SCRIPTS%\spyder.bat -del %SCRIPTS%\spyder diff --git a/installers-exp/spyder-app/build.sh b/installers-exp/spyder-app/build.sh deleted file mode 100644 index 6922fd07655..00000000000 --- a/installers-exp/spyder-app/build.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -$PYTHON -m pip install . --no-deps --ignore-installed --no-cache-dir -vvv - -mkdir -p "${PREFIX}/Menu" -sed "s/__PKG_VERSION__/${PKG_VERSION}/" "${SRC_DIR}/installers-exp/menuinst_config.json" > "${PREFIX}/Menu/spyder-menu.json" -cp "${SRC_DIR}/img_src/spyder.png" "${PREFIX}/Menu/spyder.png" -cp "${SRC_DIR}/img_src/spyder.icns" "${PREFIX}/Menu/spyder.icns" -cp "${SRC_DIR}/img_src/spyder.ico" "${PREFIX}/Menu/spyder.ico" - -rm -rf $PREFIX/man -rm -f $PREFIX/bin/spyder_win_post_install.py -rm -rf $SP_DIR/Sphinx-* diff --git a/installers-exp/spyder-app/meta.yaml b/installers-exp/spyder-app/meta.yaml deleted file mode 100644 index 928712eaea1..00000000000 --- a/installers-exp/spyder-app/meta.yaml +++ /dev/null @@ -1,119 +0,0 @@ -{% set name = "spyder" %} -{% set version = "5.4.0.dev0" %} -{% set build = 0 %} - -package: - name: spyder - version: {{ version }} - -source: - path: /Users/rclary/Documents/Repos/Spyder-IDE/spyder - -patches: - # See spyder-ide/spyder#8316 - - osx-zmq.patch - -build: - number: {{ build }} - entry_points: - - spyder = spyder.app.start:main - -requirements: - build: - - python # [build_platform != target_platform] - - cross-python_{{ target_platform }} # [build_platform != target_platform] - host: - - python - - pip - - setuptools - run: - - python - - applaunchservices >=0.3.0 # [osx] - - atomicwrites >=1.2.0 - - chardet >=2.0.0 - - cloudpickle >=0.5.0 - - cookiecutter >=1.6.0 - - diff-match-patch >=20181111 - - intervaltree >=3.0.2 - - ipython >=7.31.1,<8.0.0 - - jedi >=0.17.2,<0.19.0 - - jellyfish >=0.7 - - jsonschema >=3.2.0 - - keyring >=17.0.0 - - nbconvert >=4.0 - - numpydoc >=0.6.0 - - paramiko >=2.4.0 # [win] - - parso >=0.7.0,<0.9.0 - - pexpect >=4.4.0 - - pickleshare >=0.4 - # This is here to work around a bug in mamba - - ptyprocess >=0.5 # [win] - - psutil >=5.3 - - pygments >=2.0 - - pylint >=2.5.0,<3.0 - - python-lsp-black >=1.2.0 - - pyls-spyder >=0.4.0 - - pyqt >=5.6,<5.16 - - pyqtwebengine <5.16 - - python.app # [osx] - - python-lsp-server >=1.5.0,<1.6.0 - - pyxdg >=0.26 # [linux] - - pyzmq >=22.1.0 - - qdarkstyle >=3.0.2,<3.1.0 - - qstylizer >=0.1.10 - - qtawesome >=1.0.2 - - qtconsole >=5.3.2,<5.4.0 - - qtpy >=2.1.0 - - rtree >=0.9.7 - - setuptools >=49.6.0 - - sphinx >=0.6.6 - - spyder-kernels >=2.3.3,<2.4.0 - - textdistance >=4.2.0 - - three-merge >=0.1.1 - - watchdog >=0.10.3 - # This is here to workaround an inconsistency with flake8 - # See: https://github.com/conda-forge/flake8-feedstock/issues/52 - - importlib-metadata <4.3 # [py < 38] - run_constrained: - - menuinst >=1.4.17 - -# test: -# requires: -# - pip -# commands: -# - USER=test spyder -h # [unix] -# - spyder -h # [win] -# # Pip fails when running but the package is installed correctly -# - python -m pip check # [not aarch64] -# imports: -# - spyder - -about: - home: https://www.spyder-ide.org/ - license: MIT - license_file: LICENSE.txt - summary: The Scientific Python Development Environment - description: | - Spyder is a powerful scientific environment written in Python, for Python, - and designed by and for scientists, engineers and data analysts. - It features a unique combination of the advanced editing, analysis, - debugging and profiling functionality of a comprehensive development tool - with the data exploration, interactive execution, deep inspection and - beautiful visualization capabilities of a scientific package.\n - Furthermore, Spyder offers built-in integration with many popular - scientific packages, including NumPy, SciPy, Pandas, IPython, QtConsole, - Matplotlib, SymPy, and more.\n - Beyond its many built-in features, Spyder can be extended even further via - third-party plugins.\n - Spyder can also be used as a PyQt5 extension library, allowing you to build - upon its functionality and embed its components, such as the interactive - console or advanced editor, in your own software. - doc_url: https://docs.spyder-ide.org/ - dev_url: https://github.com/spyder-ide/spyder - -extra: - recipe-maintainers: - - ccordoba12 - - dalthviz - - andfoy - - steff456 diff --git a/installers-exp/spyder-app/osx-zmq.patch b/installers-exp/spyder-app/osx-zmq.patch deleted file mode 100644 index 83e7fc594b4..00000000000 --- a/installers-exp/spyder-app/osx-zmq.patch +++ /dev/null @@ -1,14 +0,0 @@ -diff --git a/spyder/app/start.py b/spyder/app/start.py -index bbeac112a..4883c6c47 100644 ---- a/spyder/app/start.py -+++ b/spyder/app/start.py -@@ -18,7 +18,8 @@ import time - - # To prevent a race condition with ZMQ - # See spyder-ide/spyder#5324. --import zmq -+if not sys.platform == 'darwin': -+ import zmq - - # Load GL library to prevent segmentation faults on some Linux systems - # See spyder-ide/spyder#3226 and spyder-ide/spyder#3332. From 1449ac39d4c4769bf071b198ec8433db3571cefe Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Tue, 20 Sep 2022 13:24:19 -0700 Subject: [PATCH 06/43] Some cleanup --- installers-exp/build_installers.py | 43 ++++++++++++++---------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/installers-exp/build_installers.py b/installers-exp/build_installers.py index 7ae05e0533b..77b53c39297 100644 --- a/installers-exp/build_installers.py +++ b/installers-exp/build_installers.py @@ -47,7 +47,7 @@ from pathlib import Path from tempfile import NamedTemporaryFile from textwrap import dedent, indent -from functools import lru_cache, partial +from functools import partial from subprocess import check_call, check_output from ruamel.yaml import YAML @@ -90,7 +90,6 @@ def _use_local(): return os.environ.get("CONSTRUCTOR_USE_LOCAL") -@lru_cache def _version(): from importlib.util import spec_from_file_location, module_from_spec spec = spec_from_file_location("spyder", SPYREPO / "spyder" / "__init__.py") @@ -380,7 +379,9 @@ def licenses(): return zipname.resolve() -def main(extra_specs=None, spy_repo=SPYREPO): +def main(extra_specs=None, spy_repo=SPYREPO, no_conda_build=False): + spy_repo = Path(spy_repo) # Enforce Path type + try: cwd = os.getcwd() workdir = spy_repo / "installers-exp" / "dist" @@ -401,51 +402,45 @@ def main(extra_specs=None, spy_repo=SPYREPO): def cli(argv=None): p = ArgumentParser(argv) p.add_argument( - "--version", - action="store_true", + "--version", action="store_true", help="Print local Spyder version and exit.", ) p.add_argument( - "--installer-version", - action="store_true", + "--installer-version", action="store_true", help="Print installer version and exit.", ) p.add_argument( - "--arch", - action="store_true", + "--arch", action="store_true", help="Print machine architecture tag and exit.", ) p.add_argument( - "--ext", - action="store_true", + "--ext", action="store_true", help="Print installer extension for this platform and exit.", ) p.add_argument( - "--artifact-name", - action="store_true", + "--artifact-name", action="store_true", help="Print computed artifact name and exit.", ) p.add_argument( - "--extra-specs", - nargs="+", + "--extra-specs", nargs="+", help="One or more extra conda specs to add to the installer", ) p.add_argument( - "--licenses", - action="store_true", + "--licenses", action="store_true", help="Post-process licenses AFTER having built the installer. " "This must be run as a separate step.", ) p.add_argument( - "--images", - action="store_true", + "--images", action="store_true", help="Generate background images from the logo (test only)", ) p.add_argument( - "--location", - default=SPYREPO, + "--location", default=SPYREPO, type=os.path.abspath, help="Path to spyder source repository", - type=os.path.abspath, + ) + p.add_argument( + "--no-conda-build", action="store_true", + help="Do not build conda packages for external-deps" ) return p.parse_args() @@ -474,4 +469,6 @@ def cli(argv=None): _generate_background_images(spy_repo=args.location) sys.exit() - print("Created", main(extra_specs=args.extra_specs, spy_repo=args.location)) + out = main(extra_specs=args.extra_specs, spy_repo=args.location, + no_conda_build=args.no_conda_build) + print("Created", out) From 17ecfbbbd46e87c10aabe1bd535ba97825a09f5b Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Tue, 20 Sep 2022 20:03:24 -0700 Subject: [PATCH 07/43] Add script for building conda packages --- installers-exp/build_conda_pkgs.py | 239 +++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 installers-exp/build_conda_pkgs.py diff --git a/installers-exp/build_conda_pkgs.py b/installers-exp/build_conda_pkgs.py new file mode 100644 index 00000000000..f51958a0580 --- /dev/null +++ b/installers-exp/build_conda_pkgs.py @@ -0,0 +1,239 @@ +""" +Build local conda packages +""" + +import re +from textwrap import dedent +from argparse import ArgumentParser +from shutil import rmtree +from pathlib import Path +from ruamel.yaml import YAML +from subprocess import check_call +from importlib.util import spec_from_file_location, module_from_spec +from logging import Formatter, StreamHandler, getLogger +from datetime import timedelta +from time import time + +fmt = Formatter('%(asctime)s [%(levelname)s] [%(name)s] -> %(message)s') +h = StreamHandler() +h.setFormatter(fmt) +logger = getLogger('BuildCondaPkgs') +logger.addHandler(h) +logger.setLevel('INFO') + +HERE = Path(__file__).parent +EXTDEPS = HERE.parent / "external-deps" + + +class BuildCondaPkg(): + src_path = None + feedstock = None + ver_path = None + + def __init__(self, data={}, debug=False): + # ---- Setup logger + self.logger = getLogger(self.__class__.__name__) + if not self.logger.handlers: + self.logger.addHandler(h) + self.logger.setLevel('INFO') + + self.debug = debug + + self.data = {'version': self._get_version()} + self.data.update(data) + + self.fdstk_path = HERE / self.feedstock.split("/")[-1] + + self._yaml = YAML(typ='jinja2') + self._yaml.indent(mapping=2, sequence=4, offset=2) + self.yaml = None + + self._patched_meta = False + self._patched_build = False + + def _get_version(self): + spec = spec_from_file_location(self.ver_path.parent.name, self.ver_path) + mod = module_from_spec(spec) + spec.loader.exec_module(mod) + return mod.__version__ + + def _git_init_src_path(self): + if (self.src_path / ".git").exists(): + rmtree(self.src_path / ".git", ignore_errors=True) + + self.logger.info(f"Initializing git repo at {self.src_path}...") + check_call(["git", "init", "-q", str(self.src_path)]) + check_call(["git", "-C", str(self.src_path), "add", "-A"]) + check_call(["git", "-C", str(self.src_path), "commit", "-qm", "build"]) + + def _git_clean_src_path(self): + git_path = self.src_path / ".git" + self.logger.info(f"Removing {git_path}...") + rmtree(git_path, ignore_errors=True) + + def _clone_feedstock(self): + if self.fdstk_path.exists(): + self.logger.info(f"Removing existing {self.fdstk_path}...") + rmtree(self.fdstk_path, ignore_errors=True) + + self.logger.info(f"Cloning feedstock to {self.fdstk_path}...") + check_call(["git", "clone", str(self.feedstock), str(self.fdstk_path)]) + + def _patch_meta(self): + pass + + def patch_meta(self): + if self._patched_meta: + return + + self.logger.info("Patching 'meta.yaml'...") + + file = self.fdstk_path / "recipe" / "meta.yaml" + text = file.read_text() + for k, v in self.data.items(): + text = re.sub(f".*set {k} =.*", f'{{% set {k} = "{v}" %}}', text) + + self.yaml = self._yaml.load(text) + + patches = self.yaml["source"].pop("patches", None) + self.yaml['source'] = {'path': str(self.src_path)} + if patches: + self.yaml['source']['patches'] = patches + + self.yaml.pop('test', None) + + self._patch_meta() + + self._yaml.dump_all([self.yaml], file) + + self._patched_meta = True + + def _patch_build(self): + pass + + def patch_build(self): + if self._patched_build: + return + + self.logger.info("Patching build script...") + self._patch_build() + self._patched_build = True + + def build(self): + t0 = time() + try: + self._git_init_src_path() + self._clone_feedstock() + self.patch_meta() + self.patch_build() + + self.logger.info("Building conda package...") + # check_call(["mamba", "mambabuild", str(self.fdstk_path / "recipe")]) + + finally: + self._patched_meta = False + self._patched_build = False + if not self.debug: + self.logger.info(f"Removing {self.fdstk_path}...") + rmtree(self.fdstk_path, ignore_errors=True) + + self._git_clean_src_path() + + elapse = timedelta(seconds=int(time() - t0)) + self.logger.info(f"Build time = {elapse}") + + +class SpyderCondaPkg(BuildCondaPkg): + src_path = HERE.parent + feedstock = "https://github.com/conda-forge/spyder-feedstock" + ver_path = src_path / "spyder" / "__init__.py" + + def _git_init_src_path(self): + # Do not initialize repo + pass + + def _git_clean_src_path(self): + # Do not remove .git + pass + + def _patch_meta(self): + self.yaml['build'].pop('osx_is_app', None) + self.yaml.pop('app', None) + + def _patch_build(self): + file = self.fdstk_path / "recipe" / "build.sh" + text = file.read_text() + text += dedent( + """ + mkdir -p "${PREFIX}/Menu" + sed "s/__PKG_VERSION__/${PKG_VERSION}/" "${SRC_DIR}/installers-exp/menuinst_config.json" > "${PREFIX}/Menu/spyder-menu.json" + cp "${SRC_DIR}/img_src/spyder.png" "${PREFIX}/Menu/spyder.png" + cp "${SRC_DIR}/img_src/spyder.icns" "${PREFIX}/Menu/spyder.icns" + cp "${SRC_DIR}/img_src/spyder.ico" "${PREFIX}/Menu/spyder.ico" + + """ + ) + + file.write_text(text) + + +class PylspCondaPkg(BuildCondaPkg): + src_path = EXTDEPS / "python-lsp-server" + feedstock = "https://github.com/conda-forge/python-lsp-server-feedstock" + ver_path = src_path / "pylsp" / "_version.py" + + +class QdarkstyleCondaPkg(BuildCondaPkg): + src_path = EXTDEPS / "qdarkstyle" + feedstock = "https://github.com/conda-forge/qdarkstyle-feedstock" + ver_path = src_path / "qdarkstyle" / "__init__.py" + + def _get_version(self): + text = self.ver_path.read_text() + return re.search('__version__ = "(.*)"', text).group(1) + + +class QtconsoleCondaPkg(BuildCondaPkg): + src_path = EXTDEPS / "qtconsole" + feedstock = "https://github.com/conda-forge/qtconsole-feedstock" + ver_path = src_path / "qtconsole" / "_version.py" + + def _patch_meta(self): + for out in self.yaml['outputs']: + out.pop("test", None) + + +class SpyderKernelsCondaPkg(BuildCondaPkg): + src_path = EXTDEPS / "spyder-kernels" + feedstock = "https://github.com/conda-forge/spyder-kernels-feedstock" + ver_path = src_path / "spyder_kernels" / "_version.py" + + +if __name__ == "__main__": + p = ArgumentParser( + usage="python build_conda_pkgs.py [--skip-external-deps] [--debug]", + epilog=dedent( + """ + Build conda packages from local spyder and external-deps sources. + """ + ) + ) + p.add_argument('--skip-external-deps', action='store_true', default=False, + help="Do not build conda packages for external-deps.") + p.add_argument('--debug', action='store_true', default=False, + help="Do not remove cloned feedstocks") + args = p.parse_args() + + logger.info("Building local conda packages...") + t0 = time() + + SpyderCondaPkg(debug=args.debug).build() + + if not args.skip_external_deps: + PylspCondaPkg(debug=args.debug).build() + QdarkstyleCondaPkg(debug=args.debug).build() + QtconsoleCondaPkg(debug=args.debug).build() + SpyderKernelsCondaPkg(debug=args.debug).build() + + elapse = timedelta(seconds=int(time() - t0)) + logger.info(f"Total build time = {elapse}") From 50e3bb34fdcc59b5786c20c177d9d9482bf957b8 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Tue, 20 Sep 2022 20:08:17 -0700 Subject: [PATCH 08/43] Rename directory --- {installers-exp => installers-conda}/build_conda_pkgs.py | 2 +- {installers-exp => installers-conda}/build_installers.py | 6 +++--- {installers-exp => installers-conda}/menuinst_config.json | 0 {installers-exp => installers-conda}/post-install.sh | 0 {installers-exp => installers-conda}/pre-install.sh | 0 .../resources/bundle_license.rtf | 0 .../resources/bundle_readme.md | 0 .../resources/osx_pkg_welcome.rtf.tmpl | 0 {installers-exp => installers-conda}/spy-inst-env.yml | 0 9 files changed, 4 insertions(+), 4 deletions(-) rename {installers-exp => installers-conda}/build_conda_pkgs.py (98%) rename {installers-exp => installers-conda}/build_installers.py (99%) rename {installers-exp => installers-conda}/menuinst_config.json (100%) rename {installers-exp => installers-conda}/post-install.sh (100%) rename {installers-exp => installers-conda}/pre-install.sh (100%) rename {installers-exp => installers-conda}/resources/bundle_license.rtf (100%) rename {installers-exp => installers-conda}/resources/bundle_readme.md (100%) rename {installers-exp => installers-conda}/resources/osx_pkg_welcome.rtf.tmpl (100%) rename {installers-exp => installers-conda}/spy-inst-env.yml (100%) diff --git a/installers-exp/build_conda_pkgs.py b/installers-conda/build_conda_pkgs.py similarity index 98% rename from installers-exp/build_conda_pkgs.py rename to installers-conda/build_conda_pkgs.py index f51958a0580..49887270712 100644 --- a/installers-exp/build_conda_pkgs.py +++ b/installers-conda/build_conda_pkgs.py @@ -166,7 +166,7 @@ def _patch_build(self): text += dedent( """ mkdir -p "${PREFIX}/Menu" - sed "s/__PKG_VERSION__/${PKG_VERSION}/" "${SRC_DIR}/installers-exp/menuinst_config.json" > "${PREFIX}/Menu/spyder-menu.json" + sed "s/__PKG_VERSION__/${PKG_VERSION}/" "${SRC_DIR}/installers-conda/menuinst_config.json" > "${PREFIX}/Menu/spyder-menu.json" cp "${SRC_DIR}/img_src/spyder.png" "${PREFIX}/Menu/spyder.png" cp "${SRC_DIR}/img_src/spyder.icns" "${PREFIX}/Menu/spyder.icns" cp "${SRC_DIR}/img_src/spyder.ico" "${PREFIX}/Menu/spyder.ico" diff --git a/installers-exp/build_installers.py b/installers-conda/build_installers.py similarity index 99% rename from installers-exp/build_installers.py rename to installers-conda/build_installers.py index 77b53c39297..f7e851e07c1 100644 --- a/installers-exp/build_installers.py +++ b/installers-conda/build_installers.py @@ -200,7 +200,7 @@ def _spyder_env( def _definitions(version=_version(), extra_specs=None, spy_repo=SPYREPO): - resources = spy_repo / "installers-exp" / "resources" + resources = spy_repo / "installers-conda" / "resources" base_env = _base_env() spyder_env = _spyder_env(spyder_version=version, extra_specs=extra_specs) condarc = _get_condarc() @@ -251,7 +251,7 @@ def _definitions(version=_version(), extra_specs=None, spy_repo=SPYREPO): definitions["welcome_file"] = str(welcome_file) definitions["conclusion_text"] = "" definitions["readme_text"] = "" - definitions["post_install"] = str(spy_repo / "installers-exp" / "post-install.sh") + definitions["post_install"] = str(spy_repo / "installers-conda" / "post-install.sh") signing_identity = os.environ.get("CONSTRUCTOR_SIGNING_IDENTITY") if signing_identity: definitions["signing_identity_name"] = signing_identity @@ -384,7 +384,7 @@ def main(extra_specs=None, spy_repo=SPYREPO, no_conda_build=False): try: cwd = os.getcwd() - workdir = spy_repo / "installers-exp" / "dist" + workdir = spy_repo / "installers-conda" / "dist" workdir.mkdir(exist_ok=True) os.chdir(workdir) _constructor(extra_specs=extra_specs, spy_repo=spy_repo) diff --git a/installers-exp/menuinst_config.json b/installers-conda/menuinst_config.json similarity index 100% rename from installers-exp/menuinst_config.json rename to installers-conda/menuinst_config.json diff --git a/installers-exp/post-install.sh b/installers-conda/post-install.sh similarity index 100% rename from installers-exp/post-install.sh rename to installers-conda/post-install.sh diff --git a/installers-exp/pre-install.sh b/installers-conda/pre-install.sh similarity index 100% rename from installers-exp/pre-install.sh rename to installers-conda/pre-install.sh diff --git a/installers-exp/resources/bundle_license.rtf b/installers-conda/resources/bundle_license.rtf similarity index 100% rename from installers-exp/resources/bundle_license.rtf rename to installers-conda/resources/bundle_license.rtf diff --git a/installers-exp/resources/bundle_readme.md b/installers-conda/resources/bundle_readme.md similarity index 100% rename from installers-exp/resources/bundle_readme.md rename to installers-conda/resources/bundle_readme.md diff --git a/installers-exp/resources/osx_pkg_welcome.rtf.tmpl b/installers-conda/resources/osx_pkg_welcome.rtf.tmpl similarity index 100% rename from installers-exp/resources/osx_pkg_welcome.rtf.tmpl rename to installers-conda/resources/osx_pkg_welcome.rtf.tmpl diff --git a/installers-exp/spy-inst-env.yml b/installers-conda/spy-inst-env.yml similarity index 100% rename from installers-exp/spy-inst-env.yml rename to installers-conda/spy-inst-env.yml From c32cfe86b0ad6c9df4684b59a293eb61c9cd1bae Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Wed, 21 Sep 2022 07:44:22 -0700 Subject: [PATCH 09/43] Add spyder source code patch --- installers-conda/build_conda_pkgs.py | 11 +- installers-conda/installers-conda.patch | 313 ++++++++++++++++++++++++ 2 files changed, 322 insertions(+), 2 deletions(-) create mode 100644 installers-conda/installers-conda.patch diff --git a/installers-conda/build_conda_pkgs.py b/installers-conda/build_conda_pkgs.py index 49887270712..d8fe6785e15 100644 --- a/installers-conda/build_conda_pkgs.py +++ b/installers-conda/build_conda_pkgs.py @@ -52,7 +52,8 @@ def __init__(self, data={}, debug=False): self._patched_build = False def _get_version(self): - spec = spec_from_file_location(self.ver_path.parent.name, self.ver_path) + spec = spec_from_file_location(self.ver_path.parent.name, + self.ver_path) mod = module_from_spec(spec) spec.loader.exec_module(mod) return mod.__version__ @@ -128,7 +129,9 @@ def build(self): self.patch_build() self.logger.info("Building conda package...") - # check_call(["mamba", "mambabuild", str(self.fdstk_path / "recipe")]) + check_call( + ["mamba", "mambabuild", str(self.fdstk_path / "recipe")] + ) finally: self._patched_meta = False @@ -160,6 +163,10 @@ def _patch_meta(self): self.yaml['build'].pop('osx_is_app', None) self.yaml.pop('app', None) + patches = self.yaml['source'].get('patches', []) + patches.append("../../installers-conda.patch") + self.yaml['source']['patches'] = patches + def _patch_build(self): file = self.fdstk_path / "recipe" / "build.sh" text = file.read_text() diff --git a/installers-conda/installers-conda.patch b/installers-conda/installers-conda.patch new file mode 100644 index 00000000000..aa364ed91b6 --- /dev/null +++ b/installers-conda/installers-conda.patch @@ -0,0 +1,313 @@ +From aaf106966bf500d5c2d6200e7b9f851bfb28c767 Mon Sep 17 00:00:00 2001 +From: Ryan Clary <9618975+mrclary@users.noreply.github.com> +Date: Mon, 12 Sep 2022 23:56:56 -0700 +Subject: [PATCH 1/2] Revise usage of running_in_mac_app + +--- + spyder/app/restart.py | 14 +++----- + spyder/app/utils.py | 1 + + spyder/config/base.py | 36 +++++-------------- + .../providers/languageserver/provider.py | 6 +--- + .../ipythonconsole/utils/kernelspec.py | 2 ++ + .../plugins/profiler/widgets/main_widget.py | 7 +--- + spyder/plugins/pylint/main_widget.py | 7 +--- + spyder/utils/programs.py | 5 +-- + spyder/utils/pyenv.py | 2 +- + 9 files changed, 22 insertions(+), 58 deletions(-) + +diff --git a/spyder/app/restart.py b/spyder/app/restart.py +index b85dd03cb..6bc566e89 100644 +--- a/spyder/app/restart.py ++++ b/spyder/app/restart.py +@@ -27,7 +27,7 @@ + + # Local imports + from spyder.app.utils import create_splash_screen +-from spyder.config.base import _, running_in_mac_app ++from spyder.config.base import _ + from spyder.utils.image_path_manager import get_image_path + from spyder.utils.encoding import to_unicode + from spyder.utils.qthelpers import qapplication +@@ -228,16 +228,12 @@ def main(): + args_reset = ['--reset'] + + # Build the base command +- if running_in_mac_app(sys.executable): +- exe = env['EXECUTABLEPATH'] +- command = [f'"{exe}"'] ++ if is_bootstrap: ++ script = osp.join(spyder_dir, 'bootstrap.py') + else: +- if is_bootstrap: +- script = osp.join(spyder_dir, 'bootstrap.py') +- else: +- script = osp.join(spyder_dir, 'spyder', 'app', 'start.py') ++ script = osp.join(spyder_dir, 'spyder', 'app', 'start.py') + +- command = [f'"{sys.executable}"', f'"{script}"'] ++ command = [f'"{sys.executable}"', f'"{script}"'] + + # Adjust the command and/or arguments to subprocess depending on the OS + shell = not IS_WINDOWS +diff --git a/spyder/app/utils.py b/spyder/app/utils.py +index 254df98b4..700ec6d13 100644 +--- a/spyder/app/utils.py ++++ b/spyder/app/utils.py +@@ -311,6 +311,7 @@ def create_window(WindowClass, app, splash, options, args): + QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, True) + + # Open external files with our Mac app ++ # ??? Do we need this? + if running_in_mac_app(): + app.sig_open_external_file.connect(main.open_external_file) + app._has_started = True +diff --git a/spyder/config/base.py b/spyder/config/base.py +index c0b9a4b29..204a6d72c 100644 +--- a/spyder/config/base.py ++++ b/spyder/config/base.py +@@ -549,42 +549,24 @@ def translate_gettext(x): + #============================================================================== + # Mac application utilities + #============================================================================== +-def running_in_mac_app(pyexec=None): ++def running_in_mac_app(pyexec=sys.executable): + """ +- Check if Python executable is located inside a standalone Mac app. ++ Check if Spyder is running as a macOS bundle app. ++ Checks for SPYDER_APP environment variable to determine this. + +- If no executable is provided, the default will check `sys.executable`, i.e. +- whether Spyder is running from a standalone Mac app. +- +- This is important for example for the single_instance option and the +- interpreter status in the statusbar. ++ If python executable is provided, checks if it is the same as the macOS ++ bundle app environment executable. + """ +- if pyexec is None: +- pyexec = sys.executable +- +- bpath = get_mac_app_bundle_path() ++ # Spyder is macOS app ++ mac_app = os.environ.get('SPYDER_APP') is not None + +- if bpath and pyexec == osp.join(bpath, 'Contents/MacOS/python'): ++ if mac_app and pyexec == sys.executable: ++ # executable is macOS app + return True + else: + return False + + +-def get_mac_app_bundle_path(): +- """ +- Return the full path to the macOS app bundle. Otherwise return None. +- +- EXECUTABLEPATH environment variable only exists if Spyder is a macOS app +- bundle. In which case it will always end with +- "/.app/Conents/MacOS/Spyder". +- """ +- app_exe_path = os.environ.get('EXECUTABLEPATH', None) +- if sys.platform == "darwin" and app_exe_path: +- return osp.dirname(osp.dirname(osp.dirname(osp.abspath(app_exe_path)))) +- else: +- return None +- +- + # ============================================================================= + # Micromamba + # ============================================================================= +diff --git a/spyder/plugins/completion/providers/languageserver/provider.py b/spyder/plugins/completion/providers/languageserver/provider.py +index e8959e13a..199a82b08 100644 +--- a/spyder/plugins/completion/providers/languageserver/provider.py ++++ b/spyder/plugins/completion/providers/languageserver/provider.py +@@ -23,8 +23,7 @@ + # Local imports + from spyder.api.config.decorators import on_conf_change + from spyder.utils.installers import InstallerPylspError +-from spyder.config.base import (_, get_conf_path, running_under_pytest, +- running_in_mac_app) ++from spyder.config.base import _, get_conf_path, running_under_pytest + from spyder.config.lsp import PYTHON_CONFIG + from spyder.utils.misc import check_connection_port + from spyder.plugins.completion.api import (SUPPORTED_LANGUAGES, +@@ -819,9 +818,6 @@ def generate_python_config(self): + else: + environment = self.get_conf('executable', + section='main_interpreter') +- # External interpreter cannot have PYTHONHOME +- if running_in_mac_app(): +- env_vars.pop('PYTHONHOME', None) + + jedi = { + 'environment': environment, +diff --git a/spyder/plugins/ipythonconsole/utils/kernelspec.py b/spyder/plugins/ipythonconsole/utils/kernelspec.py +index 0acdce9a9..4bdc4edd8 100644 +--- a/spyder/plugins/ipythonconsole/utils/kernelspec.py ++++ b/spyder/plugins/ipythonconsole/utils/kernelspec.py +@@ -187,6 +187,7 @@ def env(self): + env_vars['SPY_RUN_CYTHON'] = True + + # App considerations ++ # ??? Do we need this? + if (running_in_mac_app() or is_pynsist()): + if default_interpreter: + # See spyder-ide/spyder#16927 +@@ -194,6 +195,7 @@ def env(self): + # See spyder-ide/spyder#17552 + env_vars['PYDEVD_DISABLE_FILE_VALIDATION'] = 1 + else: ++ # ??? Do we need this? + env_vars.pop('PYTHONHOME', None) + + # Remove this variable because it prevents starting kernels for +diff --git a/spyder/plugins/profiler/widgets/main_widget.py b/spyder/plugins/profiler/widgets/main_widget.py +index 91c411bdf..c274f5872 100644 +--- a/spyder/plugins/profiler/widgets/main_widget.py ++++ b/spyder/plugins/profiler/widgets/main_widget.py +@@ -34,7 +34,7 @@ + from spyder.api.translations import get_translation + from spyder.api.widgets.main_widget import PluginMainWidget + from spyder.api.widgets.mixins import SpyderWidgetMixin +-from spyder.config.base import get_conf_path, running_in_mac_app ++from spyder.config.base import get_conf_path + from spyder.plugins.variableexplorer.widgets.texteditor import TextEditor + from spyder.py3compat import to_text_string + from spyder.utils.misc import get_python_executable, getcwd_or_home +@@ -534,11 +534,6 @@ def start(self, wdir=None, args=None, pythonpath=None): + + executable = self.get_conf('executable', section='main_interpreter') + +- if not running_in_mac_app(executable): +- env = self.process.processEnvironment() +- env.remove('PYTHONHOME') +- self.process.setProcessEnvironment(env) +- + self.output = '' + self.error_output = '' + self.running = True +diff --git a/spyder/plugins/pylint/main_widget.py b/spyder/plugins/pylint/main_widget.py +index a76d6fbf9..df4f00dab 100644 +--- a/spyder/plugins/pylint/main_widget.py ++++ b/spyder/plugins/pylint/main_widget.py +@@ -31,7 +31,7 @@ + from spyder.api.config.decorators import on_conf_change + from spyder.api.translations import get_translation + from spyder.api.widgets.main_widget import PluginMainWidget +-from spyder.config.base import get_conf_path, running_in_mac_app ++from spyder.config.base import get_conf_path + from spyder.plugins.pylint.utils import get_pylintrc_path + from spyder.plugins.variableexplorer.widgets.texteditor import TextEditor + from spyder.utils.icon_manager import ima +@@ -372,11 +372,6 @@ def _start(self): + user_profile = os.environ.get("USERPROFILE", home_dir) + processEnvironment.insert("USERPROFILE", user_profile) + +- # resolve spyder-ide/spyder#14262 +- if running_in_mac_app(): +- pyhome = os.environ.get("PYTHONHOME") +- processEnvironment.insert("PYTHONHOME", pyhome) +- + process.setProcessEnvironment(processEnvironment) + process.start(sys.executable, command_args) + running = process.waitForStarted() +diff --git a/spyder/utils/programs.py b/spyder/utils/programs.py +index ccd22f9e6..56123ca9c 100644 +--- a/spyder/utils/programs.py ++++ b/spyder/utils/programs.py +@@ -30,8 +30,7 @@ + import psutil + + # Local imports +-from spyder.config.base import (running_under_pytest, get_home_dir, +- running_in_mac_app) ++from spyder.config.base import running_under_pytest, get_home_dir + from spyder.py3compat import is_text_string, to_text_string + from spyder.utils import encoding + from spyder.utils.misc import get_python_executable +@@ -777,8 +776,6 @@ def run_python_script_in_terminal(fname, wdir, args, interact, debug, + delete=False) + if wdir: + f.write('cd "{}"\n'.format(wdir)) +- if running_in_mac_app(executable): +- f.write(f'export PYTHONHOME={os.environ["PYTHONHOME"]}\n') + if pypath is not None: + f.write(f'export PYTHONPATH={pypath}\n') + f.write(' '.join([executable] + p_args)) +diff --git a/spyder/utils/pyenv.py b/spyder/utils/pyenv.py +index 49f894e7c..018e02130 100644 +--- a/spyder/utils/pyenv.py ++++ b/spyder/utils/pyenv.py +@@ -11,7 +11,7 @@ + import os + import os.path as osp + +-from spyder.config.base import get_home_dir, running_in_mac_app ++from spyder.config.base import get_home_dir + from spyder.utils.programs import find_program, run_shell_command + + +-- +2.37.3 + + +From eadd3399de480315769cae4b97a2ebc6e26013d7 Mon Sep 17 00:00:00 2001 +From: Ryan Clary <9618975+mrclary@users.noreply.github.com> +Date: Mon, 12 Sep 2022 23:58:36 -0700 +Subject: [PATCH 2/2] Update standalone conda executable. + +--- + spyder/config/base.py | 5 +++-- + spyder/plugins/ipythonconsole/scripts/conda-activate.sh | 4 ++-- + spyder/utils/conda.py | 4 ++-- + 3 files changed, 7 insertions(+), 6 deletions(-) + +diff --git a/spyder/config/base.py b/spyder/config/base.py +index 204a6d72c..2629026d8 100644 +--- a/spyder/config/base.py ++++ b/spyder/config/base.py +@@ -573,8 +573,9 @@ def running_in_mac_app(pyexec=sys.executable): + def get_spyder_umamba_path(): + """Return the path to the Micromamba executable bundled with Spyder.""" + if running_in_mac_app(): +- path = osp.join(osp.dirname(osp.dirname(__file__)), +- 'bin', 'micromamba') ++ # TODO: Change to CONDA_EXE when ++ # conda-forge/conda-standalone-feedstock#45 is resolved ++ path = os.environ.get('CONDA_PYTHON_EXE') + elif is_pynsist(): + path = osp.abspath(osp.join(osp.dirname(osp.dirname(__file__)), + 'bin', 'micromamba.exe')) +diff --git a/spyder/plugins/ipythonconsole/scripts/conda-activate.sh b/spyder/plugins/ipythonconsole/scripts/conda-activate.sh +index f2243cfcc..0d92d4205 100755 +--- a/spyder/plugins/ipythonconsole/scripts/conda-activate.sh ++++ b/spyder/plugins/ipythonconsole/scripts/conda-activate.sh +@@ -8,8 +8,8 @@ CONDA_ENV_PYTHON=$3 + SPYDER_KERNEL_SPEC=$4 + + # Activate kernel environment +-if [[ "$CONDA_ACTIVATE_SCRIPT" = *"micromamba" ]]; then +- eval "$($CONDA_ACTIVATE_SCRIPT shell activate -p $CONDA_ENV_PATH)" ++if [[ "$CONDA_ACTIVATE_SCRIPT" = *"_conda.exe" ]]; then ++ eval "$($CONDA_ACTIVATE_SCRIPT shell.bash activate $CONDA_ENV_PATH)" + else + source $CONDA_ACTIVATE_SCRIPT $CONDA_ENV_PATH + fi +diff --git a/spyder/utils/conda.py b/spyder/utils/conda.py +index 2c43c37dc..e8db8a4f5 100644 +--- a/spyder/utils/conda.py ++++ b/spyder/utils/conda.py +@@ -73,8 +73,8 @@ def get_conda_activation_script(quote=False): + # Use micromamba bundled with Spyder installers or find conda exe + exe = get_spyder_umamba_path() or find_conda() + +- if osp.basename(exe).startswith('micromamba'): +- # For Micromamba, use the executable ++ if osp.basename(exe) in ('micromamba.exe', '_conda.exe'): ++ # For stadalone conda, use the executable + script_path = exe + else: + # Conda activation script is relative to executable +-- +2.37.3 + From f87a3c815d4fc534fc9073cbad11b60fc5bae1f8 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Wed, 21 Sep 2022 10:56:46 -0700 Subject: [PATCH 10/43] Cleanup --- ...spy-inst-env.yml => build-environment.yml} | 0 installers-conda/build_conda_pkgs.py | 18 +- installers-conda/build_installers.py | 356 +++++++----------- .../{ => resources}/installers-conda.patch | 0 .../{ => resources}/post-install.sh | 0 .../{ => resources}/pre-install.sh | 0 .../spyder-menu.json} | 0 7 files changed, 144 insertions(+), 230 deletions(-) rename installers-conda/{spy-inst-env.yml => build-environment.yml} (100%) rename installers-conda/{ => resources}/installers-conda.patch (100%) rename installers-conda/{ => resources}/post-install.sh (100%) rename installers-conda/{ => resources}/pre-install.sh (100%) rename installers-conda/{menuinst_config.json => resources/spyder-menu.json} (100%) diff --git a/installers-conda/spy-inst-env.yml b/installers-conda/build-environment.yml similarity index 100% rename from installers-conda/spy-inst-env.yml rename to installers-conda/build-environment.yml diff --git a/installers-conda/build_conda_pkgs.py b/installers-conda/build_conda_pkgs.py index d8fe6785e15..ff287325716 100644 --- a/installers-conda/build_conda_pkgs.py +++ b/installers-conda/build_conda_pkgs.py @@ -3,15 +3,15 @@ """ import re -from textwrap import dedent from argparse import ArgumentParser -from shutil import rmtree +from datetime import timedelta +from importlib.util import spec_from_file_location, module_from_spec +from logging import Formatter, StreamHandler, getLogger from pathlib import Path from ruamel.yaml import YAML +from shutil import rmtree from subprocess import check_call -from importlib.util import spec_from_file_location, module_from_spec -from logging import Formatter, StreamHandler, getLogger -from datetime import timedelta +from textwrap import dedent from time import time fmt = Formatter('%(asctime)s [%(levelname)s] [%(name)s] -> %(message)s') @@ -22,6 +22,7 @@ logger.setLevel('INFO') HERE = Path(__file__).parent +RESOURCES = HERE / "resources" EXTDEPS = HERE.parent / "external-deps" @@ -164,7 +165,7 @@ def _patch_meta(self): self.yaml.pop('app', None) patches = self.yaml['source'].get('patches', []) - patches.append("../../installers-conda.patch") + patches.append(str(RESOURCES / "installers-conda.patch")) self.yaml['source']['patches'] = patches def _patch_build(self): @@ -173,14 +174,15 @@ def _patch_build(self): text += dedent( """ mkdir -p "${PREFIX}/Menu" - sed "s/__PKG_VERSION__/${PKG_VERSION}/" "${SRC_DIR}/installers-conda/menuinst_config.json" > "${PREFIX}/Menu/spyder-menu.json" + sed "s/__PKG_VERSION__/${PKG_VERSION}/" """ + """"${SRC_DIR}/installers-conda/resources/spyder-menu.json" """ + """> "${PREFIX}/Menu/spyder-menu.json" cp "${SRC_DIR}/img_src/spyder.png" "${PREFIX}/Menu/spyder.png" cp "${SRC_DIR}/img_src/spyder.icns" "${PREFIX}/Menu/spyder.icns" cp "${SRC_DIR}/img_src/spyder.ico" "${PREFIX}/Menu/spyder.ico" """ ) - file.write_text(text) diff --git a/installers-conda/build_installers.py b/installers-conda/build_installers.py index f7e851e07c1..bdbc656ccf1 100644 --- a/installers-conda/build_installers.py +++ b/installers-conda/build_installers.py @@ -6,20 +6,8 @@ Some environment variables we use: -CONSTRUCTOR_APP_NAME: - in case you want to build a non-default distribution that is not - named `spyder` -CONSTRUCTOR_INSTALLER_DEFAULT_PATH_STEM: - The last component of the default installation path. Defaults to - {CONSTRUCTOR_APP_NAME}-{_version()} -CONSTRUCTOR_INSTALLER_VERSION: - Version for the installer, separate from the app being installed. - This will have an effect on the default install locations in future - releases. CONSTRUCTOR_TARGET_PLATFORM: conda-style platform (as in `platform` in `conda info -a` output) -CONSTRUCTOR_USE_LOCAL: - whether to use the local channel (populated by `conda-build` actions) CONSTRUCTOR_CONDA_EXE: when the target platform is not the same as the host, constructor needs a path to a conda-standalone (or micromamba) executable for @@ -32,9 +20,6 @@ be use to codesign some binaries bundled in the pkg (macOS only) CONSTRUCTOR_SIGNING_CERTIFICATE: Path to PFX certificate to sign the EXE installer on Windows -CONSTRUCTOR_PFX_CERTIFICATE_PASSWORD: - Password to unlock the PFX certificate. This is not used here but - it might be needed by constructor. """ import json @@ -44,33 +29,30 @@ import zipfile from argparse import ArgumentParser from distutils.spawn import find_executable -from pathlib import Path -from tempfile import NamedTemporaryFile -from textwrap import dedent, indent from functools import partial -from subprocess import check_call, check_output - +from importlib.util import spec_from_file_location, module_from_spec +from pathlib import Path from ruamel.yaml import YAML - -# import spyder +from subprocess import check_call, check_output +from textwrap import dedent, indent yaml = YAML() -yaml.indent(mapping=2) +yaml.indent(mapping=2, sequence=4, offset=2) indent4 = partial(indent, prefix=" ") -APP = os.environ.get("CONSTRUCTOR_APP_NAME", "Spyder") -# bump this when something in the installer infrastructure changes -# note that this will affect the default installation path across platforms! -INSTALLER_VERSION = os.environ.get("CONSTRUCTOR_INSTALLER_VERSION", "0.1") -SPYREPO = Path(__file__).parent.parent +APP = "Spyder" +HERE = Path(__file__).parent +RESOURCES = HERE / "resources" +DIST = HERE / "dist" +SPYREPO = HERE.parent WINDOWS = os.name == "nt" MACOS = sys.platform == "darwin" LINUX = sys.platform.startswith("linux") -if os.environ.get("CONSTRUCTOR_TARGET_PLATFORM") == "osx-arm64": +TARGET_PLATFORM = os.environ.get("CONSTRUCTOR_TARGET_PLATFORM") +if TARGET_PLATFORM == "osx-arm64": ARCH = "arm64" else: ARCH = (platform.machine() or "generic").lower().replace("amd64", "x86_64") -TARGET_PLATFORM = os.environ.get("CONSTRUCTOR_TARGET_PLATFORM") PY_VER = f"{sys.version_info.major}.{sys.version_info.minor}" if WINDOWS: EXT, OS = "exe", "Windows" @@ -82,30 +64,60 @@ raise RuntimeError(f"Unrecognized OS: {sys.platform}") -def _use_local(): - """ - Detect whether we need to build Spyder locally - (dev snapshots). This env var is set in the GHA workflow. - """ - return os.environ.get("CONSTRUCTOR_USE_LOCAL") - - def _version(): - from importlib.util import spec_from_file_location, module_from_spec - spec = spec_from_file_location("spyder", SPYREPO / "spyder" / "__init__.py") + spec = spec_from_file_location( + "spyder", SPYREPO / "spyder" / "__init__.py") mod = module_from_spec(spec) spec.loader.exec_module(mod) return mod.__version__ -OUTPUT_FILENAME = f"{APP}-{_version()}-{OS}-{ARCH}.{EXT}" -INSTALLER_DEFAULT_PATH_STEM = os.environ.get( - "CONSTRUCTOR_INSTALLER_DEFAULT_PATH_STEM", f"{APP}-{_version()}" +# ---- Parse arguments +p = ArgumentParser() +p.add_argument( + "--version", default=_version(), + help="Specify Spyder version; default is determined from source code" +) +p.add_argument( + "--no-local", action="store_true", + help="Do not use local conda packages" +) +p.add_argument( + "--debug", action="store_true", + help="Do not delete build files" +) +p.add_argument( + "--arch", action="store_true", + help="Print machine architecture tag and exit.", +) +p.add_argument( + "--ext", action="store_true", + help="Print installer extension for this platform and exit.", ) -clean_these_files = [] +p.add_argument( + "--artifact-name", action="store_true", + help="Print computed artifact name and exit.", +) +p.add_argument( + "--extra-specs", nargs="+", default=[], + help="One or more extra conda specs to add to the installer", +) +p.add_argument( + "--licenses", action="store_true", + help="Post-process licenses AFTER having built the installer. " + "This must be run as a separate step.", +) +p.add_argument( + "--images", action="store_true", + help="Generate background images from the logo (test only)", +) +args = p.parse_args() + +OUTPUT_FILE = DIST / f"{APP}-{args.version}-{OS}-{ARCH}.{EXT}" +INSTALLER_DEFAULT_PATH_STEM = f"{APP}-{args.version}" -def _generate_background_images(installer_type, outpath="resources"): +def _generate_background_images(installer_type): """Requires pillow""" if installer_type == "sh": # shell installers are text-based, no graphics @@ -116,29 +128,24 @@ def _generate_background_images(installer_type, outpath="resources"): logo_path = SPYREPO / "img_src" / "spyder.png" logo = Image.open(logo_path, "r") - global clean_these_files - if installer_type in ("exe", "all"): sidebar = Image.new("RGBA", (164, 314), (0, 0, 0, 0)) sidebar.paste(logo.resize((101, 101)), (32, 180)) - output = Path(outpath, "spyder_164x314.png") + output = DIST / "spyder_164x314.png" sidebar.save(output, format="png") - clean_these_files.append(output) banner = Image.new("RGBA", (150, 57), (0, 0, 0, 0)) banner.paste(logo.resize((44, 44)), (8, 6)) - output = Path(outpath, "spyder_150x57.png") + output = DIST / "spyder_150x57.png" banner.save(output, format="png") - clean_these_files.append(output) if installer_type in ("pkg", "all"): _logo = Image.new("RGBA", logo.size, "WHITE") _logo.paste(logo, mask=logo) background = Image.new("RGBA", (1227, 600), (0, 0, 0, 0)) background.paste(_logo.resize((148, 148)), (95, 418)) - output = Path(outpath, "spyder_1227x600.png") + output = DIST / "spyder_1227x600.png" background.save(output, format="png") - clean_these_files.append(output) def _get_condarc(): @@ -161,101 +168,83 @@ def _get_condarc(): ) # the undocumented #!final comment is explained here # https://www.anaconda.com/blog/conda-configuration-engine-power-users - with NamedTemporaryFile(delete=False, mode="w+") as f: - f.write(contents) - return f.name + file = DIST / "condarc" + file.write_text(contents) + + return str(file) -def _base_env(python_version=PY_VER): - return { - "name": "base", +def _definitions(): + condarc = _get_condarc() + definitions = { + "name": APP, + "company": "Spyder-IDE", + "reverse_domain_identifier": "org.spyder-ide.Spyder", + "version": args.version.replace("+", "_"), "channels": [ "napari/label/bundle_tools", "spyder-ide", "conda-forge", ], + "conda_default_channels": ["conda-forge"], "specs": [ "python", "conda", "mamba", "pip", ], - } - - -def _spyder_env( - python_version=PY_VER, - spyder_version=_version(), - extra_specs=None, -): - return { - "name": f"spyder-{spyder_version}", - # "channels": same as _base_env(), omit to inherit :) - "specs": [ - f"spyder={spyder_version}", - ] - + (extra_specs or []), - # "exclude": exclude, # TODO: not supported yet in constructor - } - - -def _definitions(version=_version(), extra_specs=None, spy_repo=SPYREPO): - resources = spy_repo / "installers-conda" / "resources" - base_env = _base_env() - spyder_env = _spyder_env(spyder_version=version, extra_specs=extra_specs) - condarc = _get_condarc() - definitions = { - "name": APP, - "company": "Spyder-IDE", - "reverse_domain_identifier": "org.spyder-ide.Spyder", - "version": version.replace("+", "_"), - "channels": base_env["channels"], - "conda_default_channels": ["conda-forge"], - "installer_filename": OUTPUT_FILENAME, + "installer_filename": OUTPUT_FILE.name, "initialize_by_default": False, - "license_file": str(resources / "bundle_license.rtf"), - "specs": base_env["specs"], + "license_file": str(RESOURCES / "bundle_license.rtf"), "extra_envs": { - spyder_env["name"]: { - "specs": spyder_env["specs"], + f"spyder-{args.version}": { + "specs": [f"spyder={args.version}"] + args.extra_specs, }, }, "menu_packages": [ "spyder", ], "extra_files": { - str(resources / "bundle_readme.md"): "README.txt", + str(RESOURCES / "bundle_readme.md"): "README.txt", condarc: ".condarc", }, } - if _use_local(): + if not args.no_local: definitions["channels"].insert(0, "local") + if LINUX: definitions["default_prefix"] = os.path.join( "$HOME", ".local", INSTALLER_DEFAULT_PATH_STEM ) - definitions["license_file"] = str(spy_repo / "LICENSE.txt") + definitions["license_file"] = str(SPYREPO / "LICENSE.txt") definitions["installer_type"] = "sh" if MACOS: + welcome_text_tmpl = \ + (RESOURCES / "osx_pkg_welcome.rtf.tmpl").read_text() + welcome_file = DIST / "osx_pkg_welcome.rtf" + welcome_file.write_text( + welcome_text_tmpl.replace("__VERSION__", args.version)) + # These two options control the default install location: # ~// - definitions["pkg_name"] = INSTALLER_DEFAULT_PATH_STEM - definitions["default_location_pkg"] = "Library" - definitions["installer_type"] = "pkg" - definitions["welcome_image"] = str(resources / "spyder_1227x600.png") - welcome_text_tmpl = (resources / "osx_pkg_welcome.rtf.tmpl").read_text() - welcome_file = resources / "osx_pkg_welcome.rtf" - clean_these_files.append(welcome_file) - welcome_file.write_text(welcome_text_tmpl.replace("__VERSION__", version)) - definitions["welcome_file"] = str(welcome_file) - definitions["conclusion_text"] = "" - definitions["readme_text"] = "" - definitions["post_install"] = str(spy_repo / "installers-conda" / "post-install.sh") + definitions.update( + { + "pkg_name": INSTALLER_DEFAULT_PATH_STEM, + "default_location_pkg": "Library", + "installer_type": "pkg", + "welcome_image": str(DIST / "spyder_1227x600.png"), + "welcome_file": str(welcome_file), + "conclusion_text": "", + "readme_text": "", + "post_install": str(RESOURCES / "post-install.sh"), + } + ) signing_identity = os.environ.get("CONSTRUCTOR_SIGNING_IDENTITY") if signing_identity: definitions["signing_identity_name"] = signing_identity - notarization_identity = os.environ.get("CONSTRUCTOR_NOTARIZATION_IDENTITY") + notarization_identity = \ + os.environ.get("CONSTRUCTOR_NOTARIZATION_IDENTITY") if notarization_identity: definitions["notarization_identity_name"] = notarization_identity @@ -263,9 +252,9 @@ def _definitions(version=_version(), extra_specs=None, spy_repo=SPYREPO): definitions["conda_default_channels"].append("defaults") definitions.update( { - "welcome_image": str(resources / "spyder_164x314.png"), - "header_image": str(resources / "spyder_150x57.png"), - "icon_image": str(resources / "icon.ico"), + "welcome_image": str(DIST / "spyder_164x314.png"), + "header_image": str(DIST / "spyder_150x57.png"), + "icon_image": str(DIST / "icon.ico"), "register_python_default": False, "default_prefix": os.path.join( "%LOCALAPPDATA%", INSTALLER_DEFAULT_PATH_STEM @@ -285,18 +274,12 @@ def _definitions(version=_version(), extra_specs=None, spy_repo=SPYREPO): definitions["signing_certificate"] = signing_certificate if definitions.get("welcome_image") or definitions.get("header_image"): - _generate_background_images( - definitions.get("installer_type", "all"), - outpath=resources, - ) - - clean_these_files.append("construct.yaml") - clean_these_files.append(condarc) + _generate_background_images(definitions.get("installer_type", "all")) return definitions -def _constructor(version=_version(), extra_specs=None, spy_repo=SPYREPO): +def _constructor(): """ Create a temporary `construct.yaml` input file and run `constructor`. @@ -310,55 +293,47 @@ def _constructor(version=_version(), extra_specs=None, spy_repo=SPYREPO): Additional packages to be included in the installer. A list of conda spec strings (`numpy`, `python=3`, etc) is expected. - spy_repo: str - location where the spyder-ide/spyder repository was cloned """ constructor = find_executable("constructor") if not constructor: raise RuntimeError("Constructor must be installed and in PATH.") - # TODO: temporarily patching password - remove block when the secret has been fixed - # (I think it contains an ending newline or something like that, copypaste artifact?) - pfx_password = os.environ.get("CONSTRUCTOR_PFX_CERTIFICATE_PASSWORD") - if pfx_password: - os.environ["CONSTRUCTOR_PFX_CERTIFICATE_PASSWORD"] = pfx_password.strip() + definitions = _definitions() - definitions = _definitions( - version=version, extra_specs=extra_specs, spy_repo=spy_repo - ) - - args = [constructor, "-v", "--debug", "."] + cmd_args = [constructor, "-v", "--output-dir", str(DIST)] + if args.debug: + cmd_args.append("--debug") conda_exe = os.environ.get("CONSTRUCTOR_CONDA_EXE") if TARGET_PLATFORM and conda_exe: - args += ["--platform", TARGET_PLATFORM, "--conda-exe", conda_exe] + cmd_args += ["--platform", TARGET_PLATFORM, "--conda-exe", conda_exe] + cmd_args.append(str(DIST)) + env = os.environ.copy() env["CONDA_CHANNEL_PRIORITY"] = "strict" print("+++++++++++++++++") - print("Command:", " ".join(args)) + print("Command:", " ".join(cmd_args)) print("Configuration:") yaml.dump(definitions, sys.stdout, transform=indent4) print("\nConda config:\n") print( - indent4(check_output(["conda", "config", "--show-sources"], text=True, env=env)) + indent4(check_output(["conda", "config", "--show-sources"], + text=True, env=env)) ) print("Conda info:") print(indent4(check_output(["conda", "info"], text=True, env=env))) print("+++++++++++++++++") - with open("construct.yaml", "w") as fin: - yaml.dump(definitions, fin) + file = DIST / "construct.yaml" + yaml.dump(definitions, file) - check_call(args, env=env) - - return OUTPUT_FILENAME + check_call(cmd_args, env=env) def licenses(): - info_path = Path("dist") / "info.json" + info_path = DIST / "info.json" try: - with open(info_path) as f: - info = json.load(f) + info = json.load(info_path) except FileNotFoundError: print( "!! Use `constructor --debug` to write info.json and get licenses", @@ -366,93 +341,32 @@ def licenses(): ) raise - zipname = Path("dist") / f"licenses.{OS}-{ARCH}.zip" - output_zip = zipfile.ZipFile(zipname, mode="w", compression=zipfile.ZIP_DEFLATED) + zipname = DIST / f"licenses.{OS}-{ARCH}.zip" + output_zip = zipfile.ZipFile(zipname, mode="w", + compression=zipfile.ZIP_DEFLATED) output_zip.write(info_path) for package_id, license_info in info["_licenses"].items(): package_name = package_id.split("::", 1)[1] for license_type, license_files in license_info.items(): for i, license_file in enumerate(license_files, 1): - arcname = f"{package_name}.{license_type.replace(' ', '_')}.{i}.txt" + arcname = (f"{package_name}.{license_type.replace(' ', '_')}" + f".{i}.txt") output_zip.write(license_file, arcname=arcname) output_zip.close() return zipname.resolve() -def main(extra_specs=None, spy_repo=SPYREPO, no_conda_build=False): - spy_repo = Path(spy_repo) # Enforce Path type - +def main(): try: - cwd = os.getcwd() - workdir = spy_repo / "installers-conda" / "dist" - workdir.mkdir(exist_ok=True) - os.chdir(workdir) - _constructor(extra_specs=extra_specs, spy_repo=spy_repo) - assert Path(OUTPUT_FILENAME).exists(), f"{OUTPUT_FILENAME} was not created!" + DIST.mkdir(exist_ok=True) + _constructor() + assert Path(OUTPUT_FILE).exists() + print("Created", OUTPUT_FILE) finally: - for path in clean_these_files: - try: - os.unlink(path) - except OSError: - print("!! Could not remove", path, file=sys.stderr) - os.chdir(cwd) - return workdir / OUTPUT_FILENAME - - -def cli(argv=None): - p = ArgumentParser(argv) - p.add_argument( - "--version", action="store_true", - help="Print local Spyder version and exit.", - ) - p.add_argument( - "--installer-version", action="store_true", - help="Print installer version and exit.", - ) - p.add_argument( - "--arch", action="store_true", - help="Print machine architecture tag and exit.", - ) - p.add_argument( - "--ext", action="store_true", - help="Print installer extension for this platform and exit.", - ) - p.add_argument( - "--artifact-name", action="store_true", - help="Print computed artifact name and exit.", - ) - p.add_argument( - "--extra-specs", nargs="+", - help="One or more extra conda specs to add to the installer", - ) - p.add_argument( - "--licenses", action="store_true", - help="Post-process licenses AFTER having built the installer. " - "This must be run as a separate step.", - ) - p.add_argument( - "--images", action="store_true", - help="Generate background images from the logo (test only)", - ) - p.add_argument( - "--location", default=SPYREPO, type=os.path.abspath, - help="Path to spyder source repository", - ) - p.add_argument( - "--no-conda-build", action="store_true", - help="Do not build conda packages for external-deps" - ) - return p.parse_args() + pass if __name__ == "__main__": - args = cli() - if args.version: - print(_version()) - sys.exit() - if args.installer_version: - print(INSTALLER_VERSION) - sys.exit() if args.arch: print(ARCH) sys.exit() @@ -460,15 +374,13 @@ def cli(argv=None): print(EXT) sys.exit() if args.artifact_name: - print(OUTPUT_FILENAME) + print(OUTPUT_FILE) sys.exit() if args.licenses: print(licenses()) sys.exit() if args.images: - _generate_background_images(spy_repo=args.location) + _generate_background_images() sys.exit() - out = main(extra_specs=args.extra_specs, spy_repo=args.location, - no_conda_build=args.no_conda_build) - print("Created", out) + main() diff --git a/installers-conda/installers-conda.patch b/installers-conda/resources/installers-conda.patch similarity index 100% rename from installers-conda/installers-conda.patch rename to installers-conda/resources/installers-conda.patch diff --git a/installers-conda/post-install.sh b/installers-conda/resources/post-install.sh similarity index 100% rename from installers-conda/post-install.sh rename to installers-conda/resources/post-install.sh diff --git a/installers-conda/pre-install.sh b/installers-conda/resources/pre-install.sh similarity index 100% rename from installers-conda/pre-install.sh rename to installers-conda/resources/pre-install.sh diff --git a/installers-conda/menuinst_config.json b/installers-conda/resources/spyder-menu.json similarity index 100% rename from installers-conda/menuinst_config.json rename to installers-conda/resources/spyder-menu.json From 79a839ffb03a0cb458145c69314816baf2480fd5 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Wed, 21 Sep 2022 13:46:55 -0700 Subject: [PATCH 11/43] Add timers --- installers-conda/build_installers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/installers-conda/build_installers.py b/installers-conda/build_installers.py index bdbc656ccf1..f1c7472a5db 100644 --- a/installers-conda/build_installers.py +++ b/installers-conda/build_installers.py @@ -28,6 +28,7 @@ import sys import zipfile from argparse import ArgumentParser +from datetime import timedelta from distutils.spawn import find_executable from functools import partial from importlib.util import spec_from_file_location, module_from_spec @@ -35,6 +36,7 @@ from ruamel.yaml import YAML from subprocess import check_call, check_output from textwrap import dedent, indent +from time import time yaml = YAML() yaml.indent(mapping=2, sequence=4, offset=2) @@ -357,13 +359,15 @@ def licenses(): def main(): + t0 = time() try: DIST.mkdir(exist_ok=True) _constructor() assert Path(OUTPUT_FILE).exists() print("Created", OUTPUT_FILE) finally: - pass + elapse = timedelta(seconds=int(time() - t0)) + print(f"Build time: {elapse}") if __name__ == "__main__": From 43031a7b4be00602601a5944832b89fdde4b88e6 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Sat, 24 Sep 2022 10:09:04 -0700 Subject: [PATCH 12/43] Add logger --- installers-conda/build_installers.py | 32 +++++++++++++--------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/installers-conda/build_installers.py b/installers-conda/build_installers.py index f1c7472a5db..82c454bc48a 100644 --- a/installers-conda/build_installers.py +++ b/installers-conda/build_installers.py @@ -32,12 +32,20 @@ from distutils.spawn import find_executable from functools import partial from importlib.util import spec_from_file_location, module_from_spec +from logging import Formatter, StreamHandler, getLogger from pathlib import Path from ruamel.yaml import YAML -from subprocess import check_call, check_output +from subprocess import check_call from textwrap import dedent, indent from time import time +fmt = Formatter('%(asctime)s [%(levelname)s] [%(name)s] -> %(message)s') +h = StreamHandler() +h.setFormatter(fmt) +logger = getLogger('BuildInstallers') +logger.addHandler(h) +logger.setLevel('INFO') + yaml = YAML() yaml.indent(mapping=2, sequence=4, offset=2) indent4 = partial(indent, prefix=" ") @@ -313,21 +321,11 @@ def _constructor(): env = os.environ.copy() env["CONDA_CHANNEL_PRIORITY"] = "strict" - print("+++++++++++++++++") - print("Command:", " ".join(cmd_args)) - print("Configuration:") - yaml.dump(definitions, sys.stdout, transform=indent4) - print("\nConda config:\n") - print( - indent4(check_output(["conda", "config", "--show-sources"], - text=True, env=env)) - ) - print("Conda info:") - print(indent4(check_output(["conda", "info"], text=True, env=env))) - print("+++++++++++++++++") + logger.info("Command: " + " ".join(cmd_args)) + logger.info("Configuration:") + yaml.dump(definitions, sys.stdout) - file = DIST / "construct.yaml" - yaml.dump(definitions, file) + yaml.dump(definitions, DIST / "construct.yaml") check_call(cmd_args, env=env) @@ -364,10 +362,10 @@ def main(): DIST.mkdir(exist_ok=True) _constructor() assert Path(OUTPUT_FILE).exists() - print("Created", OUTPUT_FILE) + logger.info(f"Created {OUTPUT_FILE}") finally: elapse = timedelta(seconds=int(time() - t0)) - print(f"Build time: {elapse}") + logger.info(f"Build time: {elapse}") if __name__ == "__main__": From 50f2f06b6d96635297cb7fbb39264df91467142f Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Thu, 22 Sep 2022 18:45:53 -0700 Subject: [PATCH 13/43] Use setuptools_scm instead of git init - Use scm for python-lsp-server - Clone remote python-lsp-server (shallow), unless user specifies source via environment variable - Do not git init the other external-deps --- installers-conda/build-environment.yml | 2 + installers-conda/build_conda_pkgs.py | 96 +++++++++++++++----------- 2 files changed, 56 insertions(+), 42 deletions(-) diff --git a/installers-conda/build-environment.yml b/installers-conda/build-environment.yml index 40169c64e48..c90c95b8637 100644 --- a/installers-conda/build-environment.yml +++ b/installers-conda/build-environment.yml @@ -6,4 +6,6 @@ dependencies: - boa - conda-standalone - constructor + - gitpython - ruamel.yaml.jinja2 + - setuptools_scm diff --git a/installers-conda/build_conda_pkgs.py b/installers-conda/build_conda_pkgs.py index ff287325716..84505e08163 100644 --- a/installers-conda/build_conda_pkgs.py +++ b/installers-conda/build_conda_pkgs.py @@ -1,14 +1,16 @@ """ Build local conda packages """ - +import os import re from argparse import ArgumentParser from datetime import timedelta +from git import Repo from importlib.util import spec_from_file_location, module_from_spec from logging import Formatter, StreamHandler, getLogger from pathlib import Path from ruamel.yaml import YAML +from setuptools_scm import get_version from shutil import rmtree from subprocess import check_call from textwrap import dedent @@ -59,20 +61,6 @@ def _get_version(self): spec.loader.exec_module(mod) return mod.__version__ - def _git_init_src_path(self): - if (self.src_path / ".git").exists(): - rmtree(self.src_path / ".git", ignore_errors=True) - - self.logger.info(f"Initializing git repo at {self.src_path}...") - check_call(["git", "init", "-q", str(self.src_path)]) - check_call(["git", "-C", str(self.src_path), "add", "-A"]) - check_call(["git", "-C", str(self.src_path), "commit", "-qm", "build"]) - - def _git_clean_src_path(self): - git_path = self.src_path / ".git" - self.logger.info(f"Removing {git_path}...") - rmtree(git_path, ignore_errors=True) - def _clone_feedstock(self): if self.fdstk_path.exists(): self.logger.info(f"Removing existing {self.fdstk_path}...") @@ -121,10 +109,13 @@ def patch_build(self): self._patch_build() self._patched_build = True + def _build_cleanup(self): + pass + def build(self): t0 = time() try: - self._git_init_src_path() + # self._git_init_src_path() self._clone_feedstock() self.patch_meta() self.patch_build() @@ -141,7 +132,7 @@ def build(self): self.logger.info(f"Removing {self.fdstk_path}...") rmtree(self.fdstk_path, ignore_errors=True) - self._git_clean_src_path() + self._build_cleanup() elapse = timedelta(seconds=int(time() - t0)) self.logger.info(f"Build time = {elapse}") @@ -152,14 +143,6 @@ class SpyderCondaPkg(BuildCondaPkg): feedstock = "https://github.com/conda-forge/spyder-feedstock" ver_path = src_path / "spyder" / "__init__.py" - def _git_init_src_path(self): - # Do not initialize repo - pass - - def _git_clean_src_path(self): - # Do not remove .git - pass - def _patch_meta(self): self.yaml['build'].pop('osx_is_app', None) self.yaml.pop('app', None) @@ -187,9 +170,25 @@ def _patch_build(self): class PylspCondaPkg(BuildCondaPkg): - src_path = EXTDEPS / "python-lsp-server" + src_path = Path(os.environ.get('PYTHON_LSP_SERVER_SOURCE', + HERE / "python-lsp-server")) feedstock = "https://github.com/conda-forge/python-lsp-server-feedstock" - ver_path = src_path / "pylsp" / "_version.py" + + def _get_version(self): + self._build_cleanup() # Remove existing if HERE + + if not self.src_path.exists(): + # Clone from remote + Repo.clone_from( + "https://github.com/python-lsp/python-lsp-server.git", + to_path=self.src_path, shallow_exclude="v1.4.1" + ) + return get_version(self.src_path) + + def _build_cleanup(self): + if self.src_path == HERE / "python-lsp-server": + logger.info(f"Removing {self.src_path}...") + rmtree(self.src_path, ignore_errors=True) class QdarkstyleCondaPkg(BuildCondaPkg): @@ -219,30 +218,43 @@ class SpyderKernelsCondaPkg(BuildCondaPkg): if __name__ == "__main__": + repos = { + "spyder": SpyderCondaPkg, + "python-lsp-server": PylspCondaPkg, + "qdarkstyle": QdarkstyleCondaPkg, + "qtconsole": QtconsoleCondaPkg, + "spyder-kernels": SpyderKernelsCondaPkg + } + p = ArgumentParser( - usage="python build_conda_pkgs.py [--skip-external-deps] [--debug]", - epilog=dedent( + description=dedent( """ Build conda packages from local spyder and external-deps sources. + Alternative git repo for python-lsp-server may be provided by + setting the environment variable PYTHON_LSP_SERVER_SOURCE, + otherwise the upstream remote will be used. All other external-deps + use the subrepo source within the spyder repo. """ - ) + ), + usage="python build_conda_pkgs.py " + "[--build subrepo [subrepo] ...] [--debug]", + ) + p.add_argument( + '--debug', action='store_true', default=False, + help="Do not remove cloned feedstocks" + ) + p.add_argument( + '--build', nargs="+", default=repos.keys(), + help=("Space-separated list of repos to build. " + f"Default is {list(repos.keys())}") ) - p.add_argument('--skip-external-deps', action='store_true', default=False, - help="Do not build conda packages for external-deps.") - p.add_argument('--debug', action='store_true', default=False, - help="Do not remove cloned feedstocks") args = p.parse_args() - logger.info("Building local conda packages...") + logger.info(f"Building local conda packages {list(args.build)}...") t0 = time() - SpyderCondaPkg(debug=args.debug).build() - - if not args.skip_external_deps: - PylspCondaPkg(debug=args.debug).build() - QdarkstyleCondaPkg(debug=args.debug).build() - QtconsoleCondaPkg(debug=args.debug).build() - SpyderKernelsCondaPkg(debug=args.debug).build() + for k in args.build: + repos[k](debug=args.debug).build() elapse = timedelta(seconds=int(time() - t0)) logger.info(f"Total build time = {elapse}") From 0205384c0c80a3537d3399833e477f7dd6c3ee37 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Tue, 4 Oct 2022 15:30:09 -0700 Subject: [PATCH 14/43] Add script for notarizing --- installers-conda/notarize.sh | 73 ++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100755 installers-conda/notarize.sh diff --git a/installers-conda/notarize.sh b/installers-conda/notarize.sh new file mode 100755 index 00000000000..77855a431dc --- /dev/null +++ b/installers-conda/notarize.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash + +help(){ cat <&1 # Additional output descriptor for logging +log(){ + level="INFO" + date "+%Y-%m-%d %H:%M:%S [$level] [notarize] -> $1" 1>&3 +} + +notarize_args=("--apple-id" "mrclary@me.com") +pwd_args=("-p" "spyder-ide") +while getopts "ht:p:v" option; do + case $option in + (h) help; exit ;; + (t) notarize_args+=("--timeout" "$OPTARG") ;; + (p) pwd_args=("--password" "$OPTARG") ;; + (v) notarize_args+=("--verbose") ;; + esac +done +shift $(($OPTIND - 1)) + +[[ $# = 0 ]] && log "File not provided" && exit 1 + +PKG=$(cd $(dirname $1) && pwd -P)/$(basename $1) # Resolve full path + +# --- Get certificate id +CNAME=$(security find-identity -p codesigning -v | pcregrep -o1 "\(([0-9A-Z]+)\)") +[[ -z $CNAME ]] && log "Could not locate certificate ID" && exit 1 +log "Certificate ID: $CNAME" + +notarize_args+=("--team-id" "$CNAME" "${pwd_args[@]}") + +# --- Notarize +log "Notarizing..." +xcrun notarytool submit $PKG --wait ${notarize_args[@]} | tee temp.txt + +submitid=$(pcregrep -o1 "^\s*id: ([0-9a-z-]+)" temp.txt | head -1) +status=$(pcregrep -o1 "^\s*status: (\w+$)" temp.txt) +rm temp.txt + +xcrun notarytool log $submitid ${notarize_args[@]} + +if [[ "$status" != "Accepted" ]]; then + log "Notarizing failed!" + exit 1 +fi + +log "Stapling notary ticket..." +xcrun stapler staple -v "$PKG" +if [[ $? != 0 ]]; then + log "Stapling failed!" + exit 1 +fi From ca798a709836a48e09e798733c9c1f203f0c9451 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Wed, 28 Sep 2022 12:58:45 -0700 Subject: [PATCH 15/43] Add script for creating keychain on CI --- installers-conda/certkeychain.sh | 85 ++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100755 installers-conda/certkeychain.sh diff --git a/installers-conda/certkeychain.sh b/installers-conda/certkeychain.sh new file mode 100755 index 00000000000..be4893425a4 --- /dev/null +++ b/installers-conda/certkeychain.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +set -e + +CERTFILE=certificate.p12 +KEY_PASS=keypass +KEYCHAIN=build.keychain +KEYCHAINFILE=$HOME/Library/Keychains/$KEYCHAIN-db + +help(){ cat <&1 +log(){ + level="INFO" + date "+%Y-%m-%d %H:%M:%S [$level] [keychain] -> $1" 1>&3 +} + +cleanup(){ + log "Removing $CERTFILE and $KEYCHAINFILE..." + rm -f $CERTFILE + rm -f $KEYCHAINFILE +} + +while getopts "hc" option; do + case $option in + (h) help; exit ;; + (c) cleanup; exit ;; + esac +done +shift $(($OPTIND - 1)) + +[[ $# < 2 ]] && log "Password and certificate(s) not provided" && exit 1 +PASS=$1; shift +CERTS=($@) + +# ---- Remove existing keychain +if [[ -e $KEYCHAINFILE ]]; then + log "Removing existing $KEYCHAINFILE..." + security delete-keychain $KEYCHAIN +fi + +# --- Create keychain +log "Creating keychain $KEYCHAINFILE..." +security create-keychain -p $KEY_PASS $KEYCHAIN +security list-keychains -s $KEYCHAIN +security unlock-keychain -p $KEY_PASS $KEYCHAIN + +log "Importing certificate(s)..." +args=("-k" "$KEYCHAIN" "-P" "$PASS" "-T" "/usr/bin/codesign" "-T" "/usr/bin/productsign") +for cert in ${CERTS[@]}; do + if [[ -e $cert ]]; then + log "Importing cert file $cert..." + _cert=$cert + else + log "Decoding/importing base64 cert..." + echo $cert | base64 --decode > $CERTFILE + _cert=$CERTFILE + fi + security import $_cert ${args[@]} +done + +# Ensure that applications can access the cert without GUI prompt +security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEY_PASS $KEYCHAIN + +# verify import +log "Verifying identity..." +security find-identity -p codesigning -v $KEYCHAIN From 475decb0c7f44c4f92773dbd569a13a167ec29fd Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Fri, 23 Sep 2022 11:31:47 -0700 Subject: [PATCH 16/43] Add github workflow --- .github/workflows/installers-conda.yml | 118 +++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 .github/workflows/installers-conda.yml diff --git a/.github/workflows/installers-conda.yml b/.github/workflows/installers-conda.yml new file mode 100644 index 00000000000..fd89edeb9ce --- /dev/null +++ b/.github/workflows/installers-conda.yml @@ -0,0 +1,118 @@ +on: + pull_request: + paths: + - 'installers-conda/**' + - '.github/workflows/installers-conda.yml' + - 'requirements/*.yml' + - 'MANIFEST.in' + - '**.bat' + - '**.py' + - '**.sh' + - '!**.md' + - '!installers/**' + - '!.github/workflows/installer-win.yml' + - '!.github/workflows/installer-macos.yml' + + release: + types: + - created + +name: Create conda-based installers for Windows, macOS, and Linux + +jobs: + build: + name: Build installer for ${{ matrix.target-platform }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: macos-11 + python-version: "3.9" + target-platform: "osx-64" + defaults: + run: + shell: bash -l {0} + working-directory: ${{ github.workspace }}/installers-conda + env: + DISTDIR: ${{ github.workspace }}/installers-conda/dist + MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }} + MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} + MACOS_INSTALLER_CERTIFICATE: ${{ secrets.MACOS_INSTALLER_CERTIFICATE }} + APPLICATION_PWD: ${{ secrets.APPLICATION_PWD }} + CONSTRUCTOR_TARGET_PLATFORM: ${{ matrix.target-platform }} + steps: + - name: Checkout Code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Setup Build Environment + uses: mamba-org/provision-with-micromamba@main + with: + environment-file: installers-conda/build-environment.yml + extra-specs: python=${{ matrix.python-version }} + + - name: Build Conda Packages + run: | + [[ $GITHUB_EVENT_NAME != "release" ]] && args=("--build" "spyder") || args=() + python build_conda_pkgs.py ${args[@]} + + - name: Create Keychain + if: github.event_name == 'release' && startsWith(matrix.os, 'macos') + run: ./certkeychain.sh "${MACOS_CERTIFICATE_PWD}" "${MACOS_CERTIFICATE}" "${MACOS_INSTALLER_CERTIFICATE}" + + - name: Build Package Installer + run: | + _codesign=$(which codesign) + if [[ $_codesign =~ ${CONDA_PREFIX}.* ]]; then + # Find correct codesign + echo "Moving $_codesign..." + mv $_codesign ${_codesign}.bak + fi + [[ "$GITHUB_EVENT_NAME" == "release" ]] && args=("--no-local") || args=() + python build_installers.py "${args[@]}" + PKG_FILE=$(python build_installers.py --artifact-name) + PKG_NAME=$(basename $PKG_FILE) + echo "PKG_FILE=$PKG_FILE" >> $GITHUB_ENV + echo "PKG_NAME=$PKG_NAME" >> $GITHUB_ENV + + - name: Test Application Bundle + if: startsWith(matrix.os, 'macos') + run: | + installer -dumplog -pkg $PKG_FILE -target CurrentUserHomeDirectory + app_path=$HOME/Applications/Spyder.app + if [[ -e "$app_path" ]]; then + ls -al $app_path/Contents/MacOS + cat $app_path/Contents/MacOS/Spyder + echo "" + else + echo "$app_path does not exist" + fi + + - name: Notarize package installer + if: github.event_name == 'release' && startsWith(matrix.os, 'macos') + run: ./notarize.sh -p $APPLICATION_PWD $PKG_FILE + + - name: Upload Artifact + uses: actions/upload-artifact@v2 + with: + path: ${{ env.PKG_FILE }} + name: ${{ env.PKG_NAME }} + + - name: Get Release + if: github.event_name == 'release' + id: get_release + env: + GITHUB_TOKEN: ${{ github.token }} + uses: bruceadams/get-release@v1.2.0 + + - name: Upload Release Asset + if: github.event_name == 'release' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ steps.get_release.outputs.upload_url }} + asset_path: ${{ env.PKG_FILE }} + asset_name: ${{ env.PKG_NAME }} + asset_content_type: application/octet-stream From 878e8f97f67edaf2952c9a9c7d9ca0ba6efb0bc6 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Thu, 22 Sep 2022 12:06:28 -0700 Subject: [PATCH 17/43] Update workflow paths to ignore installers-conda directory for unit tests and other installers --- .github/workflows/installer-macos.yml | 2 ++ .github/workflows/installer-win.yml | 2 ++ .github/workflows/test-files.yml | 4 ++-- .github/workflows/test-linux.yml | 4 ++-- .github/workflows/test-mac.yml | 4 ++-- .github/workflows/test-win.yml | 4 ++-- setup.cfg | 2 +- 7 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.github/workflows/installer-macos.yml b/.github/workflows/installer-macos.yml index bf1b6dce3ea..f30a4267b1e 100644 --- a/.github/workflows/installer-macos.yml +++ b/.github/workflows/installer-macos.yml @@ -10,7 +10,9 @@ on: - '**.sh' - '!**.md' - '!installers/Windows/**' + - '!installers-conda/**' - '!.github/workflows/installer-win.yml' + - '!.github/workflows/installers-conda.yml' release: types: diff --git a/.github/workflows/installer-win.yml b/.github/workflows/installer-win.yml index d41f17b0906..fc622d05c94 100644 --- a/.github/workflows/installer-win.yml +++ b/.github/workflows/installer-win.yml @@ -11,7 +11,9 @@ on: - '**.sh' - '!**.md' - '!installers/macOS/**' + - '!installers-conda/**' - '!.github/workflows/installer-macos.yml' + - '!.github/workflows/installers-conda.yml' release: types: diff --git a/.github/workflows/test-files.yml b/.github/workflows/test-files.yml index d61b393e5f8..25b3fc6d267 100644 --- a/.github/workflows/test-files.yml +++ b/.github/workflows/test-files.yml @@ -14,7 +14,7 @@ on: - '**.bat' - '**.py' - '**.sh' - - '!installers/**' + - '!installers*/**' - '!.github/workflows/installer*.yml' pull_request: @@ -30,7 +30,7 @@ on: - '**.bat' - '**.py' - '**.sh' - - '!installers/**' + - '!installers*/**' - '!.github/workflows/installer*.yml' jobs: diff --git a/.github/workflows/test-linux.yml b/.github/workflows/test-linux.yml index 2d1dee2fcd6..8eea69643f3 100644 --- a/.github/workflows/test-linux.yml +++ b/.github/workflows/test-linux.yml @@ -14,7 +14,7 @@ on: - '**.bat' - '**.py' - '**.sh' - - '!installers/**' + - '!installers*/**' - '!.github/workflows/installer*.yml' pull_request: @@ -30,7 +30,7 @@ on: - '**.bat' - '**.py' - '**.sh' - - '!installers/**' + - '!installers*/**' - '!.github/workflows/installer*.yml' jobs: diff --git a/.github/workflows/test-mac.yml b/.github/workflows/test-mac.yml index a2bcb7d5c11..c547934cb14 100644 --- a/.github/workflows/test-mac.yml +++ b/.github/workflows/test-mac.yml @@ -14,7 +14,7 @@ on: - '**.bat' - '**.py' - '**.sh' - - '!installers/**' + - '!installers*/**' - '!.github/workflows/installer*.yml' pull_request: @@ -30,7 +30,7 @@ on: - '**.bat' - '**.py' - '**.sh' - - '!installers/**' + - '!installers*/**' - '!.github/workflows/installer*.yml' jobs: diff --git a/.github/workflows/test-win.yml b/.github/workflows/test-win.yml index aebe9019a5a..80f47cad82e 100644 --- a/.github/workflows/test-win.yml +++ b/.github/workflows/test-win.yml @@ -14,7 +14,7 @@ on: - '**.bat' - '**.py' - '**.sh' - - '!installers/**' + - '!installers*/**' - '!.github/workflows/installer*.yml' pull_request: @@ -30,7 +30,7 @@ on: - '**.bat' - '**.py' - '**.sh' - - '!installers/**' + - '!installers*/**' - '!.github/workflows/installer*.yml' jobs: diff --git a/setup.cfg b/setup.cfg index 026bb4ef2be..221d71bd400 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,7 +25,7 @@ ignore = img_src/*.xcf img_src/*.pdn img_src/*.icns - installers/** + installers*/** pytest.ini requirements/** rope_profiling/** From 4a26d619a4bd08ca237636f0cf0227b2edaaf803 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Thu, 22 Sep 2022 23:38:42 -0700 Subject: [PATCH 18/43] Update patch file --- .../resources/installers-conda.patch | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/installers-conda/resources/installers-conda.patch b/installers-conda/resources/installers-conda.patch index aa364ed91b6..8111fede195 100644 --- a/installers-conda/resources/installers-conda.patch +++ b/installers-conda/resources/installers-conda.patch @@ -1,4 +1,4 @@ -From aaf106966bf500d5c2d6200e7b9f851bfb28c767 Mon Sep 17 00:00:00 2001 +From 4fc591ffa69a8f3f2b4030e4395818962be71f79 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Mon, 12 Sep 2022 23:56:56 -0700 Subject: [PATCH 1/2] Revise usage of running_in_mac_app @@ -187,21 +187,21 @@ index 91c411bdf..c274f5872 100644 self.error_output = '' self.running = True diff --git a/spyder/plugins/pylint/main_widget.py b/spyder/plugins/pylint/main_widget.py -index a76d6fbf9..df4f00dab 100644 +index aa35d27d2..7fe5793db 100644 --- a/spyder/plugins/pylint/main_widget.py +++ b/spyder/plugins/pylint/main_widget.py @@ -31,7 +31,7 @@ from spyder.api.config.decorators import on_conf_change from spyder.api.translations import get_translation from spyder.api.widgets.main_widget import PluginMainWidget --from spyder.config.base import get_conf_path, running_in_mac_app -+from spyder.config.base import get_conf_path +-from spyder.config.base import get_conf_path, is_pynsist, running_in_mac_app ++from spyder.config.base import get_conf_path, is_pynsist + from spyder.config.utils import is_anaconda from spyder.plugins.pylint.utils import get_pylintrc_path from spyder.plugins.variableexplorer.widgets.texteditor import TextEditor - from spyder.utils.icon_manager import ima -@@ -372,11 +372,6 @@ def _start(self): - user_profile = os.environ.get("USERPROFILE", home_dir) - processEnvironment.insert("USERPROFILE", user_profile) +@@ -377,11 +377,6 @@ def _start(self): + if not is_pynsist() and not is_anaconda(): + processEnvironment.insert("APPDATA", os.environ.get("APPDATA")) - # resolve spyder-ide/spyder#14262 - if running_in_mac_app(): @@ -251,7 +251,7 @@ index 49f894e7c..018e02130 100644 2.37.3 -From eadd3399de480315769cae4b97a2ebc6e26013d7 Mon Sep 17 00:00:00 2001 +From 2f66ddf6fed81c3da069761eb046c2abd52e694c Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Mon, 12 Sep 2022 23:58:36 -0700 Subject: [PATCH 2/2] Update standalone conda executable. From 052d03e06bc6c4171c77878fb17a31bd41db1dac Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Mon, 3 Oct 2022 13:11:49 -0700 Subject: [PATCH 19/43] Fix PermissionError - Put feedstock build.sh patch into separate file and apply only for posix platforms. - Build executable in build.sh patch - Allow user's shell to be determined at runtime instead of install time. - Set SPYDER_APP to runtime location of app bundle - Compile application executable for macOS at conda package build time. Requires shc - Post install script moves compiled executable to correct location and patches Info.plist Note that Spyder.sh must be compiled before or at conda-build; referenced install-time dependent variables must be included in LSEnvironment. --- .github/workflows/installers-conda.yml | 13 ++++++------ installers-conda/build_conda_pkgs.py | 23 +++++++-------------- installers-conda/resources/Spyder.sh | 6 ++++++ installers-conda/resources/build-patch.sh | 14 +++++++++++++ installers-conda/resources/post-install.sh | 24 ++++++++++++++-------- 5 files changed, 49 insertions(+), 31 deletions(-) create mode 100644 installers-conda/resources/Spyder.sh create mode 100644 installers-conda/resources/build-patch.sh diff --git a/.github/workflows/installers-conda.yml b/.github/workflows/installers-conda.yml index fd89edeb9ce..3e75c72a452 100644 --- a/.github/workflows/installers-conda.yml +++ b/.github/workflows/installers-conda.yml @@ -58,7 +58,7 @@ jobs: python build_conda_pkgs.py ${args[@]} - name: Create Keychain - if: github.event_name == 'release' && startsWith(matrix.os, 'macos') + if: github.event_name == 'release' && runner.os == 'macOS' run: ./certkeychain.sh "${MACOS_CERTIFICATE_PWD}" "${MACOS_CERTIFICATE}" "${MACOS_INSTALLER_CERTIFICATE}" - name: Build Package Installer @@ -69,28 +69,27 @@ jobs: echo "Moving $_codesign..." mv $_codesign ${_codesign}.bak fi - [[ "$GITHUB_EVENT_NAME" == "release" ]] && args=("--no-local") || args=() - python build_installers.py "${args[@]}" + python build_installers.py PKG_FILE=$(python build_installers.py --artifact-name) PKG_NAME=$(basename $PKG_FILE) echo "PKG_FILE=$PKG_FILE" >> $GITHUB_ENV echo "PKG_NAME=$PKG_NAME" >> $GITHUB_ENV - name: Test Application Bundle - if: startsWith(matrix.os, 'macos') + if: runner.os == 'macOS' run: | - installer -dumplog -pkg $PKG_FILE -target CurrentUserHomeDirectory + installer -dumplog -pkg $PKG_FILE -target CurrentUserHomeDirectory 2>&1 app_path=$HOME/Applications/Spyder.app if [[ -e "$app_path" ]]; then ls -al $app_path/Contents/MacOS - cat $app_path/Contents/MacOS/Spyder + cat $app_path/Contents/Info.plist echo "" else echo "$app_path does not exist" fi - name: Notarize package installer - if: github.event_name == 'release' && startsWith(matrix.os, 'macos') + if: github.event_name == 'release' && runner.os == 'macOS' run: ./notarize.sh -p $APPLICATION_PWD $PKG_FILE - name: Upload Artifact diff --git a/installers-conda/build_conda_pkgs.py b/installers-conda/build_conda_pkgs.py index 84505e08163..86e7d20eab6 100644 --- a/installers-conda/build_conda_pkgs.py +++ b/installers-conda/build_conda_pkgs.py @@ -152,21 +152,12 @@ def _patch_meta(self): self.yaml['source']['patches'] = patches def _patch_build(self): - file = self.fdstk_path / "recipe" / "build.sh" - text = file.read_text() - text += dedent( - """ - mkdir -p "${PREFIX}/Menu" - sed "s/__PKG_VERSION__/${PKG_VERSION}/" """ - """"${SRC_DIR}/installers-conda/resources/spyder-menu.json" """ - """> "${PREFIX}/Menu/spyder-menu.json" - cp "${SRC_DIR}/img_src/spyder.png" "${PREFIX}/Menu/spyder.png" - cp "${SRC_DIR}/img_src/spyder.icns" "${PREFIX}/Menu/spyder.icns" - cp "${SRC_DIR}/img_src/spyder.ico" "${PREFIX}/Menu/spyder.ico" - - """ - ) - file.write_text(text) + if os.name == 'posix': + file = self.fdstk_path / "recipe" / "build.sh" + build_patch = RESOURCES / "build-patch.sh" + text = file.read_text() + text += build_patch.read_text() + file.write_text(text) class PylspCondaPkg(BuildCondaPkg): @@ -237,7 +228,7 @@ class SpyderKernelsCondaPkg(BuildCondaPkg): """ ), usage="python build_conda_pkgs.py " - "[--build subrepo [subrepo] ...] [--debug]", + "[--build BUILD [BUILD] ...] [--debug]", ) p.add_argument( '--debug', action='store_true', default=False, diff --git a/installers-conda/resources/Spyder.sh b/installers-conda/resources/Spyder.sh new file mode 100644 index 00000000000..0d2930f5e9c --- /dev/null +++ b/installers-conda/resources/Spyder.sh @@ -0,0 +1,6 @@ +#!/bin/bash +eval "$($SHELL -l -c "declare -x")" + +eval "$("$ROOT_PREFIX/_conda.exe" shell.bash activate "$PREFIX")" + +$(dirname "$0")/python $CONDA_PREFIX/bin/spyder "$@" diff --git a/installers-conda/resources/build-patch.sh b/installers-conda/resources/build-patch.sh new file mode 100644 index 00000000000..ff69f9d5fb4 --- /dev/null +++ b/installers-conda/resources/build-patch.sh @@ -0,0 +1,14 @@ +mkdir -p "${PREFIX}/Menu" +sed "s/__PKG_VERSION__/${PKG_VERSION}/" "${SRC_DIR}/installers-conda/resources/spyder-menu.json" > "${PREFIX}/Menu/spyder-menu.json" +cp "${SRC_DIR}/img_src/spyder.png" "${PREFIX}/Menu/spyder.png" +cp "${SRC_DIR}/img_src/spyder.icns" "${PREFIX}/Menu/spyder.icns" +cp "${SRC_DIR}/img_src/spyder.ico" "${PREFIX}/Menu/spyder.ico" + +if [[ $OSTYPE = "darwin"* ]]; then + if [[ -z $(which shc) ]]; then + echo "Installing shc shell script compiler..." + brew install shc + fi + echo "Compiling Spyder.sh..." + shc -r -f "${SRC_DIR}/installers-conda/resources/Spyder.sh" -o "${PREFIX}/Menu/Spyder" +fi diff --git a/installers-conda/resources/post-install.sh b/installers-conda/resources/post-install.sh index a40a996dfca..a1771860f20 100755 --- a/installers-conda/resources/post-install.sh +++ b/installers-conda/resources/post-install.sh @@ -35,18 +35,26 @@ echo "Args = $@" echo "$(declare -p)" if [[ -e "$app_path" ]]; then + if [[ ! -e "/usr/libexec/PlistBuddy" ]]; then + echo "/usr/libexec/PlistBuddy not installed" + exit 1 + fi + echo "Creating python symbolic link..." ln -sf "$PREFIX/bin/python" "$app_path/Contents/MacOS/python" echo "Modifying application executable..." -cat < $app_path/Contents/MacOS/__NAME__ -#!/bin/bash -eval "\$(/bin/bash -l -c "declare -x")" -eval "\$("$ROOT_PREFIX/_conda.exe" shell.bash activate "$PREFIX")" -export SPYDER_APP=0 -\$(dirname \$BASH_SOURCE)/python $PREFIX/bin/spyder "\$@" - -EOF + cp -fp "$PREFIX/Menu/__NAME__" "$app_path/Contents/MacOS/__NAME__" + + echo "Patching Info.plist..." + plist=$app_path/Contents/Info.plist + /usr/libexec/PlistBuddy -c "Add :LSEnvironment dict" $plist || true + /usr/libexec/PlistBuddy -c "Add :LSEnvironment:ROOT_PREFIX string" $plist || true + /usr/libexec/PlistBuddy -c "Set :LSEnvironment:ROOT_PREFIX $ROOT_PREFIX" $plist + /usr/libexec/PlistBuddy -c "Add :LSEnvironment:PREFIX string" $plist || true + /usr/libexec/PlistBuddy -c "Set :LSEnvironment:PREFIX $PREFIX" $plist + /usr/libexec/PlistBuddy -c "Add :LSEnvironment:SPYDER_APP string" $plist || true + /usr/libexec/PlistBuddy -c "Set :LSEnvironment:SPYDER_APP $app_path" $plist else echo "$app_path does not exist" fi From 78a03feffa542af0114ff7b0c2c4fa0cede2804a Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Tue, 27 Sep 2022 10:25:51 -0700 Subject: [PATCH 20/43] Make certificate id an argument rather than environment variable --- .github/workflows/installers-conda.yml | 7 +++++-- installers-conda/build_installers.py | 20 +++++++------------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/.github/workflows/installers-conda.yml b/.github/workflows/installers-conda.yml index 3e75c72a452..543aa177352 100644 --- a/.github/workflows/installers-conda.yml +++ b/.github/workflows/installers-conda.yml @@ -59,7 +59,10 @@ jobs: - name: Create Keychain if: github.event_name == 'release' && runner.os == 'macOS' - run: ./certkeychain.sh "${MACOS_CERTIFICATE_PWD}" "${MACOS_CERTIFICATE}" "${MACOS_INSTALLER_CERTIFICATE}" + run: | + ./certkeychain.sh "${MACOS_CERTIFICATE_PWD}" "${MACOS_CERTIFICATE}" "${MACOS_INSTALLER_CERTIFICATE}" + CNAME=$(security find-identity -p codesigning -v | pcregrep -o1 "\(([0-9A-Z]+)\)") + echo "CNAME=$CNAME" >> $GITHUB_ENV - name: Build Package Installer run: | @@ -69,7 +72,7 @@ jobs: echo "Moving $_codesign..." mv $_codesign ${_codesign}.bak fi - python build_installers.py + python build_installers.py --cert-id=$CNAME PKG_FILE=$(python build_installers.py --artifact-name) PKG_NAME=$(basename $PKG_FILE) echo "PKG_FILE=$PKG_FILE" >> $GITHUB_ENV diff --git a/installers-conda/build_installers.py b/installers-conda/build_installers.py index 82c454bc48a..6d4278455ae 100644 --- a/installers-conda/build_installers.py +++ b/installers-conda/build_installers.py @@ -12,12 +12,6 @@ when the target platform is not the same as the host, constructor needs a path to a conda-standalone (or micromamba) executable for that platform. needs to be provided in this env var in that case! -CONSTRUCTOR_SIGNING_IDENTITY: - Apple ID Installer Certificate identity (common name) that should - be use to productsign the resulting PKG (macOS only) -CONSTRUCTOR_NOTARIZATION_IDENTITY: - Apple ID Developer Certificate identity (common name) that should - be use to codesign some binaries bundled in the pkg (macOS only) CONSTRUCTOR_SIGNING_CERTIFICATE: Path to PFX certificate to sign the EXE installer on Windows """ @@ -121,6 +115,10 @@ def _version(): "--images", action="store_true", help="Generate background images from the logo (test only)", ) +p.add_argument( + "--cert-id", default=None, + help="Apple Developer ID Application certificate common name." +) args = p.parse_args() OUTPUT_FILE = DIST / f"{APP}-{args.version}-{OS}-{ARCH}.{EXT}" @@ -250,13 +248,9 @@ def _definitions(): "post_install": str(RESOURCES / "post-install.sh"), } ) - signing_identity = os.environ.get("CONSTRUCTOR_SIGNING_IDENTITY") - if signing_identity: - definitions["signing_identity_name"] = signing_identity - notarization_identity = \ - os.environ.get("CONSTRUCTOR_NOTARIZATION_IDENTITY") - if notarization_identity: - definitions["notarization_identity_name"] = notarization_identity + if args.cert_id: + definitions["signing_identity_name"] = args.cert_id + definitions["notarization_identity_name"] = args.cert_id if WINDOWS: definitions["conda_default_channels"].append("defaults") From 0885d0e2e9d75aaee474d34cae6ef69e466c98cb Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Tue, 4 Oct 2022 17:46:40 -0700 Subject: [PATCH 21/43] Only move codesign if creating keychain. Ensure only signing on release (secrets not available on forked branch PRs). Fix build package logic --- .github/workflows/installers-conda.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/installers-conda.yml b/.github/workflows/installers-conda.yml index 543aa177352..2e69e88bddf 100644 --- a/.github/workflows/installers-conda.yml +++ b/.github/workflows/installers-conda.yml @@ -54,7 +54,7 @@ jobs: - name: Build Conda Packages run: | - [[ $GITHUB_EVENT_NAME != "release" ]] && args=("--build" "spyder") || args=() + [[ $GITHUB_EVENT_NAME = "release" ]] && args=("--build" "spyder") || args=() python build_conda_pkgs.py ${args[@]} - name: Create Keychain @@ -64,15 +64,17 @@ jobs: CNAME=$(security find-identity -p codesigning -v | pcregrep -o1 "\(([0-9A-Z]+)\)") echo "CNAME=$CNAME" >> $GITHUB_ENV - - name: Build Package Installer - run: | _codesign=$(which codesign) if [[ $_codesign =~ ${CONDA_PREFIX}.* ]]; then # Find correct codesign echo "Moving $_codesign..." mv $_codesign ${_codesign}.bak fi - python build_installers.py --cert-id=$CNAME + + - name: Build Package Installer + run: | + [[ -n $CNAME ]] && args=("--cert-id" "$CNAME") || args=() + python build_installers.py ${args[@]} PKG_FILE=$(python build_installers.py --artifact-name) PKG_NAME=$(basename $PKG_FILE) echo "PKG_FILE=$PKG_FILE" >> $GITHUB_ENV From 390e53be17451051823a065a5a33805b5a108e75 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Thu, 29 Sep 2022 07:17:21 -0700 Subject: [PATCH 22/43] Explicitly use spyder and subrepo versions for build specs - Get shallow repo for all external-deps - Provide environment variable for alternative source for all external-deps - Use scm to get version for all repos - Write all specs to specs.yaml, used by installer - Alternative environment activation in application executable provides accurate environment variables; find environment root conda/mamba --- installers-conda/build_conda_pkgs.py | 161 +++++++++++++++++---------- installers-conda/build_installers.py | 57 +++++----- installers-conda/resources/Spyder.sh | 8 +- 3 files changed, 142 insertions(+), 84 deletions(-) diff --git a/installers-conda/build_conda_pkgs.py b/installers-conda/build_conda_pkgs.py index 86e7d20eab6..f66c370f764 100644 --- a/installers-conda/build_conda_pkgs.py +++ b/installers-conda/build_conda_pkgs.py @@ -1,12 +1,35 @@ """ -Build local conda packages +Build conda packages to local channel. + +This module builds conda packages for spyder and external-deps for +inclusion in the conda-based installer. The Following classes are +provided for each package: + SpyderCondaPkg + PylspCondaPkg + QdarkstyleCondaPkg + QtconsoleCondaPkg + SpyderKernelsCondaPkg + +spyder will be packaged from this repository (in its checked-out state). +qdarkstyle, qtconsole, and spyder-kernels will be packaged from the +external-deps directory of this repository (in its checked-out state). +python-lsp-server, however, will be packaged from the upstream remote +at the same commit as the external-deps state. + +Alternatively, any external-deps may be packaged from a local git repository +(in its checked out state) by setting the appropriate environment variable +from the following: + PYTHON_LSP_SERVER_SOURCE + QDARKSTYLE_SOURCE + QTCONSOLE_SOURCE + SPYDER_KERNELS_SOURCE """ import os import re from argparse import ArgumentParser +from configparser import ConfigParser from datetime import timedelta from git import Repo -from importlib.util import spec_from_file_location, module_from_spec from logging import Formatter, StreamHandler, getLogger from pathlib import Path from ruamel.yaml import YAML @@ -24,14 +47,22 @@ logger.setLevel('INFO') HERE = Path(__file__).parent +DIST = HERE / "dist" RESOURCES = HERE / "resources" EXTDEPS = HERE.parent / "external-deps" +SPECS = DIST / "specs.yaml" + +DIST.mkdir(exist_ok=True) class BuildCondaPkg(): + name = None src_path = None feedstock = None - ver_path = None + shallow_ver = None + + _yaml = YAML(typ='jinja2') + _yaml.indent(mapping=2, sequence=4, offset=2) def __init__(self, data={}, debug=False): # ---- Setup logger @@ -42,24 +73,39 @@ def __init__(self, data={}, debug=False): self.debug = debug - self.data = {'version': self._get_version()} + self._get_source() + self._get_version() + + self.data = {'version': self.version} self.data.update(data) self.fdstk_path = HERE / self.feedstock.split("/")[-1] - self._yaml = YAML(typ='jinja2') - self._yaml.indent(mapping=2, sequence=4, offset=2) self.yaml = None self._patched_meta = False self._patched_build = False + def _get_source(self): + self._build_cleanup() # Remove existing if HERE + + if not self.src_path.exists(): + cfg = ConfigParser() + cfg.read(EXTDEPS / self.name / '.gitrepo') + # Clone from remote + repo = Repo.clone_from( + cfg['subrepo']['remote'], + to_path=self.src_path, shallow_exclude=self.shallow_ver + ) + repo.git.checkout(cfg['subrepo']['commit']) + + def _build_cleanup(self): + if self.src_path == HERE / self.name: + logger.info(f"Removing {self.src_path}...") + rmtree(self.src_path, ignore_errors=True) + def _get_version(self): - spec = spec_from_file_location(self.ver_path.parent.name, - self.ver_path) - mod = module_from_spec(spec) - spec.loader.exec_module(mod) - return mod.__version__ + self.version = get_version(self.src_path).split('+')[0] def _clone_feedstock(self): if self.fdstk_path.exists(): @@ -109,9 +155,6 @@ def patch_build(self): self._patch_build() self._patched_build = True - def _build_cleanup(self): - pass - def build(self): t0 = time() try: @@ -120,7 +163,8 @@ def build(self): self.patch_meta() self.patch_build() - self.logger.info("Building conda package...") + self.logger.info("Building conda package " + f"{self.name}={self.version}...") check_call( ["mamba", "mambabuild", str(self.fdstk_path / "recipe")] ) @@ -139,9 +183,10 @@ def build(self): class SpyderCondaPkg(BuildCondaPkg): + name = "spyder" src_path = HERE.parent feedstock = "https://github.com/conda-forge/spyder-feedstock" - ver_path = src_path / "spyder" / "__init__.py" + shallow_ver = "v5.3.2" def _patch_meta(self): self.yaml['build'].pop('osx_is_app', None) @@ -161,41 +206,30 @@ def _patch_build(self): class PylspCondaPkg(BuildCondaPkg): - src_path = Path(os.environ.get('PYTHON_LSP_SERVER_SOURCE', - HERE / "python-lsp-server")) + name = "python-lsp-server" + src_path = Path( + os.environ.get('PYTHON_LSP_SERVER_SOURCE', HERE / name) + ) feedstock = "https://github.com/conda-forge/python-lsp-server-feedstock" - - def _get_version(self): - self._build_cleanup() # Remove existing if HERE - - if not self.src_path.exists(): - # Clone from remote - Repo.clone_from( - "https://github.com/python-lsp/python-lsp-server.git", - to_path=self.src_path, shallow_exclude="v1.4.1" - ) - return get_version(self.src_path) - - def _build_cleanup(self): - if self.src_path == HERE / "python-lsp-server": - logger.info(f"Removing {self.src_path}...") - rmtree(self.src_path, ignore_errors=True) + shallow_ver = "v1.4.1" class QdarkstyleCondaPkg(BuildCondaPkg): - src_path = EXTDEPS / "qdarkstyle" + name = "qdarkstyle" + src_path = Path( + os.environ.get('QDARKSTYLE_SOURCE', HERE / name) + ) feedstock = "https://github.com/conda-forge/qdarkstyle-feedstock" - ver_path = src_path / "qdarkstyle" / "__init__.py" - - def _get_version(self): - text = self.ver_path.read_text() - return re.search('__version__ = "(.*)"', text).group(1) + shallow_ver = "v3.0.2" class QtconsoleCondaPkg(BuildCondaPkg): - src_path = EXTDEPS / "qtconsole" + name = "qtconsole" + src_path = Path( + os.environ.get('QTCONSOLE_SOURCE', HERE / name) + ) feedstock = "https://github.com/conda-forge/qtconsole-feedstock" - ver_path = src_path / "qtconsole" / "_version.py" + shallow_ver = "5.3.1" def _patch_meta(self): for out in self.yaml['outputs']: @@ -203,20 +237,23 @@ def _patch_meta(self): class SpyderKernelsCondaPkg(BuildCondaPkg): - src_path = EXTDEPS / "spyder-kernels" + name = "spyder-kernels" + src_path = Path( + os.environ.get('SPYDER_KERNELS_SOURCE', HERE / name) + ) feedstock = "https://github.com/conda-forge/spyder-kernels-feedstock" - ver_path = src_path / "spyder_kernels" / "_version.py" + shallow_ver = "v2.3.1" -if __name__ == "__main__": - repos = { - "spyder": SpyderCondaPkg, - "python-lsp-server": PylspCondaPkg, - "qdarkstyle": QdarkstyleCondaPkg, - "qtconsole": QtconsoleCondaPkg, - "spyder-kernels": SpyderKernelsCondaPkg - } +PKGS = { + SpyderCondaPkg.name: SpyderCondaPkg, + PylspCondaPkg.name: PylspCondaPkg, + QdarkstyleCondaPkg.name: QdarkstyleCondaPkg, + QtconsoleCondaPkg.name: QtconsoleCondaPkg, + SpyderKernelsCondaPkg.name: SpyderKernelsCondaPkg +} +if __name__ == "__main__": p = ArgumentParser( description=dedent( """ @@ -235,17 +272,29 @@ class SpyderKernelsCondaPkg(BuildCondaPkg): help="Do not remove cloned feedstocks" ) p.add_argument( - '--build', nargs="+", default=repos.keys(), - help=("Space-separated list of repos to build. " - f"Default is {list(repos.keys())}") + '--build', nargs="+", default=PKGS.keys(), + help=("Space-separated list of packages to build. " + f"Default is {list(PKGS.keys())}") ) args = p.parse_args() logger.info(f"Building local conda packages {list(args.build)}...") t0 = time() + yaml = YAML() + yaml.indent(mapping=2, sequence=4, offset=2) + for k in args.build: - repos[k](debug=args.debug).build() + if SPECS.exists(): + specs = yaml.load(SPECS.read_text()) + else: + specs = {k: "" for k in PKGS} + + pkg = PKGS[k](debug=args.debug) + pkg.build() + specs[k] = pkg.version + + yaml.dump(specs, SPECS) elapse = timedelta(seconds=int(time() - t0)) logger.info(f"Total build time = {elapse}") diff --git a/installers-conda/build_installers.py b/installers-conda/build_installers.py index 6d4278455ae..8e495674b50 100644 --- a/installers-conda/build_installers.py +++ b/installers-conda/build_installers.py @@ -19,23 +19,22 @@ import json import os import platform +import re import sys import zipfile from argparse import ArgumentParser from datetime import timedelta from distutils.spawn import find_executable from functools import partial -from importlib.util import spec_from_file_location, module_from_spec -from logging import Formatter, StreamHandler, getLogger +from logging import getLogger from pathlib import Path from ruamel.yaml import YAML from subprocess import check_call from textwrap import dedent, indent from time import time -fmt = Formatter('%(asctime)s [%(levelname)s] [%(name)s] -> %(message)s') -h = StreamHandler() -h.setFormatter(fmt) +from build_conda_pkgs import HERE, DIST, RESOURCES, SPECS, h, get_version, PKGS + logger = getLogger('BuildInstallers') logger.addHandler(h) logger.setLevel('INFO') @@ -45,9 +44,6 @@ indent4 = partial(indent, prefix=" ") APP = "Spyder" -HERE = Path(__file__).parent -RESOURCES = HERE / "resources" -DIST = HERE / "dist" SPYREPO = HERE.parent WINDOWS = os.name == "nt" MACOS = sys.platform == "darwin" @@ -67,21 +63,8 @@ else: raise RuntimeError(f"Unrecognized OS: {sys.platform}") - -def _version(): - spec = spec_from_file_location( - "spyder", SPYREPO / "spyder" / "__init__.py") - mod = module_from_spec(spec) - spec.loader.exec_module(mod) - return mod.__version__ - - # ---- Parse arguments p = ArgumentParser() -p.add_argument( - "--version", default=_version(), - help="Specify Spyder version; default is determined from source code" -) p.add_argument( "--no-local", action="store_true", help="Do not use local conda packages" @@ -121,8 +104,28 @@ def _version(): ) args = p.parse_args() -OUTPUT_FILE = DIST / f"{APP}-{args.version}-{OS}-{ARCH}.{EXT}" -INSTALLER_DEFAULT_PATH_STEM = f"{APP}-{args.version}" +SPYVER = get_version(SPYREPO).strip().split("+")[0] + +try: + specs = yaml.load(SPECS.read_text()) +except Exception: + specs = {k: "" for k in PKGS} + +for spec in args.extra_specs: + k, *v = re.split('([<>= ]+)', spec) + specs[k] = "".join(v).strip() or "" + if k == "spyder": + if v[-1]: + SPYVER = v[-1] + else: + specs[k] = SPYVER + +for k, v in specs.items(): + if not v.startswith(("<", ">", "=")): + specs[k] = "=" + v + +OUTPUT_FILE = DIST / f"{APP}-{SPYVER}-{OS}-{ARCH}.{EXT}" +INSTALLER_DEFAULT_PATH_STEM = f"{APP}-{SPYVER}" def _generate_background_images(installer_type): @@ -188,7 +191,7 @@ def _definitions(): "name": APP, "company": "Spyder-IDE", "reverse_domain_identifier": "org.spyder-ide.Spyder", - "version": args.version.replace("+", "_"), + "version": SPYVER, "channels": [ "napari/label/bundle_tools", "spyder-ide", @@ -205,8 +208,8 @@ def _definitions(): "initialize_by_default": False, "license_file": str(RESOURCES / "bundle_license.rtf"), "extra_envs": { - f"spyder-{args.version}": { - "specs": [f"spyder={args.version}"] + args.extra_specs, + f"spyder-{SPYVER}": { + "specs": [k + v for k, v in specs.items()], }, }, "menu_packages": [ @@ -232,7 +235,7 @@ def _definitions(): (RESOURCES / "osx_pkg_welcome.rtf.tmpl").read_text() welcome_file = DIST / "osx_pkg_welcome.rtf" welcome_file.write_text( - welcome_text_tmpl.replace("__VERSION__", args.version)) + welcome_text_tmpl.replace("__VERSION__", SPYVER)) # These two options control the default install location: # ~// diff --git a/installers-conda/resources/Spyder.sh b/installers-conda/resources/Spyder.sh index 0d2930f5e9c..679e11553cb 100644 --- a/installers-conda/resources/Spyder.sh +++ b/installers-conda/resources/Spyder.sh @@ -1,6 +1,12 @@ #!/bin/bash +# Get user environment variables eval "$($SHELL -l -c "declare -x")" -eval "$("$ROOT_PREFIX/_conda.exe" shell.bash activate "$PREFIX")" +# Activate the conda environment +source $ROOT_PREFIX/bin/activate $PREFIX +# Find root conda and mamba +export PATH=$ROOT_PREFIX/condabin:$PATH + +# Launch Spyder $(dirname "$0")/python $CONDA_PREFIX/bin/spyder "$@" From 8bcdb65b0684097c5a7a38c0b0b783ad1bb33c19 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Thu, 29 Sep 2022 23:21:55 -0700 Subject: [PATCH 23/43] Test matrix workflow --- .github/workflows/installers-conda.yml | 186 ++++++++++++++++--------- 1 file changed, 119 insertions(+), 67 deletions(-) diff --git a/.github/workflows/installers-conda.yml b/.github/workflows/installers-conda.yml index 2e69e88bddf..1b90fe1c629 100644 --- a/.github/workflows/installers-conda.yml +++ b/.github/workflows/installers-conda.yml @@ -20,15 +20,119 @@ on: name: Create conda-based installers for Windows, macOS, and Linux jobs: - build: + build-spyder-conda-pkg: + name: Build spyder for ${{ matrix.target-platform }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + target-platform: ["osx-64", "osx-arm64"] + python-version: ["3.9"] + include: + - os: macos-11 + target-platform: "osx-64" + - os: macos-latest + target-platform: "osx-arm64" + defaults: + run: + shell: bash -l {0} + working-directory: ${{ github.workspace }}/installers-conda + env: + DISTDIR: ${{ github.workspace }}/installers-conda/dist + artifact_name: spyder_${{ matrix.platform }}_${{ matrix.python-version }} + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup Build Environment + uses: mamba-org/provision-with-micromamba@main + with: + environment-file: installers-conda/build-environment.yml + extra-specs: python=${{ matrix.python-version }} + + - name: Build Spyder Conda Package + run: python build_conda_pkgs.py --build spyder + + - name: Build Artifact + run: tar -a -C $CONDA_PREFIX -cf $PWD/${artifact_name}.tar.bz2 conda-bld + + - name: Upload Artifact + uses: actions/upload-artifact@v3 + with: + path: ${{ github.workspace }}/installers-conda/${{ env.artifact_name }}.tar.bz2 + name: ${{ env.artifact_name }} + + build-conda-pkgs: + name: Build ${{ matrix.pkg }} for ${{ matrix.target-platform }} + runs-on: ${{ matrix.os }} + if: github.event_name != 'release' + strategy: + matrix: + target-platform: ["osx-64", "osx-arm64"] + pkg: ["spyder-kernels"] + python-version: ["3.9"] + include: + - os: macos-11 + target-platform: "osx-64" + - os: macos-latest + target-platform: "osx-arm64" + - os: macos-11 + target-platform: "osx-64" + pkg: "python-lsp-server" + - os: macos-11 + target-platform: "osx-64" + pkg: "qdarkstyle" + - os: macos-11 + target-platform: "osx-64" + pkg: "qtconsole" + defaults: + run: + shell: bash -l {0} + working-directory: ${{ github.workspace }}/installers-conda + env: + DISTDIR: ${{ github.workspace }}/installers-conda/dist + pkg: ${{ matrix.pkg }} + artifact_name: ${{ matrix.pkg }}_${{ matrix.platform }}_${{ matrix.python-version }} + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup Build Environment + uses: mamba-org/provision-with-micromamba@main + with: + environment-file: installers-conda/build-environment.yml + extra-specs: python=${{ matrix.python-version }} + + - name: Build Conda Packages + run: python build_conda_pkgs.py --build $pkg + + - name: Build Artifact + run: tar -a -C $CONDA_PREFIX -cf $PWD/${artifact_name}.tar.bz2 conda-bld + + - name: Upload Artifact + uses: actions/upload-artifact@v3 + with: + path: ${{ github.workspace }}/installers-conda/${{ env.artifact_name }}.tar.bz2 + name: ${{ env.artifact_name }} + + build-installers: name: Build installer for ${{ matrix.target-platform }} runs-on: ${{ matrix.os }} + needs: [build-spyder-conda-pkg, build-conda-pkgs] strategy: matrix: include: - os: macos-11 python-version: "3.9" target-platform: "osx-64" + - os: macos-latest + python-version: "3.9" + target-platform: "osx-arm64" defaults: run: shell: bash -l {0} @@ -42,7 +146,7 @@ jobs: CONSTRUCTOR_TARGET_PLATFORM: ${{ matrix.target-platform }} steps: - name: Checkout Code - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 @@ -52,71 +156,19 @@ jobs: environment-file: installers-conda/build-environment.yml extra-specs: python=${{ matrix.python-version }} - - name: Build Conda Packages - run: | - [[ $GITHUB_EVENT_NAME = "release" ]] && args=("--build" "spyder") || args=() - python build_conda_pkgs.py ${args[@]} + - name: Download Local Conda Packages + uses: actions/download-artifact@v3 + with: + path: ${{ github.workspace }}/installers-conda/artifacts - - name: Create Keychain - if: github.event_name == 'release' && runner.os == 'macOS' - run: | - ./certkeychain.sh "${MACOS_CERTIFICATE_PWD}" "${MACOS_CERTIFICATE}" "${MACOS_INSTALLER_CERTIFICATE}" - CNAME=$(security find-identity -p codesigning -v | pcregrep -o1 "\(([0-9A-Z]+)\)") - echo "CNAME=$CNAME" >> $GITHUB_ENV - - _codesign=$(which codesign) - if [[ $_codesign =~ ${CONDA_PREFIX}.* ]]; then - # Find correct codesign - echo "Moving $_codesign..." - mv $_codesign ${_codesign}.bak - fi - - - name: Build Package Installer - run: | - [[ -n $CNAME ]] && args=("--cert-id" "$CNAME") || args=() - python build_installers.py ${args[@]} - PKG_FILE=$(python build_installers.py --artifact-name) - PKG_NAME=$(basename $PKG_FILE) - echo "PKG_FILE=$PKG_FILE" >> $GITHUB_ENV - echo "PKG_NAME=$PKG_NAME" >> $GITHUB_ENV - - - name: Test Application Bundle - if: runner.os == 'macOS' + - name: Create Local Conda Channel run: | - installer -dumplog -pkg $PKG_FILE -target CurrentUserHomeDirectory 2>&1 - app_path=$HOME/Applications/Spyder.app - if [[ -e "$app_path" ]]; then - ls -al $app_path/Contents/MacOS - cat $app_path/Contents/Info.plist - echo "" - else - echo "$app_path does not exist" - fi - - - name: Notarize package installer - if: github.event_name == 'release' && runner.os == 'macOS' - run: ./notarize.sh -p $APPLICATION_PWD $PKG_FILE + files=($(ls artifacts)) + echo $files + for file in $files; do + tar -C $CONDA_PREFIX -xf $file + done - - name: Upload Artifact - uses: actions/upload-artifact@v2 - with: - path: ${{ env.PKG_FILE }} - name: ${{ env.PKG_NAME }} - - - name: Get Release - if: github.event_name == 'release' - id: get_release - env: - GITHUB_TOKEN: ${{ github.token }} - uses: bruceadams/get-release@v1.2.0 - - - name: Upload Release Asset - if: github.event_name == 'release' - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ github.token }} - with: - upload_url: ${{ steps.get_release.outputs.upload_url }} - asset_path: ${{ env.PKG_FILE }} - asset_name: ${{ env.PKG_NAME }} - asset_content_type: application/octet-stream + mamba index $CONDA_PREFIX/conda-bld + + mamba search -c $CONDA_PREFIX/conda-bld --override-channels From ad21ca0da2b4818220f5d49a451ab941d7ccbd48 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Fri, 30 Sep 2022 07:33:47 -0700 Subject: [PATCH 24/43] Adjust matrix - Fix matrix.platform - Fix python-version for some matrix elements --- .github/workflows/installers-conda.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/installers-conda.yml b/.github/workflows/installers-conda.yml index 1b90fe1c629..657be41f229 100644 --- a/.github/workflows/installers-conda.yml +++ b/.github/workflows/installers-conda.yml @@ -38,7 +38,7 @@ jobs: working-directory: ${{ github.workspace }}/installers-conda env: DISTDIR: ${{ github.workspace }}/installers-conda/dist - artifact_name: spyder_${{ matrix.platform }}_${{ matrix.python-version }} + artifact_name: spyder_${{ matrix.target-platform }}_${{ matrix.python-version }} steps: - name: Checkout Code @@ -80,12 +80,15 @@ jobs: target-platform: "osx-arm64" - os: macos-11 target-platform: "osx-64" + python-version: "3.9" pkg: "python-lsp-server" - os: macos-11 target-platform: "osx-64" + python-version: "3.9" pkg: "qdarkstyle" - os: macos-11 target-platform: "osx-64" + python-version: "3.9" pkg: "qtconsole" defaults: run: @@ -94,7 +97,7 @@ jobs: env: DISTDIR: ${{ github.workspace }}/installers-conda/dist pkg: ${{ matrix.pkg }} - artifact_name: ${{ matrix.pkg }}_${{ matrix.platform }}_${{ matrix.python-version }} + artifact_name: ${{ matrix.pkg }}_${{ matrix.target-platform }}_${{ matrix.python-version }} steps: - name: Checkout Code From 25f65fa04f0d72a73ea614a0fc53397a118d659a Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Fri, 30 Sep 2022 09:16:09 -0700 Subject: [PATCH 25/43] Fix list of files to untar --- .github/workflows/installers-conda.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/installers-conda.yml b/.github/workflows/installers-conda.yml index 657be41f229..a53e43025df 100644 --- a/.github/workflows/installers-conda.yml +++ b/.github/workflows/installers-conda.yml @@ -166,9 +166,9 @@ jobs: - name: Create Local Conda Channel run: | - files=($(ls artifacts)) - echo $files - for file in $files; do + files=($(find $PWD/artifacts -name *.tar.bz2)) + echo ${files[@]} + for file in ${files[@]}; do tar -C $CONDA_PREFIX -xf $file done From 509886a95238381c1b4fe029aa9f6fa91bf4e70a Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Fri, 30 Sep 2022 09:55:19 -0700 Subject: [PATCH 26/43] Restore build steps --- .github/workflows/installers-conda.yml | 64 ++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/.github/workflows/installers-conda.yml b/.github/workflows/installers-conda.yml index a53e43025df..6657e3e056b 100644 --- a/.github/workflows/installers-conda.yml +++ b/.github/workflows/installers-conda.yml @@ -175,3 +175,67 @@ jobs: mamba index $CONDA_PREFIX/conda-bld mamba search -c $CONDA_PREFIX/conda-bld --override-channels + + - name: Create Keychain + if: github.event_name == 'release' && runner.os == 'macOS' + run: | + ./certkeychain.sh "${MACOS_CERTIFICATE_PWD}" "${MACOS_CERTIFICATE}" "${MACOS_INSTALLER_CERTIFICATE}" + CNAME=$(security find-identity -p codesigning -v | pcregrep -o1 "\(([0-9A-Z]+)\)") + echo "CNAME=$CNAME" >> $GITHUB_ENV + + _codesign=$(which codesign) + if [[ $_codesign =~ ${CONDA_PREFIX}.* ]]; then + # Find correct codesign + echo "Moving $_codesign..." + mv $_codesign ${_codesign}.bak + fi + + - name: Build Package Installer + run: | + [[ -n $CNAME ]] && args=("--cert-id" "$CNAME") || args=() + python build_installers.py ${args[@]} + PKG_FILE=$(python build_installers.py --artifact-name) + PKG_NAME=$(basename $PKG_FILE) + echo "PKG_FILE=$PKG_FILE" >> $GITHUB_ENV + echo "PKG_NAME=$PKG_NAME" >> $GITHUB_ENV + + - name: Test Application Bundle + if: runner.os == 'macOS' + run: | + installer -dumplog -pkg $PKG_FILE -target CurrentUserHomeDirectory 2>&1 + app_path=$HOME/Applications/Spyder.app + if [[ -e "$app_path" ]]; then + ls -al $app_path/Contents/MacOS + cat $app_path/Contents/Info.plist + echo "" + else + echo "$app_path does not exist" + fi + + - name: Notarize package installer + if: github.event_name == 'release' && runner.os == 'macOS' + run: ./notarize.sh -p $APPLICATION_PWD $PKG_FILE + + - name: Upload Artifact + uses: actions/upload-artifact@v2 + with: + path: ${{ env.PKG_FILE }} + name: ${{ env.PKG_NAME }} + + - name: Get Release + if: github.event_name == 'release' + id: get_release + env: + GITHUB_TOKEN: ${{ github.token }} + uses: bruceadams/get-release@v1.2.0 + + - name: Upload Release Asset + if: github.event_name == 'release' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ steps.get_release.outputs.upload_url }} + asset_path: ${{ env.PKG_FILE }} + asset_name: ${{ env.PKG_NAME }} + asset_content_type: application/octet-stream From 36cf00aad07a0ff16620aba1e35e8fb870434750 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Fri, 30 Sep 2022 11:00:02 -0700 Subject: [PATCH 27/43] Revise specs handling - Always explicitly specify spyder version - Only add spec relation when specs file is read --- installers-conda/build_installers.py | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/installers-conda/build_installers.py b/installers-conda/build_installers.py index 8e495674b50..ea106a33075 100644 --- a/installers-conda/build_installers.py +++ b/installers-conda/build_installers.py @@ -33,16 +33,12 @@ from textwrap import dedent, indent from time import time -from build_conda_pkgs import HERE, DIST, RESOURCES, SPECS, h, get_version, PKGS +from build_conda_pkgs import HERE, DIST, RESOURCES, SPECS, h, get_version logger = getLogger('BuildInstallers') logger.addHandler(h) logger.setLevel('INFO') -yaml = YAML() -yaml.indent(mapping=2, sequence=4, offset=2) -indent4 = partial(indent, prefix=" ") - APP = "Spyder" SPYREPO = HERE.parent WINDOWS = os.name == "nt" @@ -104,25 +100,25 @@ ) args = p.parse_args() +yaml = YAML() +yaml.indent(mapping=2, sequence=4, offset=2) +indent4 = partial(indent, prefix=" ") + SPYVER = get_version(SPYREPO).strip().split("+")[0] +specs = {"spyder": "=" + SPYVER} try: - specs = yaml.load(SPECS.read_text()) + logger.info(f"Reading specs from {SPECS}...") + _specs = yaml.load(SPECS.read_text()) + specs.update({k: "=" + v for k, v in _specs.items()}) except Exception: - specs = {k: "" for k in PKGS} + logger.info(f"Did not read specs from {SPECS}") for spec in args.extra_specs: k, *v = re.split('([<>= ]+)', spec) - specs[k] = "".join(v).strip() or "" + specs[k] = "".join(v).strip() if k == "spyder": - if v[-1]: - SPYVER = v[-1] - else: - specs[k] = SPYVER - -for k, v in specs.items(): - if not v.startswith(("<", ">", "=")): - specs[k] = "=" + v + SPYVER = v[-1] OUTPUT_FILE = DIST / f"{APP}-{SPYVER}-{OS}-{ARCH}.{EXT}" INSTALLER_DEFAULT_PATH_STEM = f"{APP}-{SPYVER}" From 1f57fd00f0d39f2ffc391827629a7664a15a7926 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Tue, 4 Oct 2022 20:05:04 -0700 Subject: [PATCH 28/43] Refactor matrix jobs: noarch conda builds, followed by platform dependent builds (conda and installer) --- .github/workflows/installers-conda.yml | 86 +++++--------------------- 1 file changed, 16 insertions(+), 70 deletions(-) diff --git a/.github/workflows/installers-conda.yml b/.github/workflows/installers-conda.yml index 6657e3e056b..c02e9261826 100644 --- a/.github/workflows/installers-conda.yml +++ b/.github/workflows/installers-conda.yml @@ -20,76 +20,14 @@ on: name: Create conda-based installers for Windows, macOS, and Linux jobs: - build-spyder-conda-pkg: - name: Build spyder for ${{ matrix.target-platform }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - target-platform: ["osx-64", "osx-arm64"] - python-version: ["3.9"] - include: - - os: macos-11 - target-platform: "osx-64" - - os: macos-latest - target-platform: "osx-arm64" - defaults: - run: - shell: bash -l {0} - working-directory: ${{ github.workspace }}/installers-conda - env: - DISTDIR: ${{ github.workspace }}/installers-conda/dist - artifact_name: spyder_${{ matrix.target-platform }}_${{ matrix.python-version }} - - steps: - - name: Checkout Code - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Setup Build Environment - uses: mamba-org/provision-with-micromamba@main - with: - environment-file: installers-conda/build-environment.yml - extra-specs: python=${{ matrix.python-version }} - - - name: Build Spyder Conda Package - run: python build_conda_pkgs.py --build spyder - - - name: Build Artifact - run: tar -a -C $CONDA_PREFIX -cf $PWD/${artifact_name}.tar.bz2 conda-bld - - - name: Upload Artifact - uses: actions/upload-artifact@v3 - with: - path: ${{ github.workspace }}/installers-conda/${{ env.artifact_name }}.tar.bz2 - name: ${{ env.artifact_name }} - - build-conda-pkgs: - name: Build ${{ matrix.pkg }} for ${{ matrix.target-platform }} - runs-on: ${{ matrix.os }} + build-noarch-conda-pkgs: + name: Build ${{ matrix.pkg }} + runs-on: ubuntu-latest if: github.event_name != 'release' strategy: matrix: - target-platform: ["osx-64", "osx-arm64"] - pkg: ["spyder-kernels"] + pkg: ["python-lsp-server", "qdarkstyle", "qtconsole"] python-version: ["3.9"] - include: - - os: macos-11 - target-platform: "osx-64" - - os: macos-latest - target-platform: "osx-arm64" - - os: macos-11 - target-platform: "osx-64" - python-version: "3.9" - pkg: "python-lsp-server" - - os: macos-11 - target-platform: "osx-64" - python-version: "3.9" - pkg: "qdarkstyle" - - os: macos-11 - target-platform: "osx-64" - python-version: "3.9" - pkg: "qtconsole" defaults: run: shell: bash -l {0} @@ -97,7 +35,7 @@ jobs: env: DISTDIR: ${{ github.workspace }}/installers-conda/dist pkg: ${{ matrix.pkg }} - artifact_name: ${{ matrix.pkg }}_${{ matrix.target-platform }}_${{ matrix.python-version }} + artifact_name: ${{ matrix.pkg }}_${{ matrix.python-version }} steps: - name: Checkout Code @@ -126,15 +64,15 @@ jobs: build-installers: name: Build installer for ${{ matrix.target-platform }} runs-on: ${{ matrix.os }} - needs: [build-spyder-conda-pkg, build-conda-pkgs] + needs: build-noarch-conda-pkgs strategy: matrix: + target-platform: ["osx-64", "osx-arm64"] + python-version: ["3.9"] include: - os: macos-11 - python-version: "3.9" target-platform: "osx-64" - os: macos-latest - python-version: "3.9" target-platform: "osx-arm64" defaults: run: @@ -176,6 +114,14 @@ jobs: mamba search -c $CONDA_PREFIX/conda-bld --override-channels + - name: Build ${{ matrix.target-platform }} conda packages + run: | + pkgs=("spyder") + if [[ $GITHUB_EVENT_NAME != "release" ]]; then + pkgs+=("spyder-kernels") + fi + python build_conda_pkgs.py --build ${pkgs[@]} + - name: Create Keychain if: github.event_name == 'release' && runner.os == 'macOS' run: | From 875f8d34fa55a379daecb8b877ca21435a6f4d1f Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Thu, 6 Oct 2022 12:30:22 -0700 Subject: [PATCH 29/43] Provide function to change file permissions. This is required for Windows in order to remove some .git files. --- installers-conda/build_conda_pkgs.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/installers-conda/build_conda_pkgs.py b/installers-conda/build_conda_pkgs.py index f66c370f764..fb3e9b6f225 100644 --- a/installers-conda/build_conda_pkgs.py +++ b/installers-conda/build_conda_pkgs.py @@ -55,6 +55,20 @@ DIST.mkdir(exist_ok=True) +def remove_readonly(func, path, exc): + """ + Change readonly status of file. + Windows file systems may require this if rmdir fails + """ + import errno, stat + excvalue = exc[1] + if func in (os.rmdir, os.remove, os.unlink) and excvalue.errno == errno.EACCES: + os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777 + func(path) + else: + raise + + class BuildCondaPkg(): name = None src_path = None @@ -100,9 +114,9 @@ def _get_source(self): repo.git.checkout(cfg['subrepo']['commit']) def _build_cleanup(self): - if self.src_path == HERE / self.name: + if self.src_path.exists() and self.src_path == HERE / self.name: logger.info(f"Removing {self.src_path}...") - rmtree(self.src_path, ignore_errors=True) + rmtree(self.src_path, onerror=remove_readonly) def _get_version(self): self.version = get_version(self.src_path).split('+')[0] @@ -110,7 +124,7 @@ def _get_version(self): def _clone_feedstock(self): if self.fdstk_path.exists(): self.logger.info(f"Removing existing {self.fdstk_path}...") - rmtree(self.fdstk_path, ignore_errors=True) + rmtree(self.fdstk_path, onerror=remove_readonly) self.logger.info(f"Cloning feedstock to {self.fdstk_path}...") check_call(["git", "clone", str(self.feedstock), str(self.fdstk_path)]) @@ -174,7 +188,7 @@ def build(self): self._patched_build = False if not self.debug: self.logger.info(f"Removing {self.fdstk_path}...") - rmtree(self.fdstk_path, ignore_errors=True) + rmtree(self.fdstk_path, onerror=remove_readonly) self._build_cleanup() From 99a0a881dde9086b3792e13a5d903519de83ad02 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Thu, 6 Oct 2022 12:30:53 -0700 Subject: [PATCH 30/43] Patch bld.bat --- installers-conda/build_conda_pkgs.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/installers-conda/build_conda_pkgs.py b/installers-conda/build_conda_pkgs.py index fb3e9b6f225..462a3863ca7 100644 --- a/installers-conda/build_conda_pkgs.py +++ b/installers-conda/build_conda_pkgs.py @@ -217,7 +217,17 @@ def _patch_build(self): text = file.read_text() text += build_patch.read_text() file.write_text(text) - + if os.name == 'nt': + file = self.fdstk_path / "recipe" / "bld.bat" + text = file.read_text() + text = text.replace( + r"copy %RECIPE_DIR%\menu-windows.json %MENU_DIR%\spyder_shortcut.json", + """powershell -Command""" + r""" "(gc %SRC_DIR%\installers-conda\resources\spyder-menu.json)""" + r""" -replace '__PKG_VERSION__', '%PKG_VERSION%' | """ + r"""Out-File -encoding ASCII %MENU_DIR%\spyder-menu.json" """ + ) + file.write_text(text) class PylspCondaPkg(BuildCondaPkg): name = "python-lsp-server" From 6d636ea2dbde37560a322d45b72e2228711a12a4 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Thu, 6 Oct 2022 20:05:25 -0700 Subject: [PATCH 31/43] Remove tests from outputs --- installers-conda/build_conda_pkgs.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/installers-conda/build_conda_pkgs.py b/installers-conda/build_conda_pkgs.py index 462a3863ca7..d8c1005dfdc 100644 --- a/installers-conda/build_conda_pkgs.py +++ b/installers-conda/build_conda_pkgs.py @@ -151,6 +151,9 @@ def patch_meta(self): self.yaml['source']['patches'] = patches self.yaml.pop('test', None) + if 'outputs' in self.yaml: + for out in self.yaml['outputs']: + out.pop('test', None) self._patch_meta() From 316cc7885c3424bb9c44563cdf0c9da666670c24 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Thu, 6 Oct 2022 20:06:40 -0700 Subject: [PATCH 32/43] Remove superfluous treatment of patches --- installers-conda/build_conda_pkgs.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/installers-conda/build_conda_pkgs.py b/installers-conda/build_conda_pkgs.py index d8c1005dfdc..d8fef21cc04 100644 --- a/installers-conda/build_conda_pkgs.py +++ b/installers-conda/build_conda_pkgs.py @@ -145,10 +145,7 @@ def patch_meta(self): self.yaml = self._yaml.load(text) - patches = self.yaml["source"].pop("patches", None) self.yaml['source'] = {'path': str(self.src_path)} - if patches: - self.yaml['source']['patches'] = patches self.yaml.pop('test', None) if 'outputs' in self.yaml: From bd2832bbb380738b3cef13e7226af155254dc8d6 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Thu, 6 Oct 2022 20:07:02 -0700 Subject: [PATCH 33/43] Fix icon_image specification --- installers-conda/build_installers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installers-conda/build_installers.py b/installers-conda/build_installers.py index ea106a33075..0f436fabfa7 100644 --- a/installers-conda/build_installers.py +++ b/installers-conda/build_installers.py @@ -257,7 +257,7 @@ def _definitions(): { "welcome_image": str(DIST / "spyder_164x314.png"), "header_image": str(DIST / "spyder_150x57.png"), - "icon_image": str(DIST / "icon.ico"), + "icon_image": str(SPYREPO / "img_src" / "spyder.ico"), "register_python_default": False, "default_prefix": os.path.join( "%LOCALAPPDATA%", INSTALLER_DEFAULT_PATH_STEM From b2194f5ba59cabfb13fd71c2ec61f60b271c9151 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Fri, 7 Oct 2022 08:54:04 -0700 Subject: [PATCH 34/43] Add linux and windows platforms --- .github/workflows/installers-conda.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/installers-conda.yml b/.github/workflows/installers-conda.yml index c02e9261826..ab3e1c26b55 100644 --- a/.github/workflows/installers-conda.yml +++ b/.github/workflows/installers-conda.yml @@ -67,13 +67,18 @@ jobs: needs: build-noarch-conda-pkgs strategy: matrix: - target-platform: ["osx-64", "osx-arm64"] + target-platform: ["osx-64", "osx-arm64", "linux-64", "win-64"] python-version: ["3.9"] include: - os: macos-11 target-platform: "osx-64" - os: macos-latest target-platform: "osx-arm64" + - os: ubuntu-latest + target-platform: "linux-64" + - os: windows-latest + target-platform: "win-64" + defaults: run: shell: bash -l {0} From 7a6006811949fe1fd6d1cb5030a403aad8daf230 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Fri, 7 Oct 2022 11:26:46 -0700 Subject: [PATCH 35/43] tar's -C does not work properly for windows so we must change directories manually --- .github/workflows/installers-conda.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/installers-conda.yml b/.github/workflows/installers-conda.yml index ab3e1c26b55..cf4bf5d79a4 100644 --- a/.github/workflows/installers-conda.yml +++ b/.github/workflows/installers-conda.yml @@ -111,8 +111,9 @@ jobs: run: | files=($(find $PWD/artifacts -name *.tar.bz2)) echo ${files[@]} + cd $CONDA_PREFIX for file in ${files[@]}; do - tar -C $CONDA_PREFIX -xf $file + tar -xf $file done mamba index $CONDA_PREFIX/conda-bld From 2b803402fa077e7affd1c5f4d7a088ea5df0866b Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Sat, 8 Oct 2022 07:08:15 -0700 Subject: [PATCH 36/43] Set CONDA_BLD_PATH to shorter path than default to avoid "FileNotFoundError: [WinError 206] The filename or extension is too long" on Windows --- .github/workflows/installers-conda.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/installers-conda.yml b/.github/workflows/installers-conda.yml index cf4bf5d79a4..b60eb8605d3 100644 --- a/.github/workflows/installers-conda.yml +++ b/.github/workflows/installers-conda.yml @@ -109,16 +109,19 @@ jobs: - name: Create Local Conda Channel run: | + CONDA_BLD_PATH=$HOME/conda-bld + echo "CONDA_BLD_PATH=$CONDA_BLD_PATH" >> $GITHUB_ENV + files=($(find $PWD/artifacts -name *.tar.bz2)) echo ${files[@]} - cd $CONDA_PREFIX + cd $(dirname $CONDA_BLD_PATH) for file in ${files[@]}; do tar -xf $file done - mamba index $CONDA_PREFIX/conda-bld + mamba index $CONDA_BLD_PATH - mamba search -c $CONDA_PREFIX/conda-bld --override-channels + mamba search -c $CONDA_BLD_PATH --override-channels - name: Build ${{ matrix.target-platform }} conda packages run: | From 40b80c6b6550dd0f16fc1908c28b0fb294b2961f Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Sun, 9 Oct 2022 20:33:31 -0700 Subject: [PATCH 37/43] Include scientific packages - Include lite option for package installer - include paramiko and pyxdg --- installers-conda/build_conda_pkgs.py | 2 +- installers-conda/build_installers.py | 29 ++++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/installers-conda/build_conda_pkgs.py b/installers-conda/build_conda_pkgs.py index d8fef21cc04..df09c2fc2a2 100644 --- a/installers-conda/build_conda_pkgs.py +++ b/installers-conda/build_conda_pkgs.py @@ -316,7 +316,7 @@ class SpyderKernelsCondaPkg(BuildCondaPkg): pkg = PKGS[k](debug=args.debug) pkg.build() - specs[k] = pkg.version + specs[k] = "=" + pkg.version yaml.dump(specs, SPECS) diff --git a/installers-conda/build_installers.py b/installers-conda/build_installers.py index 0f436fabfa7..8378e23d07b 100644 --- a/installers-conda/build_installers.py +++ b/installers-conda/build_installers.py @@ -59,6 +59,16 @@ else: raise RuntimeError(f"Unrecognized OS: {sys.platform}") +scientific_packages = { + "cython": "", + "matplotlib": "", + "numpy": "", + "openpyxl": "", + "pandas": "", + "scipy": "", + "sympy": "", +} + # ---- Parse arguments p = ArgumentParser() p.add_argument( @@ -98,6 +108,10 @@ "--cert-id", default=None, help="Apple Developer ID Application certificate common name." ) +p.add_argument( + "--lite", action="store_true", + help=f"Do not include packages {scientific_packages.keys()}" +) args = p.parse_args() yaml = YAML() @@ -106,14 +120,21 @@ SPYVER = get_version(SPYREPO).strip().split("+")[0] -specs = {"spyder": "=" + SPYVER} -try: +specs = { + "spyder": "=" + SPYVER, + "paramiko": "", + "pyxdg": "", +} +if SPECS.exists(): logger.info(f"Reading specs from {SPECS}...") _specs = yaml.load(SPECS.read_text()) - specs.update({k: "=" + v for k, v in _specs.items()}) -except Exception: + specs.update(_specs) +else: logger.info(f"Did not read specs from {SPECS}") +if not args.lite: + specs.update(scientific_packages) + for spec in args.extra_specs: k, *v = re.split('([<>= ]+)', spec) specs[k] = "".join(v).strip() From 6ffa4168734104ff145e2a3ab7bf7814859c5a9a Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Tue, 11 Oct 2022 09:28:32 -0700 Subject: [PATCH 38/43] Remove win-64 from matrix build for release. Restore after release. --- .github/workflows/installers-conda.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/installers-conda.yml b/.github/workflows/installers-conda.yml index b60eb8605d3..bac0545a9d4 100644 --- a/.github/workflows/installers-conda.yml +++ b/.github/workflows/installers-conda.yml @@ -67,7 +67,7 @@ jobs: needs: build-noarch-conda-pkgs strategy: matrix: - target-platform: ["osx-64", "osx-arm64", "linux-64", "win-64"] + target-platform: ["osx-64", "osx-arm64", "linux-64"] python-version: ["3.9"] include: - os: macos-11 @@ -76,8 +76,6 @@ jobs: target-platform: "osx-arm64" - os: ubuntu-latest target-platform: "linux-64" - - os: windows-latest - target-platform: "win-64" defaults: run: From e75157df5ecfaeabe5fab9905f1daa81c3405b2d Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Tue, 11 Oct 2022 09:55:00 -0700 Subject: [PATCH 39/43] Add EXPERIMENTAL to artifact name --- installers-conda/build_installers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installers-conda/build_installers.py b/installers-conda/build_installers.py index 8378e23d07b..e9194392306 100644 --- a/installers-conda/build_installers.py +++ b/installers-conda/build_installers.py @@ -141,7 +141,7 @@ if k == "spyder": SPYVER = v[-1] -OUTPUT_FILE = DIST / f"{APP}-{SPYVER}-{OS}-{ARCH}.{EXT}" +OUTPUT_FILE = DIST / f"EXPERIMENTAL-{APP}-{SPYVER}-{OS}-{ARCH}.{EXT}" INSTALLER_DEFAULT_PATH_STEM = f"{APP}-{SPYVER}" From acad9d587c2eef87148287bcb08be866b88cdc4e Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Tue, 18 Oct 2022 15:35:07 -0700 Subject: [PATCH 40/43] Include win-64 only on release - Matrix build job uses new output paradigm --- .github/workflows/installers-conda.yml | 41 +++++++++++++++++++------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/.github/workflows/installers-conda.yml b/.github/workflows/installers-conda.yml index bac0545a9d4..b45b3fad694 100644 --- a/.github/workflows/installers-conda.yml +++ b/.github/workflows/installers-conda.yml @@ -61,21 +61,42 @@ jobs: path: ${{ github.workspace }}/installers-conda/${{ env.artifact_name }}.tar.bz2 name: ${{ env.artifact_name }} + build-matrix: + name: Determine Build Matrix + runs-on: ubuntu-latest + outputs: + target_platform: ${{ steps.build-matrix.outputs.target_platform }} + include: ${{ steps.build-matrix.outputs.include }} + python_version: ${{ steps.build-matrix.outputs.python_version }} + steps: + - id: build-matrix + run: | + target_platform="'osx-64', 'osx-arm64', 'linux-64'" + include="\ + {'os': 'macos-11', 'target-platform': 'osx-64'},\ + {'os': 'macos-latest', 'target-platform': 'osx-arm64'},\ + {'os': 'ubuntu-latest', 'target-platform': 'linux-64'}\ + " + python_version="'3.9'" + + if [[ ${GITHUB_EVENT_NAME} == 'release' ]]; then + target_platform=$target_platform", 'win-64'" + include=$include",{'os': 'windows-latest', 'target-platform': 'win-64'}" + fi + + echo "target_platform=[$target_platform]" >> $GITHUB_OUTPUT + echo "include=[$include]" >> $GITHUB_OUTPUT + echo "python_version=[$python_version]" >> $GITHUB_OUTPUT + build-installers: name: Build installer for ${{ matrix.target-platform }} runs-on: ${{ matrix.os }} - needs: build-noarch-conda-pkgs + needs: [build-matrix, build-noarch-conda-pkgs] strategy: matrix: - target-platform: ["osx-64", "osx-arm64", "linux-64"] - python-version: ["3.9"] - include: - - os: macos-11 - target-platform: "osx-64" - - os: macos-latest - target-platform: "osx-arm64" - - os: ubuntu-latest - target-platform: "linux-64" + target-platform: ${{fromJson(needs.build-matrix.outputs.target_platform)}} + python-version: ${{fromJson(needs.build-matrix.outputs.python_version)}} + include: ${{fromJson(needs.build-matrix.outputs.include)}} defaults: run: From b5ce815475358e17e7d341a56edd95bbcd728178 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Tue, 18 Oct 2022 15:38:44 -0700 Subject: [PATCH 41/43] Add python version to job name Ensure build-installers runs, even on release when build-noarch-conda-pkgs does not run Do not download local conda package artifacts on release --- .github/workflows/installers-conda.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/installers-conda.yml b/.github/workflows/installers-conda.yml index b45b3fad694..3cbb3cc61d8 100644 --- a/.github/workflows/installers-conda.yml +++ b/.github/workflows/installers-conda.yml @@ -89,9 +89,10 @@ jobs: echo "python_version=[$python_version]" >> $GITHUB_OUTPUT build-installers: - name: Build installer for ${{ matrix.target-platform }} + name: Build installer for ${{ matrix.target-platform }} Python-${{ matrix.python-version }} runs-on: ${{ matrix.os }} needs: [build-matrix, build-noarch-conda-pkgs] + if: always() strategy: matrix: target-platform: ${{fromJson(needs.build-matrix.outputs.target_platform)}} @@ -122,6 +123,7 @@ jobs: extra-specs: python=${{ matrix.python-version }} - name: Download Local Conda Packages + if: github.event_name != 'release' uses: actions/download-artifact@v3 with: path: ${{ github.workspace }}/installers-conda/artifacts From cff56903b3ac59b6acf65828a256d010551f3474 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Tue, 11 Oct 2022 09:42:07 -0700 Subject: [PATCH 42/43] Update patch file --- installers-conda/resources/installers-conda.patch | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/installers-conda/resources/installers-conda.patch b/installers-conda/resources/installers-conda.patch index 8111fede195..9df9e545c27 100644 --- a/installers-conda/resources/installers-conda.patch +++ b/installers-conda/resources/installers-conda.patch @@ -1,4 +1,4 @@ -From 4fc591ffa69a8f3f2b4030e4395818962be71f79 Mon Sep 17 00:00:00 2001 +From 40db10418cf9b46a70b48026d202d4474d62b99a Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Mon, 12 Sep 2022 23:56:56 -0700 Subject: [PATCH 1/2] Revise usage of running_in_mac_app @@ -187,7 +187,7 @@ index 91c411bdf..c274f5872 100644 self.error_output = '' self.running = True diff --git a/spyder/plugins/pylint/main_widget.py b/spyder/plugins/pylint/main_widget.py -index aa35d27d2..7fe5793db 100644 +index ffe47d6a8..811ed1458 100644 --- a/spyder/plugins/pylint/main_widget.py +++ b/spyder/plugins/pylint/main_widget.py @@ -31,7 +31,7 @@ @@ -199,7 +199,7 @@ index aa35d27d2..7fe5793db 100644 from spyder.config.utils import is_anaconda from spyder.plugins.pylint.utils import get_pylintrc_path from spyder.plugins.variableexplorer.widgets.texteditor import TextEditor -@@ -377,11 +377,6 @@ def _start(self): +@@ -378,11 +378,6 @@ def _start(self): if not is_pynsist() and not is_anaconda(): processEnvironment.insert("APPDATA", os.environ.get("APPDATA")) @@ -212,7 +212,7 @@ index aa35d27d2..7fe5793db 100644 process.start(sys.executable, command_args) running = process.waitForStarted() diff --git a/spyder/utils/programs.py b/spyder/utils/programs.py -index ccd22f9e6..56123ca9c 100644 +index 3c6c03e35..b640f4a6f 100644 --- a/spyder/utils/programs.py +++ b/spyder/utils/programs.py @@ -30,8 +30,7 @@ @@ -222,10 +222,10 @@ index ccd22f9e6..56123ca9c 100644 -from spyder.config.base import (running_under_pytest, get_home_dir, - running_in_mac_app) +from spyder.config.base import running_under_pytest, get_home_dir - from spyder.py3compat import is_text_string, to_text_string from spyder.utils import encoding from spyder.utils.misc import get_python_executable -@@ -777,8 +776,6 @@ def run_python_script_in_terminal(fname, wdir, args, interact, debug, + +@@ -774,8 +773,6 @@ def run_python_script_in_terminal(fname, wdir, args, interact, debug, delete=False) if wdir: f.write('cd "{}"\n'.format(wdir)) @@ -251,7 +251,7 @@ index 49f894e7c..018e02130 100644 2.37.3 -From 2f66ddf6fed81c3da069761eb046c2abd52e694c Mon Sep 17 00:00:00 2001 +From 01bd81747e570df7d9be7a50f0589809e58cf5d4 Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Mon, 12 Sep 2022 23:58:36 -0700 Subject: [PATCH 2/2] Update standalone conda executable. From ca3ebf3ae1da1231f838671bd2ab9f10e224068f Mon Sep 17 00:00:00 2001 From: Ryan Clary <9618975+mrclary@users.noreply.github.com> Date: Wed, 19 Oct 2022 10:34:37 -0700 Subject: [PATCH 43/43] Apply suggestions from code review Co-authored-by: Carlos Cordoba --- installers-conda/build_conda_pkgs.py | 31 +++++++++----- installers-conda/build_installers.py | 45 +++++++++++---------- installers-conda/resources/bundle_readme.md | 10 ++--- 3 files changed, 49 insertions(+), 37 deletions(-) diff --git a/installers-conda/build_conda_pkgs.py b/installers-conda/build_conda_pkgs.py index df09c2fc2a2..42918205b1f 100644 --- a/installers-conda/build_conda_pkgs.py +++ b/installers-conda/build_conda_pkgs.py @@ -1,7 +1,13 @@ +# -*- coding: utf-8 -*- +# +# Copyright © Spyder Project Contributors +# Licensed under the terms of the MIT License +# (see spyder/__init__.py for details) + """ Build conda packages to local channel. -This module builds conda packages for spyder and external-deps for +This module builds conda packages for Spyder and external-deps for inclusion in the conda-based installer. The Following classes are provided for each package: SpyderCondaPkg @@ -10,10 +16,10 @@ QtconsoleCondaPkg SpyderKernelsCondaPkg -spyder will be packaged from this repository (in its checked-out state). +Spyder will be packaged from this repository (in its checked-out state). qdarkstyle, qtconsole, and spyder-kernels will be packaged from the external-deps directory of this repository (in its checked-out state). -python-lsp-server, however, will be packaged from the upstream remote +Python-lsp-server, however, will be packaged from the upstream remote at the same commit as the external-deps state. Alternatively, any external-deps may be packaged from a local git repository @@ -24,21 +30,25 @@ QTCONSOLE_SOURCE SPYDER_KERNELS_SOURCE """ + +# Standard library imports import os import re from argparse import ArgumentParser from configparser import ConfigParser from datetime import timedelta -from git import Repo from logging import Formatter, StreamHandler, getLogger from pathlib import Path -from ruamel.yaml import YAML -from setuptools_scm import get_version from shutil import rmtree from subprocess import check_call from textwrap import dedent from time import time +# Third-party imports +from git import Repo +from ruamel.yaml import YAML +from setuptools_scm import get_version + fmt = Formatter('%(asctime)s [%(levelname)s] [%(name)s] -> %(message)s') h = StreamHandler() h.setFormatter(fmt) @@ -69,7 +79,7 @@ def remove_readonly(func, path, exc): raise -class BuildCondaPkg(): +class BuildCondaPkg: name = None src_path = None feedstock = None @@ -101,7 +111,7 @@ def __init__(self, data={}, debug=False): self._patched_build = False def _get_source(self): - self._build_cleanup() # Remove existing if HERE + self._build_cleanup() if not self.src_path.exists(): cfg = ConfigParser() @@ -182,7 +192,6 @@ def build(self): check_call( ["mamba", "mambabuild", str(self.fdstk_path / "recipe")] ) - finally: self._patched_meta = False self._patched_build = False @@ -281,11 +290,11 @@ class SpyderKernelsCondaPkg(BuildCondaPkg): p = ArgumentParser( description=dedent( """ - Build conda packages from local spyder and external-deps sources. + Build conda packages from local Spyder and external-deps sources. Alternative git repo for python-lsp-server may be provided by setting the environment variable PYTHON_LSP_SERVER_SOURCE, otherwise the upstream remote will be used. All other external-deps - use the subrepo source within the spyder repo. + use the subrepo source within the Spyder repo. """ ), usage="python build_conda_pkgs.py " diff --git a/installers-conda/build_installers.py b/installers-conda/build_installers.py index e9194392306..869a1f19630 100644 --- a/installers-conda/build_installers.py +++ b/installers-conda/build_installers.py @@ -1,3 +1,9 @@ +# -*- coding: utf-8 -*- +# +# Copyright © Spyder Project Contributors +# Licensed under the terms of the MIT License +# (see spyder/__init__.py for details) + """ Create Spyder installers using `constructor`. @@ -16,23 +22,27 @@ Path to PFX certificate to sign the EXE installer on Windows """ -import json -import os -import platform -import re -import sys -import zipfile +# Standard library imports from argparse import ArgumentParser from datetime import timedelta from distutils.spawn import find_executable from functools import partial +import json from logging import getLogger +import os from pathlib import Path -from ruamel.yaml import YAML +import platform +import re from subprocess import check_call +import sys from textwrap import dedent, indent from time import time +import zipfile + +# Third-party imports +from ruamel.yaml import YAML +# Local imports from build_conda_pkgs import HERE, DIST, RESOURCES, SPECS, h, get_version logger = getLogger('BuildInstallers') @@ -45,11 +55,12 @@ MACOS = sys.platform == "darwin" LINUX = sys.platform.startswith("linux") TARGET_PLATFORM = os.environ.get("CONSTRUCTOR_TARGET_PLATFORM") +PY_VER = f"{sys.version_info.major}.{sys.version_info.minor}" + if TARGET_PLATFORM == "osx-arm64": ARCH = "arm64" else: ARCH = (platform.machine() or "generic").lower().replace("amd64", "x86_64") -PY_VER = f"{sys.version_info.major}.{sys.version_info.minor}" if WINDOWS: EXT, OS = "exe", "Windows" elif LINUX: @@ -125,6 +136,7 @@ "paramiko": "", "pyxdg": "", } + if SPECS.exists(): logger.info(f"Reading specs from {SPECS}...") _specs = yaml.load(SPECS.read_text()) @@ -146,7 +158,7 @@ def _generate_background_images(installer_type): - """Requires pillow""" + """This requires Pillow.""" if installer_type == "sh": # shell installers are text-based, no graphics return @@ -183,7 +195,6 @@ def _get_condarc(): contents = dedent( f""" channels: #!final - - spyder-ide - conda-forge {defaults} repodata_fns: #!final @@ -211,7 +222,6 @@ def _definitions(): "version": SPYVER, "channels": [ "napari/label/bundle_tools", - "spyder-ide", "conda-forge", ], "conda_default_channels": ["conda-forge"], @@ -237,6 +247,7 @@ def _definitions(): condarc: ".condarc", }, } + if not args.no_local: definitions["channels"].insert(0, "local") @@ -268,6 +279,7 @@ def _definitions(): "post_install": str(RESOURCES / "post-install.sh"), } ) + if args.cert_id: definitions["signing_identity_name"] = args.cert_id definitions["notarization_identity_name"] = args.cert_id @@ -293,6 +305,7 @@ def _definitions(): "installer_type": "exe", } ) + signing_certificate = os.environ.get("CONSTRUCTOR_SIGNING_CERTIFICATE") if signing_certificate: definitions["signing_certificate"] = signing_certificate @@ -307,16 +320,6 @@ def _constructor(): """ Create a temporary `construct.yaml` input file and run `constructor`. - - Parameters - ---------- - version: str - Version of `spyder` to be built. Defaults to the - one detected by `importlib` from the source code. - extra_specs: list of str - Additional packages to be included in the installer. - A list of conda spec strings (`numpy`, `python=3`, etc) - is expected. """ constructor = find_executable("constructor") if not constructor: diff --git a/installers-conda/resources/bundle_readme.md b/installers-conda/resources/bundle_readme.md index 0bc76757af3..ac6bd600792 100644 --- a/installers-conda/resources/bundle_readme.md +++ b/installers-conda/resources/bundle_readme.md @@ -8,9 +8,9 @@ This is the base installation of Spyder, the Scientific Python Development Envir In most cases, you would run it through the platform-specific shortcut we created for your convenience. In other words, _not_ through this directory! -* Linux: check your desktop launcher. -* MacOS: check `~/Applications` or the Launchpad. -* Windows: check the Start Menu or the Desktop. +* Linux: Check your desktop launcher. +* MacOS: Check `~/Applications` or the Launchpad. +* Windows: Check the Start Menu or the Desktop. We generally recommend using the shortcut because it will pre-activate the `conda` environment for you! That said, you can also execute the `spyder` executable directly from these locations: @@ -23,13 +23,13 @@ need to activate the `conda` environment to ensure all dependencies are importab ## What does `conda` have to do with `spyder`? -The Spyder installer uses `conda` packages to bundle all its dependencies (Python, qt, etc). +The Spyder installer uses `conda` packages to bundle all its dependencies (Python, Qt, etc). This directory is actually a full `conda` installation! If you have used `conda` before, this is equivalent to what you usually call the `base` environment. ## Can I modify the `spyder` installation? -Yes. In practice, you can consider it a `conda` environment. You can even activate it as usual, +Yes, but it is not recommended (see below). In practice, you can consider it a `conda` environment. You can even activate it as usual, provided you specify the full path to the location, instead of the _name_. ```