Windows Release: Script creation of MSIX package

Script create_msix_package.py will download the ZIP file
from the given URL. It will create the MSIX package
with the version number and publisher ID given.

Strongly recommended are the path to a valid PFX file, and the
password to use that PFX file. These are needed for signing
the resulting MSIX package. The signing step is optional though,
but the resulting MSIX package cannot be installed outside of the
Microsoft Store

Example

set VERSION=2.83.2.0
set URL=https://download.blender.org/release/Blender2.83/blender-2.83.2-windows64.zip
set PUBID=CN=PUBIDHERE
set PFX=X:\path\to\cert.pfx
set PFXPW=pwhere

python create_msix_package.py --version %VERSION% --url %URL% --publisher %PUBID% --pfx %PFX% --password %PFXPW%

Requirements:
* Python default from the Microsoft Store should do (3.8)
* requests can be installed with `pip install requests`

Note that for an LTS release that gets uploaded to its own LTS application release
in the store you need to specify the `--lts` switch on the command-line to the script.

Upon completion there will be a file with the
name blender-2.83.2.0-windows64.msix. In case PFX file and its password were
given on the command line MSIX package will also be signed for the Microsoft Store.

Related Wiki page: https://wiki.blender.org/wiki/Process/Release_On_Windows_Store

Reviewed By: jbakker

Maniphest Tasks: T77348, T79356

Differential Revision: https://developer.blender.org/D8310
This commit is contained in:
Nathan Letwory 2020-09-23 11:19:49 +02:00 committed by Jeroen Bakker
parent c3a0618fbf
commit 782baa8f54
Notes: blender-bot 2023-02-14 10:29:32 +01:00
Referenced by issue #79356, Windows 10 Start Menu and Taskbar Visual Appearance Issues for Microsoft Store Installations
Referenced by issue #77348, Blender LTS: Maintenance Task 2.83
10 changed files with 277 additions and 1 deletions

@ -1 +1 @@
Subproject commit 1be0b3210d8a3a30e99a853b50703a7ca7e8ac1e
Subproject commit feca8c5289794a70bdd375be76fc4bc59d83c96b

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap2="http://schemas.microsoft.com/appx/manifest/uap/windows10/2" xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4" xmlns:uap6="http://schemas.microsoft.com/appx/manifest/uap/windows10/6" xmlns:uap7="http://schemas.microsoft.com/appx/manifest/uap/windows10/7" xmlns:uap8="http://schemas.microsoft.com/appx/manifest/uap/windows10/8" xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10" xmlns:mobile="http://schemas.microsoft.com/appx/manifest/mobile/windows10" xmlns:iot="http://schemas.microsoft.com/appx/manifest/iot/windows10" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop2="http://schemas.microsoft.com/appx/manifest/desktop/windows10/2" xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:rescap3="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities/3" xmlns:rescap6="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities/6" xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10" xmlns:com2="http://schemas.microsoft.com/appx/manifest/com/windows10/2" xmlns:com3="http://schemas.microsoft.com/appx/manifest/com/windows10/3" IgnorableNamespaces="uap uap2 uap3 uap4 uap6 uap7 uap8 uap10 mobile iot desktop desktop2 desktop6 rescap rescap3 rescap6 com com2 com3">
<Identity Name="BlenderFoundation.Blender[PACKAGETYPE]" Publisher="[PUBLISHER]" Version="[VERSION]" ProcessorArchitecture="x64" />
<Properties>
<DisplayName>Blender[LTSORNOT]</DisplayName>
<PublisherDisplayName>Blender Foundation</PublisherDisplayName>
<Description>Blender [VERSION] is the Free and Open Source 3D creation suite</Description>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Resources>
<Resource Language="en-us" />
</Resources>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.18335.0" />
</Dependencies>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
<Applications>
<Application Id="BLENDER" Executable="Blender\blender.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
BackgroundColor="transparent"
DisplayName="Blender [VERSION]"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png"
Description="Blender is the Free and Open Source 3D creation suite"
>
<uap:DefaultTile
Wide310x150Logo="Assets\Wide310x150Logo.png"
Square310x310Logo="Assets\Square310x310Logo.png"
Square71x71Logo="Assets\Square71x71Logo.png"
ShortName="Blender [VERSION]"
>
<uap:ShowNameOnTiles>
<uap:ShowOn Tile="square150x150Logo"/> <!-- Show app name on the 150x150 tile -->
<uap:ShowOn Tile="wide310x150Logo"/> <!-- …and also on the 310x150 tile -->
<uap:ShowOn Tile="square310x310Logo"/> <!-- …and also on the 310x150 tile -->
</uap:ShowNameOnTiles>
</uap:DefaultTile>
</uap:VisualElements>
<Extensions>
<uap3:Extension Category="windows.fileTypeAssociation">
<uap3:FileTypeAssociation Name="blend">
<uap:SupportedFileTypes>
<uap:FileType>.blend</uap:FileType>
</uap:SupportedFileTypes>
<uap2:SupportedVerbs>
<uap3:Verb Id="open" Parameters="&quot;%1&quot;">open</uap3:Verb>
</uap2:SupportedVerbs>
</uap3:FileTypeAssociation>
</uap3:Extension>
<uap3:Extension Category="windows.appExecutionAlias" Executable="Blender\blender.exe" EntryPoint="Windows.FullTrustApplication">
<uap3:AppExecutionAlias>
<desktop:ExecutionAlias Alias="blender.exe" />
</uap3:AppExecutionAlias>
</uap3:Extension>
</Extensions>
</Application>
</Applications>
</Package>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,81 @@
create_msix_package
===================
This tool is used to create MSIX packages from a given ZiP archive. The MSIX
package is distributed mainly through the Microsoft Store. It can also be
installed when downloaded from blender.org. For that to work the MSIX package
needs to be signed.
Requirements
============
* MakeAppX - this tool is distributed with Windows 10 SDK
* SignTool - this tool is also distributed with Windows 10 SDK
* Python 3 (3.7 or later tested) - to run the create_msix_package.py script
* requests module - can be installed with `pip install requests`
* PFX file (optional, but strongly recommended) - for signing the resulting MSIX
package. **NOTE:** If the MSIX package is not signed when uploaded to the Microsoft
store the validation and certification process can take up to three full
business day.
Usage
=====
On the command-line:
```batch
set VERSION=2.83.4.0
set URL=https://download.blender.org/release/Blender2.83/blender-2.83.4-windows64.zip
set PUBID=CN=PUBIDHERE
set PFX=X:\path\to\cert.pfx
set PFXPW=pwhere
python create_msix_package.py --version %VERSION% --url %URL% --publisher %PUBID% --pfx %PFX% --password %PFXPW%
```
Result will be a MSIX package with the name `blender-2.83.4-windows64.msix`.
With the above usage it will be signed. If the signing options are left out the
package will not be signed.
Optional arguments
==================
In support of testing and developing the manifest and scripts there are a few
optional arguments:
* `--skipdl` : If a `blender.zip` is available already next to the tool use this
to skip actual downloading of the archive designated by `--url`. The latter
option is still required
* `--overwrite` : When script fails the final clean-up may be incomplete leaving
the `Content` folder with its structure. Specify this argument to automatically
clean up this folder before starting to seed the `Content` folder
* `--leavezip` : When specified leave the `blender.zip` file while cleaning up
all other intermediate files, including the `Content` folder. This is useful
to not have to re-download the same archive from `--url` on each usage
What it does
============
The tool creates in the directory it lives a subfolder called `Content`. This is
where all necessary files are placed.
The `Assets` folder is copied to the `Content` folder.
From the application manifest template a version with necessary parts replaced as
their actual values as specified on the command-line is realized. This manifest controls the packaging of Blender into the MSIX format.
Next the tool downloads the designated ZIP archive locally as blender.zip. From
this archive the files are extracted into the `Content\Blender` folder, but skip
the leading part of paths in the ZIP. We want to write the files to the
content_blender_folder where blender.exe ends up as
`Content\Blender\blender.exe`, and not
`Content\Blender\blender-2.83.4-windows64\blender.exe`
Once the extraction is completed the MakeAppX tool is executed with the `Content`
folder as input. The result will be the MSIX package with the name in the form
`blender-X.YY.Z-windows64.msix`.
If the PFX file and its password are given on the command-line this MSIX package
will be signed.
All intermediate files and directories will be removed.

View File

@ -0,0 +1,135 @@
#!/usr/bin/env python3
import argparse
import os
import pathlib
import requests
import shutil
import subprocess
import zipfile
parser = argparse.ArgumentParser()
parser.add_argument("--version", required=True, help="Version string in the form of 2.83.3.0")
parser.add_argument("--url", required=True, help="Location of the release ZIP archive to download")
parser.add_argument("--publisher", required=True, help="A string in the form of 'CN=PUBLISHER'")
parser.add_argument("--pfx", required=False, help="Absolute path to the PFX file used for signing the resulting MSIX package")
parser.add_argument("--password", required=False, default="blender", help="Password for the PFX file")
parser.add_argument("--lts", required=False, help="If set this MSIX is for an LTS release", action='store_const', const=1)
parser.add_argument("--skipdl", required=False, help="If set skip downloading of the specified URL as blender.zip. The tool assumes blender.zip exists", action='store_const', const=1)
parser.add_argument("--leavezip", required=False, help="If set don't clean up the downloaded blender.zip", action='store_const', const=1)
parser.add_argument("--overwrite", required=False, help="If set remove Content folder if it already exists", action='store_const', const=1)
args = parser.parse_args()
def execute_command(cmd : list, name : str, errcode : int):
"""
Execute given command in cmd. Output is captured. If an error
occurs name is used to print ERROR message, along with stderr and
stdout of the process if either was captured.
"""
cmd_process = subprocess.run(cmd, capture_output=True, encoding="UTF-8")
if cmd_process.returncode != 0:
print(f"ERROR: {name} failed.")
if cmd_process.stdout: print(cmd_process.stdout)
if cmd_process.stderr: print(cmd_process.stderr)
exit(errcode)
LTSORNOT = ""
PACKAGETYPE = ""
if args.lts:
versionparts = args.version.split(".")
LTSORNOT = f" {versionparts[0]}.{versionparts[1]} LTS"
PACKAGETYPE = f"{versionparts[0]}.{versionparts[1]}LTS"
blender_package_msix = pathlib.Path(".", f"blender-{args.version}-windows64.msix").absolute()
content_folder = pathlib.Path(".", "Content")
content_blender_folder = pathlib.Path(content_folder, "Blender").absolute()
content_assets_folder = pathlib.Path(content_folder, "Assets")
assets_original_folder = pathlib.Path(".", "Assets")
local_blender_zip = pathlib.Path(".", "blender.zip")
if args.pfx:
pfx_path = pathlib.Path(args.pfx)
if not pfx_path.exists():
print("ERROR: PFX file not found. Please ensure you give the correct path to the PFX file on the command-line.")
exit(1)
print(f"Creating MSIX package with signing using PFX file at {pfx_path}")
else:
pfx_path = None
print("Creating MSIX package without signing.")
msix_command = ["makeappx",
"pack",
"/h", "SHA256",
"/d", f"{content_folder.absolute()}",
"/p", f"{blender_package_msix}"
]
if pfx_path:
sign_command = ["signtool",
"sign",
"/fd", "sha256",
"/a", "/f", f"{pfx_path.absolute()}",
"/p", f"{args.password}",
f"{blender_package_msix}"
]
if args.overwrite:
if content_folder.joinpath("Assets").exists():
shutil.rmtree(content_folder)
content_folder.mkdir(exist_ok=True)
shutil.copytree(assets_original_folder, content_assets_folder)
manifest_text = pathlib.Path("AppxManifest.xml.template").read_text()
manifest_text = manifest_text.replace("[VERSION]", args.version)
manifest_text = manifest_text.replace("[PUBLISHER]", args.publisher)
manifest_text = manifest_text.replace("[LTSORNOT]", LTSORNOT)
manifest_text = manifest_text.replace("[PACKAGETYPE]", PACKAGETYPE)
pathlib.Path(content_folder, "AppxManifest.xml").write_text(manifest_text)
if not args.skipdl:
print(f"Downloading blender archive {args.url} to {local_blender_zip}...")
with open(local_blender_zip, "wb") as download_zip:
response = requests.get(args.url)
download_zip.write(response.content)
print("... download complete.")
else:
print("Skipping download")
print(f"Extracting files from ZIP to {content_blender_folder}...")
# Extract the files from the ZIP archive, but skip the leading part of paths
# in the ZIP. We want to write the files to the content_blender_folder where
# blender.exe ends up as ./Content/Blender/blender.exe, and not
# ./Content/Blender/blender-2.83.3-windows64/blender.exe
with zipfile.ZipFile(local_blender_zip, "r") as blender_zip:
for entry in blender_zip.infolist():
if entry.is_dir(): continue
entry_location = pathlib.Path(entry.filename)
target_location = content_blender_folder.joinpath(*entry_location.parts[1:])
pathlib.Path(target_location.parent).mkdir(parents=True, exist_ok=True)
extracted_entry = blender_zip.read(entry)
target_location.write_bytes(extracted_entry)
print("... extraction complete.")
print(f"Creating MSIX package using command: {' '.join(msix_command)}")
# Remove MSIX file if it already exists. Otherwise the MakeAppX tool
# will hang.
if blender_package_msix.exists():
os.remove(blender_package_msix)
execute_command(msix_command, "MakeAppX", 2)
if args.pfx:
print(f"Signing MSIX package using command: {' '.join(sign_command)}")
execute_command(sign_command, "SignTool", 3)
if not args.leavezip:
os.remove(local_blender_zip)
shutil.rmtree(content_folder)
print("Done.")