modified release pipeline #4
@@ -84,6 +84,10 @@ jobs:
|
||||
# reserves the last one, so it carries nothing the tag could tell
|
||||
"version=$($tag.Substring(1)).0" | Out-File $env:GITHUB_OUTPUT -Append -Encoding utf8
|
||||
|
||||
# The installer answers to nobody about a fourth number and takes the
|
||||
# tag as it is
|
||||
"plain=$($tag.Substring(1))" | Out-File $env:GITHUB_OUTPUT -Append -Encoding utf8
|
||||
|
||||
- name: Show the toolchain
|
||||
run: dotnet --info
|
||||
|
||||
@@ -128,6 +132,12 @@ jobs:
|
||||
|
||||
./Packaging/build-msix.ps1 @arguments
|
||||
|
||||
# The other half of the release: the same application as an ordinary
|
||||
# installer, for handing round outside the Store. Nobody signs it, so
|
||||
# SmartScreen warns about it — see Packaging\installer.iss
|
||||
- name: Build the installer
|
||||
run: ./Packaging/build-installer.ps1 -Version ${{ steps.version.outputs.plain }}
|
||||
|
||||
# The artifact is where the package waits to be uploaded to Partner Center
|
||||
- name: Keep the package
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -135,3 +145,52 @@ jobs:
|
||||
name: msix-${{ steps.version.outputs.version }}
|
||||
path: artifacts/packages/
|
||||
if-no-files-found: error
|
||||
|
||||
# The .wixpdb next to each installer is left out on purpose: it is of use
|
||||
# only when something has to be traced back to the WiX source
|
||||
- name: Keep the installer
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: installer-${{ steps.version.outputs.plain }}
|
||||
path: artifacts/installers/*.msi
|
||||
if-no-files-found: error
|
||||
|
||||
# Only the installers go into the release. The MSIX stays in the artifacts
|
||||
# of the run: unsigned, it installs nowhere, and its one destination is
|
||||
# Partner Center
|
||||
- name: Publish the release
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# The GITHUB_ names are what Gitea itself hands to the workflow — its
|
||||
# actions repeat those of GitHub, and the addresses in them point at
|
||||
# this Gitea instance. GITHUB_API_URL used not to reach the steps at
|
||||
# all, so the address is put together from the server one when empty
|
||||
$root = if ($env:GITHUB_API_URL) { $env:GITHUB_API_URL } else { "$env:GITHUB_SERVER_URL/api/v1" }
|
||||
$api = "$root/repos/$env:GITHUB_REPOSITORY/releases"
|
||||
$headers = @{ Authorization = "token $env:GITEA_TOKEN" }
|
||||
|
||||
# Gitea makes a release of its own for a pushed tag, so the release is
|
||||
# looked up first and only made when it is not there
|
||||
$release = $null
|
||||
try { $release = Invoke-RestMethod "$api/tags/$env:TAG" -Headers $headers } catch { }
|
||||
|
||||
if (-not $release) {
|
||||
$body = @{ tag_name = $env:TAG; name = $env:TAG; draft = $false; prerelease = $false } | ConvertTo-Json
|
||||
$release = Invoke-RestMethod $api -Method Post -Headers $headers -ContentType 'application/json' -Body $body
|
||||
}
|
||||
|
||||
foreach ($file in Get-ChildItem artifacts/installers -File -Filter *.msi) {
|
||||
# A tag can be pushed again after it was deleted; the old file of
|
||||
# the same name is dropped, otherwise the upload is refused
|
||||
$existing = $release.assets | Where-Object { $_.name -eq $file.Name }
|
||||
foreach ($asset in $existing) {
|
||||
Invoke-RestMethod "$api/$($release.id)/assets/$($asset.id)" -Method Delete -Headers $headers | Out-Null
|
||||
}
|
||||
|
||||
Write-Host "Uploading $($file.Name)"
|
||||
Invoke-RestMethod "$api/$($release.id)/assets?name=$($file.Name)" -Method Post -Headers $headers -Form @{ attachment = $file } | Out-Null
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>..\CursorLang.Core\Resources\CursorLang.ico</ApplicationIcon>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<RuntimeIdentifiers>win-x64</RuntimeIdentifiers>
|
||||
<SelfContained Condition="'$(RuntimeIdentifier)' != ''">true</SelfContained>
|
||||
<PublishTrimmed>false</PublishTrimmed>
|
||||
<PublishReadyToRun Condition="'$(RuntimeIdentifier)' == 'win-x64'">true</PublishReadyToRun>
|
||||
<PublishReadyToRun Condition="'$(RuntimeIdentifier)' != ''">true</PublishReadyToRun>
|
||||
<Version>1.0.0</Version>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<RootNamespace>CursorLang.Core</RootNamespace>
|
||||
<AssemblyName>CursorLang.Core</AssemblyName>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<RuntimeIdentifiers>win-x64</RuntimeIdentifiers>
|
||||
<Version>1.0.0</Version>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>..\CursorLang.Core\Resources\CursorLang.ico</ApplicationIcon>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<RuntimeIdentifiers>win-x64</RuntimeIdentifiers>
|
||||
<SelfContained Condition="'$(RuntimeIdentifier)' != ''">true</SelfContained>
|
||||
<PublishTrimmed>false</PublishTrimmed>
|
||||
<PublishReadyToRun Condition="'$(RuntimeIdentifier)' == 'win-x64'">true</PublishReadyToRun>
|
||||
<PublishReadyToRun Condition="'$(RuntimeIdentifier)' != ''">true</PublishReadyToRun>
|
||||
<Version>1.0.0</Version>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
The installer for handing the application round outside the Store.
|
||||
|
||||
The Store gets an MSIX and signs it itself; this one nobody signs, so Windows
|
||||
warns about an unknown publisher and the user has to insist. That is the price
|
||||
of an unsigned build, and short of a certificate from a trusted authority there
|
||||
is no way round it.
|
||||
|
||||
Built by build-installer.ps1 — it publishes both halves of the application and
|
||||
passes the version, the architecture and the folders in as preprocessor
|
||||
variables.
|
||||
-->
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
|
||||
xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui"
|
||||
xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
|
||||
|
||||
<Package
|
||||
Name="CursorLang"
|
||||
Manufacturer="Aleksandr Neichev"
|
||||
Version="$(var.Version)"
|
||||
UpgradeCode="B7E4C9A2-5D31-4F86-9E27-3A1C8B0D6F45"
|
||||
Compressed="yes"
|
||||
Scope="perUser">
|
||||
|
||||
<!--
|
||||
Installing for the current user alone asks for no administrator rights,
|
||||
and so for no consent dialog either. The application needs nothing
|
||||
beyond the user's own account, and one awkward question the less is
|
||||
worth more here than a copy shared by everyone who signs in.
|
||||
|
||||
MSI was designed around installing for the whole machine, and its
|
||||
validation rules still say so: the ICE checks this trips over are
|
||||
turned off in the project file, not answered.
|
||||
-->
|
||||
|
||||
<!--
|
||||
Everything goes inside the .msi. Left to itself WiX puts the cabinet
|
||||
beside the file, and an installer that has to travel with a second
|
||||
file next to it is no use to anyone it is sent to
|
||||
-->
|
||||
<MediaTemplate EmbedCab="yes" CompressionLevel="high" />
|
||||
|
||||
<MajorUpgrade DowngradeErrorMessage="A newer version of CursorLang is already installed." />
|
||||
|
||||
<!--
|
||||
Both halves may be running, and both hold on to files in the install
|
||||
folder. Named here, they are asked to close instead of the install
|
||||
failing halfway through on a file it cannot replace
|
||||
-->
|
||||
<util:CloseApplication Id="CloseAgent" Target="CursorLang.exe"
|
||||
CloseMessage="yes" RebootPrompt="no" />
|
||||
<util:CloseApplication Id="CloseSettings" Target="CursorLang.Settings.exe"
|
||||
CloseMessage="yes" RebootPrompt="no" />
|
||||
|
||||
<Icon Id="CursorLangIcon" SourceFile="$(var.IconFile)" />
|
||||
<Property Id="ARPPRODUCTICON" Value="CursorLangIcon" />
|
||||
<Property Id="ARPNOREPAIR" Value="1" />
|
||||
|
||||
<!-- Only the folder is asked about: everything here is installed, always -->
|
||||
<ui:WixUI Id="WixUI_InstallDir" InstallDirectory="INSTALLFOLDER" />
|
||||
<WixVariable Id="WixUILicenseRtf" Value="$(var.LicenseFile)" />
|
||||
|
||||
<StandardDirectory Id="LocalAppDataFolder">
|
||||
<Directory Id="ProgramsFolder" Name="Programs">
|
||||
<Directory Id="INSTALLFOLDER" Name="CursorLang" />
|
||||
</Directory>
|
||||
</StandardDirectory>
|
||||
|
||||
<StandardDirectory Id="ProgramMenuFolder">
|
||||
<Directory Id="ShortcutFolder" Name="CursorLang" />
|
||||
</StandardDirectory>
|
||||
|
||||
<Feature Id="Main" Title="CursorLang" Level="1">
|
||||
<ComponentGroupRef Id="Payload" />
|
||||
<ComponentRef Id="StartMenuShortcut" />
|
||||
</Feature>
|
||||
|
||||
<!--
|
||||
The whole published folder: two executables, the libraries and the
|
||||
runtime. Listing them one by one would mean rewriting this file every
|
||||
time the runtime changes shape
|
||||
-->
|
||||
<ComponentGroup Id="Payload" Directory="INSTALLFOLDER">
|
||||
<Files Include="$(var.PayloadDir)\**" />
|
||||
</ComponentGroup>
|
||||
|
||||
<!--
|
||||
The shortcut starts the agent — the half that lives in the tray and
|
||||
shows the layout. The settings window opens from its menu, so it needs
|
||||
no shortcut of its own.
|
||||
|
||||
The key path is a registry value rather than the shortcut file: for
|
||||
anything installed into the user's profile Windows Installer wants it
|
||||
that way, and a shortcut cannot serve as one
|
||||
-->
|
||||
<Component Id="StartMenuShortcut" Directory="ShortcutFolder" Guid="D4A81C36-7E52-4B19-A0F3-6C2E9D5B8471">
|
||||
<Shortcut Id="CursorLangShortcut"
|
||||
Name="CursorLang"
|
||||
Description="Shows the keyboard layout at the cursor"
|
||||
Target="[INSTALLFOLDER]CursorLang.exe"
|
||||
WorkingDirectory="INSTALLFOLDER" />
|
||||
<RemoveFolder Id="RemoveShortcutFolder" Directory="ShortcutFolder" On="uninstall" />
|
||||
<RegistryValue Root="HKCU" Key="Software\CursorLang" Name="Shortcut"
|
||||
Type="integer" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
<!--
|
||||
The startup entry is written by the application itself, when startup is
|
||||
switched on in its settings, and has to go when the application does:
|
||||
left behind, it would point at an executable that is gone and Windows
|
||||
would go on listing it in the startup list for good.
|
||||
|
||||
RemoveRegistryValue would have been the obvious way to say that, but it
|
||||
means the opposite of what it sounds like — it wipes the value as the
|
||||
product is installed, which would quietly switch startup off for anyone
|
||||
who had turned it on and then upgraded. Hence reg.exe on the way out.
|
||||
|
||||
UPGRADINGPRODUCTCODE tells an uninstall apart from the removal of the
|
||||
old version during an upgrade: the second one has to leave the entry
|
||||
alone, or upgrading would cost the user their startup setting anyway.
|
||||
-->
|
||||
<CustomAction Id="ForgetStartup" Directory="INSTALLFOLDER" Execute="deferred"
|
||||
Impersonate="yes" Return="ignore"
|
||||
ExeCommand=""[SystemFolder]reg.exe" delete "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v CursorLang /f" />
|
||||
|
||||
<CustomAction Id="ForgetStartupApproval" Directory="INSTALLFOLDER" Execute="deferred"
|
||||
Impersonate="yes" Return="ignore"
|
||||
ExeCommand=""[SystemFolder]reg.exe" delete "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run" /v CursorLang /f" />
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="ForgetStartup" Before="RemoveFiles"
|
||||
Condition="REMOVE="ALL" AND NOT UPGRADINGPRODUCTCODE" />
|
||||
<Custom Action="ForgetStartupApproval" After="ForgetStartup"
|
||||
Condition="REMOVE="ALL" AND NOT UPGRADINGPRODUCTCODE" />
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<!-- Offered at the end of the wizard, the way an installer usually does -->
|
||||
<Property Id="WixShellExecTarget" Value="[#CursorLang.exe]" />
|
||||
<CustomAction Id="LaunchApplication" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)"
|
||||
DllEntry="WixShellExec" Impersonate="yes" Return="ignore" />
|
||||
|
||||
<UI>
|
||||
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="LaunchApplication"
|
||||
Condition="WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed" />
|
||||
</UI>
|
||||
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="Start CursorLang" />
|
||||
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1" />
|
||||
|
||||
</Package>
|
||||
</Wix>
|
||||
@@ -5,6 +5,9 @@
|
||||
meant to be built — build-msix.ps1 restores it and takes the program from the
|
||||
packages folder. It is kept out of the build in CursorLang.sln for the same
|
||||
reason.
|
||||
|
||||
The installer needs nothing from here: WiX arrives with its own project, at
|
||||
Installer\CursorLang.wixproj.
|
||||
-->
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
|
||||
+30
-69
@@ -25,13 +25,9 @@
|
||||
be on; a signature is not needed, because what gets registered is the layout
|
||||
the package is made of rather than the package file.
|
||||
|
||||
.EXAMPLE
|
||||
# A check on your own machine: your architecture alone
|
||||
pwsh -File Packaging\build-msix.ps1 -Architectures x64
|
||||
|
||||
.EXAMPLE
|
||||
# Build and install in one go, to click through the application
|
||||
pwsh -File Packaging\build-msix.ps1 -Architectures x64 -Install
|
||||
pwsh -File Packaging\build-msix.ps1 -Install
|
||||
|
||||
.EXAMPLE
|
||||
# A build for the Store — the identity comes from Partner Center
|
||||
@@ -47,9 +43,6 @@ param(
|
||||
[string] $Publisher = 'CN=Aleksandr Neichev',
|
||||
[string] $PublisherDisplayName = 'Aleksandr Neichev',
|
||||
|
||||
[ValidateSet('x64', 'arm64')]
|
||||
[string[]] $Architectures = @('x64', 'arm64'),
|
||||
|
||||
[switch] $Install,
|
||||
|
||||
[string] $OutputPath
|
||||
@@ -86,9 +79,8 @@ if (-not (Test-Path $assets)) {
|
||||
}
|
||||
|
||||
if ($Install) {
|
||||
# Both things below are checked before the build rather than after it: the
|
||||
# build takes minutes, and neither of them gets any truer while it runs
|
||||
|
||||
# Checked before the build rather than after it: the build takes minutes, and
|
||||
# this does not get any truer while it runs
|
||||
$developerMode = Get-ItemPropertyValue `
|
||||
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock' `
|
||||
-Name 'AllowDevelopmentWithoutDevLicense' -ErrorAction SilentlyContinue
|
||||
@@ -96,12 +88,6 @@ if ($Install) {
|
||||
if ($developerMode -ne 1) {
|
||||
throw 'Installing needs developer mode: Settings - System - For developers - Developer mode. Without a signature Windows registers a package no other way.'
|
||||
}
|
||||
|
||||
$machineArchitecture = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq 'Arm64') { 'arm64' } else { 'x64' }
|
||||
|
||||
if ($Architectures -notcontains $machineArchitecture) {
|
||||
throw "This machine is $machineArchitecture, and that architecture is not being built. Add it to -Architectures, or drop -Install."
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-Tool {
|
||||
@@ -198,71 +184,46 @@ foreach ($installed in @(Get-AppxPackage -Name $IdentityName)) {
|
||||
Remove-Item $layoutRoot -Recurse -Force -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Path $packagesPath -Force | Out-Null
|
||||
|
||||
$built = @()
|
||||
Write-Host 'Building...' -ForegroundColor Cyan
|
||||
|
||||
foreach ($architecture in $Architectures) {
|
||||
Write-Host "Building $architecture..." -ForegroundColor Cyan
|
||||
$layout = Join-Path $layoutRoot 'x64'
|
||||
|
||||
$layout = Join-Path $layoutRoot $architecture
|
||||
|
||||
foreach ($half in @($agentProject, $settingsProject)) {
|
||||
Invoke-Tool -Path 'dotnet' -Arguments @(
|
||||
'publish', $half,
|
||||
'--configuration', 'Release',
|
||||
'--runtime', "win-$architecture",
|
||||
'--self-contained', 'true',
|
||||
"-p:Version=$($Version.Substring(0, $Version.LastIndexOf('.')))",
|
||||
'--output', $layout,
|
||||
'--nologo'
|
||||
)
|
||||
}
|
||||
|
||||
# Debug symbols have no place in the package: they take up room, the user
|
||||
# has no use for them, and for crash reports the Store takes them separately
|
||||
Get-ChildItem $layout -Recurse -Filter '*.pdb' | Remove-Item -Force
|
||||
|
||||
Copy-Item $assets -Destination (Join-Path $layout 'Assets') -Recurse -Force
|
||||
|
||||
$manifest = (Get-Content $manifestTemplate -Raw).
|
||||
Replace('{IdentityName}', $IdentityName).
|
||||
Replace('{Publisher}', $Publisher).
|
||||
Replace('{PublisherDisplayName}', $PublisherDisplayName).
|
||||
Replace('{Version}', $Version).
|
||||
Replace('{Architecture}', $architecture)
|
||||
|
||||
Set-Content -Path (Join-Path $layout 'AppxManifest.xml') -Value $manifest -Encoding UTF8
|
||||
|
||||
$package = Join-Path $packagesPath "CursorLang-$Version-$architecture.msix"
|
||||
Invoke-Tool -Path $makeappx -Arguments @('pack', '/o', '/d', $layout, '/p', $package)
|
||||
|
||||
$built += $package
|
||||
foreach ($half in @($agentProject, $settingsProject)) {
|
||||
Invoke-Tool -Path 'dotnet' -Arguments @(
|
||||
'publish', $half,
|
||||
'--configuration', 'Release',
|
||||
'--runtime', 'win-x64',
|
||||
'--self-contained', 'true',
|
||||
"-p:Version=$($Version.Substring(0, $Version.LastIndexOf('.')))",
|
||||
'--output', $layout,
|
||||
'--nologo'
|
||||
)
|
||||
}
|
||||
|
||||
$result = $built[0]
|
||||
# Debug symbols have no place in the package: they take up room, the user
|
||||
# has no use for them, and for crash reports the Store takes them separately
|
||||
Get-ChildItem $layout -Recurse -Filter '*.pdb' | Remove-Item -Force
|
||||
|
||||
if ($built.Count -gt 1) {
|
||||
Write-Host 'Bringing the architectures into one package...' -ForegroundColor Cyan
|
||||
Copy-Item $assets -Destination (Join-Path $layout 'Assets') -Recurse -Force
|
||||
|
||||
# makeappx bundle takes everything from a folder, so the separate packages
|
||||
# are gathered into one of their own first — otherwise the results of
|
||||
# earlier builds would end up in the bundle
|
||||
$bundleInput = Join-Path $OutputPath 'bundle'
|
||||
Remove-Item $bundleInput -Recurse -Force -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Path $bundleInput -Force | Out-Null
|
||||
$built | ForEach-Object { Copy-Item $_ -Destination $bundleInput }
|
||||
$manifest = (Get-Content $manifestTemplate -Raw).
|
||||
Replace('{IdentityName}', $IdentityName).
|
||||
Replace('{Publisher}', $Publisher).
|
||||
Replace('{PublisherDisplayName}', $PublisherDisplayName).
|
||||
Replace('{Version}', $Version).
|
||||
Replace('{Architecture}', 'x64')
|
||||
|
||||
$result = Join-Path $packagesPath "CursorLang-$Version.msixbundle"
|
||||
Invoke-Tool -Path $makeappx -Arguments @('bundle', '/o', '/d', $bundleInput, '/p', $result, '/bv', $Version)
|
||||
Set-Content -Path (Join-Path $layout 'AppxManifest.xml') -Value $manifest -Encoding UTF8
|
||||
|
||||
Remove-Item $bundleInput -Recurse -Force
|
||||
}
|
||||
$result = Join-Path $packagesPath "CursorLang-$Version-x64.msix"
|
||||
Invoke-Tool -Path $makeappx -Arguments @('pack', '/o', '/d', $layout, '/p', $result)
|
||||
|
||||
if ($Install) {
|
||||
Write-Host "Installing the $machineArchitecture build..." -ForegroundColor Cyan
|
||||
Write-Host 'Installing...' -ForegroundColor Cyan
|
||||
|
||||
# The layout is registered rather than the package file: the two hold the
|
||||
# same thing, but a package file Windows only installs when it is signed
|
||||
Add-AppxPackage -Register (Join-Path $layoutRoot "$machineArchitecture\AppxManifest.xml")
|
||||
Add-AppxPackage -Register (Join-Path $layout 'AppxManifest.xml')
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
|
||||
+44
-6
@@ -265,17 +265,19 @@ Identity берётся из переменных репозитория, а е
|
||||
# Разово: отрисовать логотипы и иконку exe (уже в репозитории, повторить после правок)
|
||||
powershell -File Packaging\New-Assets.ps1
|
||||
|
||||
# Проверка на своей машине: только своя архитектура
|
||||
powershell -File Packaging\build-msix.ps1 -Architectures x64
|
||||
# Проверка на своей машине
|
||||
powershell -File Packaging\build-msix.ps1
|
||||
|
||||
# Для Partner Center — identity та, что зарезервирована там
|
||||
powershell -File Packaging\build-msix.ps1 -Version 1.0.1.0 `
|
||||
-IdentityName 12345AleksandrNeichev.CursorLang -Publisher "CN=ABCD1234-..."
|
||||
```
|
||||
|
||||
Результат — `artifacts\packages\CursorLang-<версия>.msixbundle`, покрывающий x64
|
||||
и arm64; рядом лежат пакеты отдельных архитектур. Bundle загружается в Partner
|
||||
Center как есть.
|
||||
Результат — `artifacts\packages\CursorLang-<версия>-x64.msix`. Он загружается в
|
||||
Partner Center как есть.
|
||||
|
||||
Собирается только x64. Сборка под arm64 удвоила бы вес каждого релиза ради
|
||||
машин, которые и так выполняют x64 через эмуляцию.
|
||||
|
||||
Здесь ничего не подписывается: подпись на пакет ставит сам Store, а для установки
|
||||
на эту машину вместо пакета регистрируется layout — см. `-Install` ниже.
|
||||
@@ -286,7 +288,7 @@ MSIX не может установить среду выполнения как
|
||||
Чтобы посмотреть, как пакет работает на этой машине, соберите его с `-Install`:
|
||||
|
||||
```powershell
|
||||
pwsh -File Packaging\build-msix.ps1 -Architectures x64 -Install
|
||||
pwsh -File Packaging\build-msix.ps1 -Install
|
||||
```
|
||||
|
||||
Приложение появится в меню «Пуск» как любое установленное. Регистрируется не сам
|
||||
@@ -302,3 +304,39 @@ pwsh -File Packaging\build-msix.ps1 -Architectures x64 -Install
|
||||
```powershell
|
||||
Remove-AppxPackage (Get-AppxPackage -Name AleksandrNeichev.CursorLang).PackageFullName
|
||||
```
|
||||
|
||||
## Сборка установщика
|
||||
|
||||
То же приложение собирается и обычным MSI — чтобы раздавать помимо Store: пока
|
||||
Store не вынес решение или если так его и не вынесет. Ставить ничего, кроме .NET
|
||||
SDK, не нужно: WiX приезжает пакетом NuGet, как и `makeappx`.
|
||||
|
||||
```powershell
|
||||
# Собрать и запустить — посмотреть глазами пользователя
|
||||
pwsh -File Packaging\build-installer.ps1 -Install
|
||||
|
||||
# Всё, что нужно релизу
|
||||
pwsh -File Packaging\build-installer.ps1 -Version 1.0.1
|
||||
```
|
||||
|
||||
Результат — `artifacts\installers\CursorLang-<версия>-x64.msi`. Всё лежит внутри
|
||||
`.msi` — отдельного архива рядом с ним нет.
|
||||
|
||||
Устанавливается только для текущего пользователя, в
|
||||
`%LOCALAPPDATA%\Programs\CursorLang`, поэтому не просит ни прав администратора,
|
||||
ни подтверждения. Удаление идёт через «Параметры» — «Приложения», как у любой
|
||||
программы, и уносит с собой запись автозапуска: иначе Windows продолжала бы
|
||||
показывать в автозагрузке приложение, которого уже нет.
|
||||
|
||||
Установщик никто не подписывает, поэтому Windows предупреждает о неизвестном
|
||||
издателе и пользователю приходится настоять. Покупка сертификата это сразу не
|
||||
снимет: SmartScreen смотрит на репутацию, а у нового сертификата её нет, пока
|
||||
приложение не наберёт установок.
|
||||
|
||||
Про проект WiX стоит знать две вещи, прежде чем его править. Он закреплён на WiX
|
||||
5, а не на нынешней 7: начиная с шестой версии инструмент требует принимать
|
||||
лицензию Open Source Maintenance Fee — бесплатную при доходе меньше $10 000 в
|
||||
год, но принимать её должен человек, а не сборочный скрипт. И он отключает три
|
||||
проверки ICE: MSI по-прежнему исходит из установки на всю машину, а установка в
|
||||
профиль пользователя нарушает правила, которые описывают ровно то, что здесь и
|
||||
задумано.
|
||||
|
||||
@@ -259,17 +259,19 @@ to submitting for certification — is written up in
|
||||
# One-off: draw the logos and the exe icon (already committed, rerun after edits)
|
||||
powershell -File Packaging\New-Assets.ps1
|
||||
|
||||
# A check on your own machine: your architecture alone
|
||||
powershell -File Packaging\build-msix.ps1 -Architectures x64
|
||||
# A check on your own machine
|
||||
powershell -File Packaging\build-msix.ps1
|
||||
|
||||
# For Partner Center — the identity is the one reserved there
|
||||
powershell -File Packaging\build-msix.ps1 -Version 1.0.1.0 `
|
||||
-IdentityName 12345AleksandrNeichev.CursorLang -Publisher "CN=ABCD1234-..."
|
||||
```
|
||||
|
||||
The result is `artifacts\packages\CursorLang-<version>.msixbundle` covering x64
|
||||
and arm64; next to it lie the packages of single architectures. Upload the bundle
|
||||
to Partner Center as it is.
|
||||
The result is `artifacts\packages\CursorLang-<version>-x64.msix`. Upload it to
|
||||
Partner Center as it is.
|
||||
|
||||
Only x64 is built. An arm64 build would double the size of every release for the
|
||||
sake of machines that run the x64 one under emulation anyway.
|
||||
|
||||
Nothing here is signed: the Store signs the package itself, and for installing it
|
||||
on this machine the layout is registered instead — see `-Install` below.
|
||||
@@ -280,7 +282,7 @@ MSIX cannot install a runtime as a package dependency.
|
||||
To see the package working on this machine, build it with `-Install`:
|
||||
|
||||
```powershell
|
||||
pwsh -File Packaging\build-msix.ps1 -Architectures x64 -Install
|
||||
pwsh -File Packaging\build-msix.ps1 -Install
|
||||
```
|
||||
|
||||
The application then shows up in the Start menu like any installed one. What
|
||||
@@ -296,3 +298,39 @@ To remove it by hand:
|
||||
```powershell
|
||||
Remove-AppxPackage (Get-AppxPackage -Name AleksandrNeichev.CursorLang).PackageFullName
|
||||
```
|
||||
|
||||
## Building the installer
|
||||
|
||||
The same application also comes as an ordinary MSI, for handing round outside the
|
||||
Store — to try it out before the Store has passed judgement, or if it never does.
|
||||
Nothing has to be installed beyond the .NET SDK: WiX comes from a NuGet package,
|
||||
the same way `makeappx` does.
|
||||
|
||||
```powershell
|
||||
# Build it and run it afterwards, to see what a user sees
|
||||
pwsh -File Packaging\build-installer.ps1 -Install
|
||||
|
||||
# Everything a release needs
|
||||
pwsh -File Packaging\build-installer.ps1 -Version 1.0.1
|
||||
```
|
||||
|
||||
The result is `artifacts\installers\CursorLang-<version>-x64.msi`. Everything
|
||||
travels inside the .msi; there is no cabinet to send alongside it.
|
||||
|
||||
It installs for the current user alone, into `%LOCALAPPDATA%\Programs\CursorLang`,
|
||||
and so asks for no administrator rights and no consent dialog. Uninstalling goes
|
||||
through Apps in Settings like any other program and takes the startup entry with
|
||||
it — otherwise Windows would go on listing an application that is no longer there.
|
||||
|
||||
Nobody signs the installer, so Windows warns about an unknown publisher and the
|
||||
user has to insist. Buying a certificate would not silence it at once either:
|
||||
SmartScreen goes by reputation, and a fresh certificate has none until enough
|
||||
people have installed the application.
|
||||
|
||||
Two things about the WiX project are worth knowing before touching it. It pins
|
||||
WiX 5 rather than the current 7: from version 6 the toolset asks every build to
|
||||
accept the Open Source Maintenance Fee licence — free below $10,000 of yearly
|
||||
revenue, but a decision for a person rather than for a build script. And it turns
|
||||
off three ICE validation rules: MSI still assumes an installation for the whole
|
||||
machine, and installing into the user's own profile trips rules that describe
|
||||
exactly what was intended here.
|
||||
|
||||
Reference in New Issue
Block a user