Unifica la catena di rilascio con Mimante/AutoBidder
La generazione del setup, il versionamento e il caricamento su Gitea passano ora da un solo file MSBuild in build/Release.proj, com'e' gia' per AutoBidder, al posto dello script PowerShell make-installer.ps1. Il cambiamento di sostanza e' da dove viene la versione: dal tag git, non piu' da Directory.Build.props. Il numero arriva a dotnet publish come proprieta' da riga di comando, quindi tag, eseguibile, installatore e release lo portano uguale per costruzione invece che per disciplina. Il tag si crea in fondo, a installatore esistente, e non si crea affatto se l'albero e' sporco: un giro andato male non lascia dietro un tag per una versione mai costruita. Tre adattamenti rispetto ad AutoBidder, segnati sul posto: la versione sta in Directory.Build.props e non nel csproj; la pubblicazione produce una cartella e non un eseguibile unico, perche' l'app legge encelado.json accanto a se', quindi la copia portabile allegata alla release e' uno zip; il target Backtest rigioca serie storiche di prezzi invece dei dossier delle aste. Il setup si chiama Encelado_<versione>.exe e la pubblicazione ora fallisce se nella cartella finiscono sorgenti o simboli di debug. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,3 +11,8 @@ logs/
|
|||||||
# Local configuration: credentials and machine-specific overrides live here
|
# Local configuration: credentials and machine-specific overrides live here
|
||||||
*.local.json
|
*.local.json
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
# Il token di Gitea per la catena di rilascio. Il modello versionato è
|
||||||
|
# build/gitea.example.json; questo file contiene una credenziale e non entra
|
||||||
|
# mai nel repository.
|
||||||
|
build/gitea.json
|
||||||
|
|||||||
Vendored
+110
-51
@@ -1,83 +1,142 @@
|
|||||||
{
|
{
|
||||||
|
// Tutte le attività passano da build/Release.proj: la catena è un solo file
|
||||||
|
// MSBuild versionato col codice, e qui restano soltanto i nomi e le domande.
|
||||||
|
// MSBuild non può chiedere niente a nessuno — i prompt stanno in "inputs".
|
||||||
|
//
|
||||||
|
// È la stessa impostazione di Mimante/AutoBidder.
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
// Referenced by the launch configuration as preLaunchTask.
|
|
||||||
"label": "build",
|
"label": "build",
|
||||||
|
"detail": "Compilazione di debug, per F5 e per il controllo rapido degli errori.",
|
||||||
"type": "process",
|
"type": "process",
|
||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"${workspaceFolder}/Encelado.slnx",
|
"${workspaceFolder}/Encelado.slnx",
|
||||||
"-p:GenerateFullPaths=true",
|
"-c",
|
||||||
"-consoleloggerparameters:NoSummary"
|
"Debug",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary"
|
||||||
],
|
],
|
||||||
"group": { "kind": "build", "isDefault": true },
|
"group": { "kind": "build", "isDefault": true },
|
||||||
"problemMatcher": "$msCompile",
|
"problemMatcher": "$msCompile"
|
||||||
"presentation": { "reveal": "silent", "clear": true }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "test",
|
"label": "verifica",
|
||||||
|
"detail": "Compila e lancia i test. Da eseguire dopo ogni modifica.",
|
||||||
"type": "process",
|
"type": "process",
|
||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": ["test", "${workspaceFolder}/Encelado.slnx", "--nologo"],
|
"args": [
|
||||||
|
"msbuild",
|
||||||
|
"${workspaceFolder}/build/Release.proj",
|
||||||
|
"-t:Verifica",
|
||||||
|
"-nologo",
|
||||||
|
"-v:m"
|
||||||
|
],
|
||||||
"group": { "kind": "test", "isDefault": true },
|
"group": { "kind": "test", "isDefault": true },
|
||||||
"problemMatcher": "$msCompile"
|
"problemMatcher": "$msCompile"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Produces the distributable build under src/Encelado.Bot/publish.
|
"label": "backtest",
|
||||||
"label": "publish (Release)",
|
"detail": "Rigioca una serie storica di prezzi: taratura contro verifica, con il confronto sul comprare e tenere.",
|
||||||
"type": "process",
|
"type": "process",
|
||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"publish",
|
"msbuild",
|
||||||
"${workspaceFolder}/src/Encelado.Bot/Encelado.Bot.csproj",
|
"${workspaceFolder}/build/Release.proj",
|
||||||
"-c",
|
"-t:Backtest",
|
||||||
"Release",
|
"-p:Dati=${input:dati}",
|
||||||
"-o",
|
"-p:Comando=${input:comando}",
|
||||||
"${workspaceFolder}/src/Encelado.Bot/publish"
|
"-nologo",
|
||||||
|
"-v:m"
|
||||||
],
|
],
|
||||||
|
"presentation": { "reveal": "always", "panel": "dedicated" },
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "crea installatore",
|
||||||
|
"detail": "Verifica, pubblica ed esegue Inno Setup: bin/installer/Encelado-<versione>-setup.exe. Crea il tag a pacchetto pronto. Non tocca Gitea.",
|
||||||
|
"type": "process",
|
||||||
|
"command": "dotnet",
|
||||||
|
"args": [
|
||||||
|
"msbuild",
|
||||||
|
"${workspaceFolder}/build/Release.proj",
|
||||||
|
"-t:Pacchetto",
|
||||||
|
"-p:Versione=${input:versione}",
|
||||||
|
"-nologo",
|
||||||
|
"-v:m"
|
||||||
|
],
|
||||||
|
"presentation": { "reveal": "always", "panel": "dedicated" },
|
||||||
"problemMatcher": "$msCompile"
|
"problemMatcher": "$msCompile"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Produce artifacts/installer/Encelado-Setup-<versione>.exe: un unico file da
|
"label": "crea installatore (senza rieseguire i test)",
|
||||||
// consegnare. Installa Inno Setup al primo utilizzo se non lo trova.
|
"detail": "Solo pubblicazione e Inno Setup. Da usare quando i test sono appena passati.",
|
||||||
"label": "installer",
|
|
||||||
"type": "process",
|
|
||||||
"command": "powershell.exe",
|
|
||||||
"args": [
|
|
||||||
"-NoProfile",
|
|
||||||
"-ExecutionPolicy",
|
|
||||||
"Bypass",
|
|
||||||
"-File",
|
|
||||||
"${workspaceFolder}/build/make-installer.ps1"
|
|
||||||
],
|
|
||||||
"problemMatcher": [],
|
|
||||||
"presentation": { "reveal": "always", "panel": "dedicated", "clear": true }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Come sopra ma senza il runtime .NET incorporato: ~2 MB invece di ~65, al
|
|
||||||
// prezzo di dover avere il .NET 10 Desktop Runtime sulla macchina di arrivo.
|
|
||||||
"label": "installer (senza runtime)",
|
|
||||||
"type": "process",
|
|
||||||
"command": "powershell.exe",
|
|
||||||
"args": [
|
|
||||||
"-NoProfile",
|
|
||||||
"-ExecutionPolicy",
|
|
||||||
"Bypass",
|
|
||||||
"-File",
|
|
||||||
"${workspaceFolder}/build/make-installer.ps1",
|
|
||||||
"-FrameworkDependent"
|
|
||||||
],
|
|
||||||
"problemMatcher": [],
|
|
||||||
"presentation": { "reveal": "always", "panel": "dedicated", "clear": true }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "clean",
|
|
||||||
"type": "process",
|
"type": "process",
|
||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": ["clean", "${workspaceFolder}/Encelado.slnx"],
|
"args": [
|
||||||
"problemMatcher": []
|
"msbuild",
|
||||||
|
"${workspaceFolder}/build/Release.proj",
|
||||||
|
"-t:Pacchetto",
|
||||||
|
"-p:Versione=${input:versione}",
|
||||||
|
"-p:SaltaVerifica=true",
|
||||||
|
"-nologo",
|
||||||
|
"-v:m"
|
||||||
|
],
|
||||||
|
"presentation": { "reveal": "always", "panel": "dedicated" },
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "rilascia su Gitea",
|
||||||
|
"detail": "Verifica, pubblica, installatore, tag e release su Gitea con i file allegati. La versione viene dal tag su HEAD. Richiede build/gitea.json.",
|
||||||
|
"type": "process",
|
||||||
|
"command": "dotnet",
|
||||||
|
"args": [
|
||||||
|
"msbuild",
|
||||||
|
"${workspaceFolder}/build/Release.proj",
|
||||||
|
"-t:Rilascia",
|
||||||
|
"-p:Versione=${input:versione}",
|
||||||
|
"-nologo",
|
||||||
|
"-v:m"
|
||||||
|
],
|
||||||
|
// Le note passano dall'ambiente, non da -p:. MSBuild spezza il valore di
|
||||||
|
// una proprietà sulle virgole e una nota in italiano ne ha quasi sempre
|
||||||
|
// una: si otterrebbe MSB1006 «proprietà non valida». Vedi Release.proj.
|
||||||
|
"options": {
|
||||||
|
"env": {
|
||||||
|
"ENCELADO_NOTE": "${input:note}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"presentation": { "reveal": "always", "panel": "dedicated" },
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"id": "versione",
|
||||||
|
"type": "promptString",
|
||||||
|
"description": "Versione — lascia vuoto se hai già taggato (git tag v3.3.0), o per la minor successiva",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "note",
|
||||||
|
"type": "promptString",
|
||||||
|
"description": "Note di rilascio (vuoto = solo il numero di versione)",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dati",
|
||||||
|
"type": "promptString",
|
||||||
|
"description": "File CSV con la serie storica dei prezzi",
|
||||||
|
"default": "C:\\Users\\alber\\Downloads\\BTC\\btcusd_bitstamp_1min_2012-2025.csv"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "comando",
|
||||||
|
"type": "pickString",
|
||||||
|
"description": "Cosa misurare",
|
||||||
|
"options": ["split", "walk", "frequency", "costs", "sweep", "explore"],
|
||||||
|
"default": "split"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
Cose da fare:
|
||||||
|
- Non caricare i sorgenti del programma nella release. Deve solo essere presente la versione portable e la versione installabile.
|
||||||
|
- Il nome del setup deve essere Encelado_Versione.exe (ad esempio Encelado_4.14.0.exe)
|
||||||
|
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
; ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
; Encelado — script di installazione (Inno Setup 6)
|
||||||
|
;
|
||||||
|
; Non si compila a mano: lo lancia build/Release.proj, che prima pubblica
|
||||||
|
; l'applicazione e poi passa qui versione e percorsi con /D. Compilarlo da solo
|
||||||
|
; produrrebbe un pacchetto con la versione sbagliata, perché il numero vive nel
|
||||||
|
; tag git e non in questo file.
|
||||||
|
;
|
||||||
|
; dotnet msbuild build/Release.proj -t:Pacchetto
|
||||||
|
;
|
||||||
|
; ── Due differenze rispetto ad AutoBidder.iss ───────────────────────────────
|
||||||
|
;
|
||||||
|
; La prima: Encelado non è un eseguibile unico. È una cartella — l'applicazione
|
||||||
|
; legge encelado.json accanto a sé — quindi si copia SourceDir, non SourceExe.
|
||||||
|
;
|
||||||
|
; La seconda: l'installazione è per utente e non è possibile forzarla altrove.
|
||||||
|
; Non è per evitare l'UAC. Encelado scrive log, diario operazioni e CSV di
|
||||||
|
; analisi accanto al proprio eseguibile: dentro C:\Program Files quelle
|
||||||
|
; scritture fallirebbero, e siccome il logger degrada in silenzio piuttosto che
|
||||||
|
; fermare il bot, l'utente se ne accorgerebbe solo cercando i log per capire
|
||||||
|
; cosa è successo — cioè nel momento peggiore.
|
||||||
|
; ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#ifndef AppVersion
|
||||||
|
#define AppVersion "0.0.0"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SourceDir
|
||||||
|
#define SourceDir "..\bin\publish\win-x64"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef OutputDir
|
||||||
|
#define OutputDir "..\bin\installer"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define AppName "Encelado"
|
||||||
|
#define AppPublisher "Alberto Balbo"
|
||||||
|
#define AppExeName "Encelado.exe"
|
||||||
|
#define AppDescription "Bot di trading automatico su Alpaca"
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
; L'AppId identifica il prodotto fra una versione e l'altra: cambiarlo farebbe
|
||||||
|
; comparire due voci in "App installate" invece di un aggiornamento.
|
||||||
|
AppId={{7C4F1E62-2B8A-4D19-9C55-3E0A6B1D8F44}
|
||||||
|
AppName={#AppName}
|
||||||
|
AppVersion={#AppVersion}
|
||||||
|
AppVerName={#AppName} {#AppVersion}
|
||||||
|
AppPublisher={#AppPublisher}
|
||||||
|
VersionInfoVersion={#AppVersion}
|
||||||
|
VersionInfoDescription={#AppDescription}
|
||||||
|
|
||||||
|
; Vedi la nota in testa al file: l'applicazione deve poter scrivere nella
|
||||||
|
; propria cartella, quindi l'installazione resta nel profilo dell'utente e non
|
||||||
|
; è consentito spostarla altrove.
|
||||||
|
PrivilegesRequired=lowest
|
||||||
|
PrivilegesRequiredOverridesAllowed=
|
||||||
|
DefaultDirName={autopf}\{#AppName}
|
||||||
|
DefaultGroupName={#AppName}
|
||||||
|
DisableProgramGroupPage=yes
|
||||||
|
DisableDirPage=auto
|
||||||
|
|
||||||
|
OutputDir={#OutputDir}
|
||||||
|
OutputBaseFilename=Encelado_{#AppVersion}
|
||||||
|
SetupIconFile=..\src\Encelado.Bot\Assets\encelado.ico
|
||||||
|
UninstallDisplayIcon={app}\{#AppExeName}
|
||||||
|
UninstallDisplayName={#AppName} {#AppVersion}
|
||||||
|
|
||||||
|
Compression=lzma2/max
|
||||||
|
SolidCompression=yes
|
||||||
|
WizardStyle=modern
|
||||||
|
ArchitecturesAllowed=x64compatible
|
||||||
|
ArchitecturesInstallIn64BitMode=x64compatible
|
||||||
|
|
||||||
|
; Se Encelado è in esecuzione, il Restart Manager lo chiude invece di lasciare
|
||||||
|
; l'installazione a metà con i file bloccati.
|
||||||
|
CloseApplications=yes
|
||||||
|
RestartApplications=no
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "italiano"; MessagesFile: "compiler:Languages\Italian.isl"
|
||||||
|
|
||||||
|
[Tasks]
|
||||||
|
Name: "desktopicon"; Description: "Crea un collegamento sul desktop"; GroupDescription: "Collegamenti:"
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
; Tutto il publish tranne la configurazione, che ha una regola sua, e i simboli
|
||||||
|
; di debug, che non servono a chi installa.
|
||||||
|
Source: "{#SourceDir}\*"; DestDir: "{app}"; \
|
||||||
|
Excludes: "encelado.json,*.pdb,*.xml,logs\*"; \
|
||||||
|
Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
|
|
||||||
|
; La configurazione è il prodotto — ogni numero dentro encelado.json è tarato su
|
||||||
|
; due dataset indipendenti — ma è anche l'unico posto dove l'utente mette mano,
|
||||||
|
; dalla scheda Impostazioni o a mano. "onlyifdoesntexist" fa sì che un
|
||||||
|
; aggiornamento non cancelli quelle modifiche; "uninsneveruninstall" che una
|
||||||
|
; disinstallazione non le butti via. Le chiavi nuove introdotte da una versione
|
||||||
|
; successiva non rompono nulla: il loader usa i valori di default per quelle che
|
||||||
|
; non trova.
|
||||||
|
Source: "{#SourceDir}\encelado.json"; DestDir: "{app}"; \
|
||||||
|
Flags: onlyifdoesntexist uninsneveruninstall
|
||||||
|
|
||||||
|
; Copia sempre aggiornata dei valori di fabbrica, per poter vedere cosa è
|
||||||
|
; cambiato rispetto al proprio encelado.json dopo un aggiornamento.
|
||||||
|
Source: "{#SourceDir}\encelado.json"; DestDir: "{app}"; \
|
||||||
|
DestName: "encelado.default.json"; Flags: ignoreversion
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Comment: "{#AppDescription}"
|
||||||
|
Name: "{group}\Disinstalla {#AppName}"; Filename: "{uninstallexe}"
|
||||||
|
Name: "{autodesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; \
|
||||||
|
Comment: "{#AppDescription}"; Tasks: desktopicon
|
||||||
|
|
||||||
|
[Run]
|
||||||
|
Filename: "{app}\{#AppExeName}"; Description: "Avvia {#AppName}"; \
|
||||||
|
Flags: nowait postinstall skipifsilent
|
||||||
|
|
||||||
|
[UninstallDelete]
|
||||||
|
; Prodotti a runtime, quindi non tracciati dall'installatore: senza questo
|
||||||
|
; resterebbero una cartella e dei file orfani.
|
||||||
|
Type: filesandordirs; Name: "{app}\logs"
|
||||||
|
Type: dirifempty; Name: "{app}"
|
||||||
|
|
||||||
|
[Code]
|
||||||
|
{ Le credenziali Alpaca vivono in %LocalAppData%\Encelado, fuori dalla cartella
|
||||||
|
di installazione, quindi una disinstallazione normale non le toccherebbe.
|
||||||
|
Lasciarle lì in silenzio però significa lasciare sul disco una chiave API
|
||||||
|
cifrata di cui l'utente si è dimenticato. Glielo chiediamo, con il "no" come
|
||||||
|
risposta predefinita: chi disinstalla per reinstallare una versione nuova non
|
||||||
|
deve ritrovarsi a reinserire le chiavi solo perché ha premuto Invio di fretta. }
|
||||||
|
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
|
||||||
|
var
|
||||||
|
DataDir: String;
|
||||||
|
begin
|
||||||
|
if CurUninstallStep <> usPostUninstall then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
{ In modalità silenziosa non c'è nessuno a cui chiedere, e la risposta che non
|
||||||
|
si può disfare è quella che cancella. Nel dubbio le credenziali restano. }
|
||||||
|
if UninstallSilent then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
DataDir := ExpandConstant('{localappdata}\Encelado');
|
||||||
|
if not DirExists(DataDir) then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
if MsgBox(
|
||||||
|
'Vuoi eliminare anche le credenziali Alpaca salvate?' + #13#10#13#10 +
|
||||||
|
DataDir + #13#10#13#10 +
|
||||||
|
'Scegli No se hai intenzione di reinstallare Encelado: le credenziali '
|
||||||
|
+ 'verranno riconosciute dalla nuova installazione.',
|
||||||
|
mbConfirmation, MB_YESNO or MB_DEFBUTTON2) = IDYES then
|
||||||
|
DelTree(DataDir, True, True, True);
|
||||||
|
end;
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
# Catena di verifica, pacchetto e rilascio
|
||||||
|
|
||||||
|
Tutto quello che serve a controllare, impacchettare e pubblicare Encelado sta in questa
|
||||||
|
cartella. La radice del progetto non contiene script.
|
||||||
|
|
||||||
|
È la stessa catena di [Mimante/AutoBidder](http://192.168.30.23:3000/Alby96/Mimante),
|
||||||
|
adattata a una soluzione con più progetti. Le differenze sono tre, tutte segnate sul
|
||||||
|
posto in `Release.proj`:
|
||||||
|
|
||||||
|
| | AutoBidder | Encelado |
|
||||||
|
|---|---|---|
|
||||||
|
| Dove sta la versione | `AutoBidder.csproj` | `Directory.Build.props`, ereditato da tutti i progetti |
|
||||||
|
| Cosa produce `dotnet publish` | un eseguibile unico | una cartella: l'app legge `encelado.json` accanto a sé |
|
||||||
|
| Copia portabile allegata | il solo `.exe` | uno zip della cartella |
|
||||||
|
| Cosa rigioca `Backtest` | i dossier delle aste | serie storiche di prezzi |
|
||||||
|
|
||||||
|
| File | Cos'è |
|
||||||
|
|---|---|
|
||||||
|
| `Release.proj` | La catena. Un solo file MSBuild, nessuno script. |
|
||||||
|
| `Encelado.iss` | Lo script di Inno Setup. Non si compila a mano: lo lancia `Release.proj`. |
|
||||||
|
| `gitea.example.json` | Modello per `gitea.json` (che è escluso dal controllo di versione). |
|
||||||
|
|
||||||
|
## Da VS Code
|
||||||
|
|
||||||
|
**Terminale ▸ Esegui attività…**
|
||||||
|
|
||||||
|
| Attività | Cosa fa |
|
||||||
|
|---|---|
|
||||||
|
| `verifica` | Compila e lancia i test. |
|
||||||
|
| `backtest` | Rigioca una serie storica di prezzi. |
|
||||||
|
| `crea installatore` | Chiede la versione, verifica, pubblica, esegue Inno Setup. |
|
||||||
|
| `crea installatore (senza rieseguire i test)` | Solo pubblicazione e installatore. |
|
||||||
|
| `rilascia su Gitea` | Tutto quanto sopra, più tag e release con i file allegati. |
|
||||||
|
|
||||||
|
## Da riga di comando
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
dotnet msbuild build/Release.proj -t:Verifica
|
||||||
|
dotnet msbuild build/Release.proj -t:Pacchetto
|
||||||
|
dotnet msbuild build/Release.proj -t:Rilascia -p:Versione=3.3.0 -p:Note="Cosa cambia"
|
||||||
|
|
||||||
|
# Il backtest vuole un file di dati
|
||||||
|
dotnet msbuild build/Release.proj -t:Backtest -p:Dati="C:\dati\btcusd.csv"
|
||||||
|
dotnet msbuild build/Release.proj -t:Backtest -p:Dati="..." -p:Comando=frequency
|
||||||
|
```
|
||||||
|
|
||||||
|
| Proprietà | Predefinito | A cosa serve |
|
||||||
|
|---|---|---|
|
||||||
|
| `Versione` | vuoto | Vuoto = incrementa la minor (3.2.0 → 3.3.0). Altrimenti la scrive, se ha la forma `X.Y.Z`. |
|
||||||
|
| `Note` | vuoto | Note di rilascio. |
|
||||||
|
| `SaltaVerifica` | `false` | Non rieseguire i test. |
|
||||||
|
| `Sovrascrivi` | `false` | Sostituisci una release Gitea con lo stesso tag. |
|
||||||
|
| `Bozza` | `false` | Crea la release come bozza. |
|
||||||
|
| `ConsentiModifiche` | `false` | Tagga anche con l'albero sporco. Serve saperlo. |
|
||||||
|
| `Dati` | — | Il CSV da rigiocare. Obbligatorio per `Backtest`. |
|
||||||
|
| `Comando` | `split` | `split`, `walk`, `frequency`, `costs`, `sweep`, `explore`. |
|
||||||
|
| `Barre` | `1d` | Ampiezza delle barre nel backtest. |
|
||||||
|
|
||||||
|
## Chi chiede la versione
|
||||||
|
|
||||||
|
MSBuild non può chiedere niente a nessuno: è un motore di compilazione. La domanda la fa
|
||||||
|
l'attività di VS Code (`inputs` in `.vscode/tasks.json`) e passa la risposta in
|
||||||
|
`-p:Versione=`. Lasciando il campo vuoto si prende la minor successiva, che è il caso
|
||||||
|
normale di fine sessione.
|
||||||
|
|
||||||
|
**La versione arriva dal tag e non viene scritta da nessuna parte.** `dotnet publish` la
|
||||||
|
riceve come proprietà da riga di comando, che è globale e vince su quella dichiarata in
|
||||||
|
`Directory.Build.props`. Tag, eseguibile, installatore e release portano quindi lo stesso
|
||||||
|
numero per costruzione.
|
||||||
|
|
||||||
|
Il tag si crea **in fondo**, quando l'installatore esiste davvero. Il contrario sembra più
|
||||||
|
naturale — decidi il numero, poi costruisci — ma lascia dietro un tag quando la verifica
|
||||||
|
fallisce, e il tentativo dopo riparte da lì: il numero sale senza che sia mai esistito un
|
||||||
|
pacchetto con quella versione.
|
||||||
|
|
||||||
|
## Gitea
|
||||||
|
|
||||||
|
Servono quattro valori. Le variabili d'ambiente hanno la precedenza sul file, così una
|
||||||
|
macchina condivisa può rilasciare senza scrivere un token su disco:
|
||||||
|
|
||||||
|
- `GITEA_URL`, `GITEA_OWNER`, `GITEA_REPO`, `GITEA_TOKEN`
|
||||||
|
- oppure `build/gitea.json`, copiato da `gitea.example.json`
|
||||||
|
|
||||||
|
Il token si crea in Gitea da *Impostazioni ▸ Applicazioni ▸ Genera nuovo token*, con il
|
||||||
|
permesso `repository: read and write`.
|
||||||
|
|
||||||
|
Il token non passa mai dalla riga di comando: sta in un file di configurazione di curl,
|
||||||
|
cancellato subito dopo il rilascio. Gli `Exec` hanno `EchoOff` perché un registro di
|
||||||
|
compilazione è la classica cosa che si incolla in una chat.
|
||||||
|
|
||||||
|
Nella release vengono caricati **sia l'installatore sia la copia portabile**: chi non
|
||||||
|
vuole installare niente deve continuare a poter scaricare l'applicazione e basta.
|
||||||
|
|
||||||
|
## Una trappola già pagata
|
||||||
|
|
||||||
|
`dotnet test` e `dotnet publish` lanciati **da dentro** MSBuild ereditano l'ambiente del
|
||||||
|
processo padre. La compilazione WPF crea un progetto temporaneo (`_wpftmp.csproj`) e con
|
||||||
|
`MSBUILD_EXE_PATH` puntata al build in corso non genera più le classi parziali dello XAML:
|
||||||
|
si ottengono decine di errori su membri che esistono benissimo.
|
||||||
|
|
||||||
|
Per questo gli `Exec` azzerano `MSBUILD_EXE_PATH` e `MSBuildLoadMicrosoftTargetsReadOnly`.
|
||||||
|
**Solo quelle due**: la ricetta che gira in rete azzera anche `MSBuildExtensionsPath` e
|
||||||
|
`MSBuildSDKsPath`, e così il figlio perde la posizione dell'SDK — *«l'SDK Microsoft.NET.Sdk
|
||||||
|
specificato non è stato trovato»*. Serve isolare il motore, non nascondergli dove abita.
|
||||||
|
|
||||||
|
I test girano in una cartella a parte (`%TEMP%\Encelado.Verifica`) perché l'applicazione
|
||||||
|
può essere aperta mentre si lavora e tiene bloccato `Encelado.exe`: senza, la compilazione
|
||||||
|
si ferma su MSB3027.
|
||||||
|
|
||||||
|
## Prerequisiti
|
||||||
|
|
||||||
|
- .NET SDK 10
|
||||||
|
- [Inno Setup 6](https://jrsoftware.org/isinfo.php) — `winget install -e --id JRSoftware.InnoSetup`
|
||||||
|
- `curl` e `git`, entrambi di serie in Windows 11
|
||||||
@@ -0,0 +1,713 @@
|
|||||||
|
<!--
|
||||||
|
═══════════════════════════════════════════════════════════════════════════
|
||||||
|
Encelado — catena di verifica, pacchetto e rilascio
|
||||||
|
═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
Un solo file, nessuno script. Si richiama con `dotnet msbuild`, dalle attività
|
||||||
|
di VS Code (Terminale ▸ Esegui attività…) oppure a mano:
|
||||||
|
|
||||||
|
dotnet msbuild build/Release.proj -t:Verifica
|
||||||
|
dotnet msbuild build/Release.proj -t:Backtest
|
||||||
|
dotnet msbuild build/Release.proj -t:Pacchetto
|
||||||
|
dotnet msbuild build/Release.proj -t:Rilascia -p:Versione=3.3.0
|
||||||
|
|
||||||
|
È la stessa catena di Mimante/AutoBidder, adattata a una soluzione con più
|
||||||
|
progetti. Le differenze rispetto a quel file sono tre, tutte segnate sul
|
||||||
|
posto: la versione vive in Directory.Build.props e non nel .csproj, la
|
||||||
|
pubblicazione produce una cartella e non un singolo eseguibile, e il target
|
||||||
|
Backtest rigioca serie storiche di prezzi invece dei dossier delle aste.
|
||||||
|
|
||||||
|
── Perché MSBuild e non uno script ──────────────────────────────────────
|
||||||
|
La catena vive accanto al codice che rilascia ed è versionata con lui: fra
|
||||||
|
sei mesi, ripescato un tag, questo file ricostruisce quel pacchetto e non
|
||||||
|
quello di oggi. La logica non banale (leggere e riscrivere la versione,
|
||||||
|
parlare con Gitea) sta in attività C# in linea: si legge come codice, non
|
||||||
|
come una successione di comandi.
|
||||||
|
|
||||||
|
── Da dove viene la versione ────────────────────────────────────────────
|
||||||
|
Dal tag git, e da nient'altro. Directory.Build.props non viene mai riscritto:
|
||||||
|
il numero arriva a `dotnet publish` come proprietà da riga di comando, quindi
|
||||||
|
tag, eseguibile, installatore e release portano lo stesso numero per
|
||||||
|
costruzione, non per disciplina.
|
||||||
|
|
||||||
|
Il modo previsto è taggare e poi rilasciare:
|
||||||
|
|
||||||
|
git tag v3.3.0
|
||||||
|
dotnet msbuild build/Release.proj -t:Rilascia
|
||||||
|
|
||||||
|
Se HEAD non ha un tag di versione la catena lo crea da sé — con il numero
|
||||||
|
passato in `-p:Versione=`, oppure la minor successiva all'ultimo tag — e lo
|
||||||
|
fa in fondo, quando l'installatore esiste davvero. Un giro andato male non
|
||||||
|
lascia dietro un tag per una versione che non è mai stata costruita.
|
||||||
|
|
||||||
|
Il numero in Directory.Build.props resta quello delle compilazioni di
|
||||||
|
sviluppo. Continua ad avere senso alzarlo a ogni modifica — è quello che
|
||||||
|
compare nel titolo della finestra durante il lavoro — ma non decide più cosa
|
||||||
|
viene rilasciato: serve solo come seme al primissimo rilascio, quando non
|
||||||
|
esiste ancora nessun tag da cui ripartire.
|
||||||
|
-->
|
||||||
|
<Project DefaultTargets="Pacchetto" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Radice>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..'))</Radice>
|
||||||
|
<Csproj>$(Radice)\src\Encelado.Bot\Encelado.Bot.csproj</Csproj>
|
||||||
|
<TestProj>$(Radice)\tests\Encelado.Tests\Encelado.Tests.csproj</TestProj>
|
||||||
|
<BacktestProj>$(Radice)\tools\Encelado.Backtest\Encelado.Backtest.csproj</BacktestProj>
|
||||||
|
<Iss>$(MSBuildThisFileDirectory)Encelado.iss</Iss>
|
||||||
|
|
||||||
|
<!-- La versione non sta nel .csproj come in AutoBidder: sta in
|
||||||
|
Directory.Build.props, da cui la ereditano tutti e quattro i progetti.
|
||||||
|
È il file che VersioneDaTag legge quando non esiste ancora nessun tag. -->
|
||||||
|
<Props>$(Radice)\Directory.Build.props</Props>
|
||||||
|
|
||||||
|
<CartellaPubblicazione>$(Radice)\bin\publish\win-x64</CartellaPubblicazione>
|
||||||
|
<CartellaPacchetti>$(Radice)\bin\installer</CartellaPacchetti>
|
||||||
|
|
||||||
|
<!-- ── Perché una cartella a parte per verifica e backtest ─────────────
|
||||||
|
Due ragioni, e servono entrambe.
|
||||||
|
|
||||||
|
La prima: l'applicazione può essere aperta mentre si lavora, e tiene
|
||||||
|
bloccato Encelado.exe — la compilazione si fermerebbe su MSB3027.
|
||||||
|
|
||||||
|
La seconda: l'opzione artifacts-path sposta anche gli INTERMEDI, non
|
||||||
|
solo il risultato. La sola -o li lascia nella obj/ condivisa, e la
|
||||||
|
compilazione WPF — che genera un progetto temporaneo `_wpftmp.csproj` a
|
||||||
|
ogni giro — ogni tanto ci trovava stato altrui e smetteva di produrre
|
||||||
|
le classi parziali dello XAML. Il sintomo era una raffica di
|
||||||
|
"AuctionMonitorControl non contiene una definizione di ...", a giri
|
||||||
|
alterni, senza che il codice fosse cambiato. -->
|
||||||
|
<CartellaProve>$([System.IO.Path]::GetTempPath())Encelado.Verifica</CartellaProve>
|
||||||
|
|
||||||
|
<!-- Serve solo quando HEAD non è ancora taggato: è il numero del tag da
|
||||||
|
creare. Vuoto = la minor successiva all'ultimo tag. Vedi VersioneDaTag. -->
|
||||||
|
<Versione Condition="'$(Versione)' == ''"></Versione>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Note di rilascio. Vuote = solo il numero di versione.
|
||||||
|
|
||||||
|
Arrivano da una variabile d'ambiente, non da -p:. MSBuild spezza il valore
|
||||||
|
di una proprietà sulle virgole: `-p:Note=uno, due` diventa la proprietà
|
||||||
|
Note=uno più l'opzione " due", e si finisce su MSB1006 "proprietà non
|
||||||
|
valida". Una nota di rilascio in italiano contiene quasi sempre una
|
||||||
|
virgola, quindi il passaggio per riga di comando è inutilizzabile.
|
||||||
|
L'ambiente non ha questo problema, e MSBuild legge le variabili
|
||||||
|
d'ambiente come proprietà.
|
||||||
|
|
||||||
|
-p:Note= resta accettato per chi lo passa a mano senza virgole.
|
||||||
|
-->
|
||||||
|
<Note Condition="'$(Note)' == ''">$(ENCELADO_NOTE)</Note>
|
||||||
|
|
||||||
|
<!-- ── Perché serve azzerare queste variabili ──────────────────────────
|
||||||
|
`dotnet test` e `dotnet publish` lanciati da dentro MSBuild ereditano
|
||||||
|
l'ambiente del processo padre. La compilazione WPF crea un progetto
|
||||||
|
temporaneo (_wpftmp.csproj) e con quelle variabili puntate al build in
|
||||||
|
corso non genera più le classi parziali dello XAML: si ottengono decine
|
||||||
|
di "AuctionMonitorControl non contiene una definizione di ..." che non
|
||||||
|
hanno niente a che vedere col codice.
|
||||||
|
|
||||||
|
Si azzerano SOLO queste due. Togliere anche MSBuildExtensionsPath o
|
||||||
|
MSBuildSDKsPath — la ricetta che gira in rete — fa perdere al figlio la
|
||||||
|
posizione dell'SDK: "l'SDK Microsoft.NET.Sdk specificato non è stato
|
||||||
|
trovato". Serve isolare il motore, non nascondergli dove abita. -->
|
||||||
|
<AmbientePulito>MSBUILD_EXE_PATH=;MSBuildLoadMicrosoftTargetsReadOnly=</AmbientePulito>
|
||||||
|
|
||||||
|
<SaltaVerifica Condition="'$(SaltaVerifica)' == ''">false</SaltaVerifica>
|
||||||
|
<Sovrascrivi Condition="'$(Sovrascrivi)' == ''">false</Sovrascrivi>
|
||||||
|
<Bozza Condition="'$(Bozza)' == ''">false</Bozza>
|
||||||
|
|
||||||
|
<!-- Un rilascio da albero sporco produce un tag che non corrisponde a
|
||||||
|
nessuno stato ricostruibile: è esattamente la garanzia che il tag come
|
||||||
|
fonte unica dovrebbe dare. Si può forzare, ma va detto. -->
|
||||||
|
<ConsentiModifiche Condition="'$(ConsentiModifiche)' == ''">false</ConsentiModifiche>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- ═════════════════════ Attività in linea ═════════════════════ -->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Decide quale versione si sta costruendo, senza toccare niente.
|
||||||
|
|
||||||
|
Il tag su HEAD, se c'è, comanda: è la fonte. Se non c'è se ne propone uno —
|
||||||
|
il numero chiesto, o la minor successiva al tag più alto esistente — che
|
||||||
|
verrà creato solo a pacchetto pronto, da CreaTag.
|
||||||
|
-->
|
||||||
|
<UsingTask TaskName="VersioneDaTag" TaskFactory="RoslynCodeTaskFactory"
|
||||||
|
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
|
||||||
|
<ParameterGroup>
|
||||||
|
<TagsHead ParameterType="System.String" />
|
||||||
|
<TagsTutti ParameterType="System.String" />
|
||||||
|
<Richiesta ParameterType="System.String" />
|
||||||
|
<FileVersione ParameterType="System.String" Required="true" />
|
||||||
|
<Versione ParameterType="System.String" Output="true" />
|
||||||
|
<Tag ParameterType="System.String" Output="true" />
|
||||||
|
<DaCreare ParameterType="System.String" Output="true" />
|
||||||
|
</ParameterGroup>
|
||||||
|
<Task>
|
||||||
|
<Using Namespace="System" />
|
||||||
|
<Using Namespace="System.IO" />
|
||||||
|
<Using Namespace="System.Collections.Generic" />
|
||||||
|
<Using Namespace="System.Text.RegularExpressions" />
|
||||||
|
<Code Type="Fragment" Language="cs">
|
||||||
|
<![CDATA[
|
||||||
|
// ConsoleToMSBuild unisce le righe con il punto e virgola; git le separa
|
||||||
|
// con a-capo. Si accettano entrambi e non si fanno domande.
|
||||||
|
Func<string, string[]> spezza = s =>
|
||||||
|
(s ?? "").Split(new[] { ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
var forma = new Regex(@"^v(\d+)\.(\d+)\.(\d+)$");
|
||||||
|
|
||||||
|
var suHead = new List<string>();
|
||||||
|
foreach (var t in spezza(TagsHead))
|
||||||
|
if (forma.IsMatch(t.Trim())) suHead.Add(t.Trim());
|
||||||
|
|
||||||
|
if (suHead.Count > 1)
|
||||||
|
{
|
||||||
|
Log.LogError(
|
||||||
|
"HEAD ha piu' di un tag di versione (" + string.Join(", ", suHead) + ").\n" +
|
||||||
|
"Non si puo' sapere quale sia il rilascio: tienine uno solo con git tag -d <tag>");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chiesta = (Richiesta ?? "").Trim();
|
||||||
|
if (chiesta.Length > 0 && !Regex.IsMatch(chiesta, @"^\d+\.\d+\.\d+$"))
|
||||||
|
{
|
||||||
|
// Un refuso qui produrrebbe un tag e un pacchetto sbagliati.
|
||||||
|
Log.LogError("Versione '" + chiesta + "' non valida: serve la forma X.Y.Z");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suHead.Count == 1)
|
||||||
|
{
|
||||||
|
var dalTag = suHead[0].Substring(1);
|
||||||
|
|
||||||
|
if (chiesta.Length > 0 && chiesta != dalTag)
|
||||||
|
{
|
||||||
|
Log.LogError(
|
||||||
|
"HEAD e' gia' taggato " + suHead[0] + ", ma e' stata chiesta la versione " + chiesta + ".\n" +
|
||||||
|
"Il tag e' la fonte: o rilasci " + dalTag + " lasciando vuota la versione,\n" +
|
||||||
|
"oppure togli il tag con git tag -d " + suHead[0] + " e rilancia.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Versione = dalTag;
|
||||||
|
Tag = suHead[0];
|
||||||
|
DaCreare = "false";
|
||||||
|
Log.LogMessage(MessageImportance.High, " versione " + Versione + " — dal tag " + Tag + " su HEAD");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chiesta.Length > 0)
|
||||||
|
{
|
||||||
|
Versione = chiesta;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// La minor successiva al tag piu' alto: cosi' il numero proposto e'
|
||||||
|
// sempre libero, anche se il tag piu' alto sta su un altro ramo.
|
||||||
|
int maggiore = -1, minore = -1;
|
||||||
|
foreach (var t in spezza(TagsTutti))
|
||||||
|
{
|
||||||
|
var m = forma.Match(t.Trim());
|
||||||
|
if (!m.Success) continue;
|
||||||
|
|
||||||
|
int ma = int.Parse(m.Groups[1].Value), mi = int.Parse(m.Groups[2].Value);
|
||||||
|
if (ma > maggiore || (ma == maggiore && mi > minore)) { maggiore = ma; minore = mi; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maggiore < 0)
|
||||||
|
{
|
||||||
|
// Primo rilascio: non c'e' nessun tag da cui ripartire, e l'unico
|
||||||
|
// numero che esiste e' quello delle compilazioni di sviluppo.
|
||||||
|
var testo = File.Exists(FileVersione) ? File.ReadAllText(FileVersione) : "";
|
||||||
|
var m = Regex.Match(testo, @"<Version>(\d+)\.(\d+)\.\d+</Version>");
|
||||||
|
if (!m.Success)
|
||||||
|
{
|
||||||
|
Log.LogError(
|
||||||
|
"Nessun tag di versione nel repository e <Version> illeggibile in " + FileVersione + ".\n" +
|
||||||
|
"Indica la versione con -p:Versione=X.Y.Z");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
maggiore = int.Parse(m.Groups[1].Value);
|
||||||
|
minore = int.Parse(m.Groups[2].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Versione = maggiore + "." + (minore + 1) + ".0";
|
||||||
|
}
|
||||||
|
|
||||||
|
Tag = "v" + Versione;
|
||||||
|
DaCreare = "true";
|
||||||
|
Log.LogMessage(MessageImportance.High, " versione " + Versione + " — il tag " + Tag + " sara' creato a pacchetto pronto");
|
||||||
|
]]>
|
||||||
|
</Code>
|
||||||
|
</Task>
|
||||||
|
</UsingTask>
|
||||||
|
|
||||||
|
<UsingTask TaskName="TrovaInnoSetup" TaskFactory="RoslynCodeTaskFactory"
|
||||||
|
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
|
||||||
|
<ParameterGroup>
|
||||||
|
<Percorso ParameterType="System.String" Output="true" />
|
||||||
|
</ParameterGroup>
|
||||||
|
<Task>
|
||||||
|
<Using Namespace="System" />
|
||||||
|
<Using Namespace="System.IO" />
|
||||||
|
<Code Type="Fragment" Language="cs">
|
||||||
|
<![CDATA[
|
||||||
|
var candidati = new[]
|
||||||
|
{
|
||||||
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Inno Setup 6\ISCC.exe"),
|
||||||
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Inno Setup 6\ISCC.exe"),
|
||||||
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), @"Inno Setup 6\ISCC.exe"),
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var c in candidati)
|
||||||
|
if (File.Exists(c)) { Percorso = c; break; }
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(Percorso))
|
||||||
|
Log.LogError("Inno Setup 6 non trovato. Installalo con: winget install -e --id JRSoftware.InnoSetup");
|
||||||
|
]]>
|
||||||
|
</Code>
|
||||||
|
</Task>
|
||||||
|
</UsingTask>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Gitea si raggiunge con curl, di serie in Windows 10 e 11.
|
||||||
|
|
||||||
|
L'alternativa naturale sarebbe HttpClient in un'attività C# in linea, ma
|
||||||
|
RoslynCodeTaskFactory referenzia solo gli assembly di base: System.Net.Http e
|
||||||
|
System.Text.Json andrebbero indicati per percorso assoluto, dentro il runtime
|
||||||
|
condiviso, con il numero di versione nel mezzo. Un percorso che oggi funziona e
|
||||||
|
al prossimo aggiornamento di .NET no. curl non ha questo problema.
|
||||||
|
|
||||||
|
Il token NON passa mai dalla riga di comando: sta in un file di configurazione
|
||||||
|
di curl, che viene cancellato subito dopo. Gli Exec hanno EchoOff perché un
|
||||||
|
registro di compilazione è la classica cosa che si incolla in una chat.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<UsingTask TaskName="LeggiConfigGitea" TaskFactory="RoslynCodeTaskFactory"
|
||||||
|
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
|
||||||
|
<ParameterGroup>
|
||||||
|
<Percorso ParameterType="System.String" Required="true" />
|
||||||
|
<Url ParameterType="System.String" Output="true" />
|
||||||
|
<Owner ParameterType="System.String" Output="true" />
|
||||||
|
<Repo ParameterType="System.String" Output="true" />
|
||||||
|
<Token ParameterType="System.String" Output="true" />
|
||||||
|
</ParameterGroup>
|
||||||
|
<Task>
|
||||||
|
<Using Namespace="System" />
|
||||||
|
<Using Namespace="System.IO" />
|
||||||
|
<Using Namespace="System.Text.RegularExpressions" />
|
||||||
|
<Code Type="Fragment" Language="cs">
|
||||||
|
<![CDATA[
|
||||||
|
var testo = File.Exists(Percorso) ? File.ReadAllText(Percorso) : "";
|
||||||
|
|
||||||
|
// Le variabili d'ambiente hanno la precedenza sul file: una macchina
|
||||||
|
// condivisa deve poter rilasciare senza scrivere un token su disco, e un
|
||||||
|
// file dimenticato non deve vincere su una scelta esplicita.
|
||||||
|
Func<string,string,string> leggi = (env, campo) =>
|
||||||
|
{
|
||||||
|
var v = Environment.GetEnvironmentVariable(env);
|
||||||
|
if (!string.IsNullOrWhiteSpace(v)) return v.Trim();
|
||||||
|
|
||||||
|
var m = Regex.Match(testo, "\"" + campo + "\"\\s*:\\s*\"([^\"]*)\"");
|
||||||
|
return m.Success ? m.Groups[1].Value.Trim() : "";
|
||||||
|
};
|
||||||
|
|
||||||
|
Url = leggi("GITEA_URL", "url").TrimEnd('/');
|
||||||
|
Owner = leggi("GITEA_OWNER", "owner");
|
||||||
|
Repo = leggi("GITEA_REPO", "repo");
|
||||||
|
Token = leggi("GITEA_TOKEN", "token");
|
||||||
|
|
||||||
|
if (Url.Length == 0 || Owner.Length == 0 || Repo.Length == 0 || Token.Length == 0)
|
||||||
|
{
|
||||||
|
Log.LogError(
|
||||||
|
"Configurazione di Gitea incompleta. Servono url, owner, repo, token:\n" +
|
||||||
|
" copia build/gitea.example.json in build/gitea.json e riempilo,\n" +
|
||||||
|
" oppure imposta GITEA_URL, GITEA_OWNER, GITEA_REPO, GITEA_TOKEN.\n" +
|
||||||
|
"Non e' stato costruito niente: si controlla prima di compilare, non dopo.\n" +
|
||||||
|
"Per il solo installatore, senza Gitea, usa il target Pacchetto.");
|
||||||
|
|
||||||
|
// Senza questo il target prosegue lo stesso: git tag, poi curl con
|
||||||
|
// l'indirizzo vuoto, e infine un "codice 3" che non dice niente a
|
||||||
|
// nessuno. Un errore va fermato dove si capisce ancora cos'era.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</Code>
|
||||||
|
</Task>
|
||||||
|
</UsingTask>
|
||||||
|
|
||||||
|
<UsingTask TaskName="PreparaCorpoRelease" TaskFactory="RoslynCodeTaskFactory"
|
||||||
|
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
|
||||||
|
<ParameterGroup>
|
||||||
|
<Destinazione ParameterType="System.String" Required="true" />
|
||||||
|
<Tag ParameterType="System.String" Required="true" />
|
||||||
|
<Versione ParameterType="System.String" Required="true" />
|
||||||
|
<Note ParameterType="System.String" />
|
||||||
|
<Bozza ParameterType="System.Boolean" />
|
||||||
|
</ParameterGroup>
|
||||||
|
<Task>
|
||||||
|
<Using Namespace="System" />
|
||||||
|
<Using Namespace="System.IO" />
|
||||||
|
<Code Type="Fragment" Language="cs">
|
||||||
|
<![CDATA[
|
||||||
|
// Le note arrivano da un prompt: possono contenere virgolette, barre e
|
||||||
|
// a-capo. Scritte grezze romperebbero il JSON, o peggio lo cambierebbero.
|
||||||
|
Func<string,string> esc = t => (t ?? "")
|
||||||
|
.Replace("\\", "\\\\").Replace("\"", "\\\"")
|
||||||
|
.Replace("\r", "").Replace("\n", "\\n").Replace("\t", " ");
|
||||||
|
|
||||||
|
var note = string.IsNullOrWhiteSpace(Note) ? "Versione " + Versione + "." : Note;
|
||||||
|
|
||||||
|
File.WriteAllText(Destinazione,
|
||||||
|
"{\"tag_name\":\"" + esc(Tag) + "\"," +
|
||||||
|
"\"name\":\"Encelado " + esc(Versione) + "\"," +
|
||||||
|
"\"body\":\"" + esc(note) + "\"," +
|
||||||
|
"\"draft\":" + (Bozza ? "true" : "false") + "," +
|
||||||
|
"\"prerelease\":false}");
|
||||||
|
]]>
|
||||||
|
</Code>
|
||||||
|
</Task>
|
||||||
|
</UsingTask>
|
||||||
|
|
||||||
|
<UsingTask TaskName="LeggiIdRelease" TaskFactory="RoslynCodeTaskFactory"
|
||||||
|
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
|
||||||
|
<ParameterGroup>
|
||||||
|
<Risposta ParameterType="System.String" Required="true" />
|
||||||
|
<Id ParameterType="System.String" Output="true" />
|
||||||
|
<Errore ParameterType="System.String" Output="true" />
|
||||||
|
</ParameterGroup>
|
||||||
|
<Task>
|
||||||
|
<Using Namespace="System" />
|
||||||
|
<Using Namespace="System.IO" />
|
||||||
|
<Using Namespace="System.Text.RegularExpressions" />
|
||||||
|
<Code Type="Fragment" Language="cs">
|
||||||
|
<![CDATA[
|
||||||
|
var testo = File.Exists(Risposta) ? File.ReadAllText(Risposta) : "";
|
||||||
|
|
||||||
|
// L'id della release e' il primo campo "id" della risposta; quelli annidati
|
||||||
|
// (autore, allegati) vengono dopo. Si prende il primo e basta.
|
||||||
|
var m = Regex.Match(testo, "\"id\"\\s*:\\s*(\\d+)");
|
||||||
|
Id = m.Success ? m.Groups[1].Value : "";
|
||||||
|
|
||||||
|
if (Id.Length == 0)
|
||||||
|
{
|
||||||
|
var msg = Regex.Match(testo, "\"message\"\\s*:\\s*\"([^\"]*)\"");
|
||||||
|
Errore = msg.Success ? msg.Groups[1].Value
|
||||||
|
: (testo.Length > 200 ? testo.Substring(0, 200) : testo);
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</Code>
|
||||||
|
</Task>
|
||||||
|
</UsingTask>
|
||||||
|
|
||||||
|
<!-- ═════════════════════ Verifica ═════════════════════ -->
|
||||||
|
|
||||||
|
<Target Name="Verifica" Condition="'$(SaltaVerifica)' != 'true'">
|
||||||
|
<Message Importance="High" Text="== Verifica (compilazione + test) ==" />
|
||||||
|
|
||||||
|
<!-- In una cartella a parte: l'applicazione puo' essere aperta e tenere
|
||||||
|
bloccato Encelado.exe. -->
|
||||||
|
<Exec Command="dotnet test "$(TestProj)" --nologo -v q --artifacts-path "$(CartellaProve)""
|
||||||
|
WorkingDirectory="$(Radice)"
|
||||||
|
EnvironmentVariables="$(AmbientePulito)" />
|
||||||
|
|
||||||
|
<Message Importance="High" Text=" tutto a posto" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<!-- ═════════════════════ Rigiocata sui prezzi ═════════════════════ -->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Il corrispettivo del backtest sui dossier di AutoBidder. Qui non si rigiocano
|
||||||
|
aste ma serie storiche di prezzi, con lo strumento in tools/Encelado.Backtest.
|
||||||
|
|
||||||
|
I parametri li legge da config/encelado.json, non li ridichiara: il banco e
|
||||||
|
il bot non possono divergere senza che qualcuno se ne accorga.
|
||||||
|
-->
|
||||||
|
<Target Name="Backtest">
|
||||||
|
<PropertyGroup>
|
||||||
|
<Comando Condition="'$(Comando)' == ''">split</Comando>
|
||||||
|
<Barre Condition="'$(Barre)' == ''">1d</Barre>
|
||||||
|
<BacktestExe>$(Radice)\tools\Encelado.Backtest\bin\Release\net10.0\backtest.exe</BacktestExe>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Error Condition="'$(Dati)' == ''"
|
||||||
|
Text="Serve un file di dati: -p:Dati="C:\percorso\btcusd.csv".%0AComandi disponibili in -p:Comando= : split, sweep, walk, frequency, costs, explore." />
|
||||||
|
|
||||||
|
<Error Condition="!Exists('$(Dati)')" Text="File di dati non trovato: $(Dati)" />
|
||||||
|
|
||||||
|
<Message Importance="High" Text="== Rigiocata sui prezzi ==" />
|
||||||
|
<Message Importance="High" Text=" dati : $(Dati)" />
|
||||||
|
<Message Importance="High" Text=" comando : $(Comando) su barre da $(Barre)" />
|
||||||
|
|
||||||
|
<Exec WorkingDirectory="$(Radice)" EnvironmentVariables="$(AmbientePulito)"
|
||||||
|
Command="dotnet build "$(BacktestProj)" -c Release --nologo -v q" />
|
||||||
|
|
||||||
|
<Exec WorkingDirectory="$(Radice)"
|
||||||
|
Command=""$(BacktestExe)" $(Comando) --file "$(Dati)" --tf $(Barre) $(Extra)" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<!-- ═════════════════════ Eseguibile ═════════════════════ -->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
I quattro numeri di versione arrivano da qui, non da Directory.Build.props:
|
||||||
|
le proprietà da riga di comando sono globali e vincono su quelle scritte nei
|
||||||
|
progetti. Vengono passati tutti e quattro anche se il file ne dichiara uno
|
||||||
|
solo — la finestra legge Assembly.GetName().Version, e vederne divergere uno
|
||||||
|
significa un numero a schermo che mente.
|
||||||
|
|
||||||
|
── Perché una cartella e non un singolo eseguibile ────────────────────────
|
||||||
|
AutoBidder pubblica con PublishSingleFile e allega quel file alla release.
|
||||||
|
Qui non si può: Encelado legge `encelado.json` accanto al proprio eseguibile
|
||||||
|
e ci scrive log, diario e CSV di analisi. Un singolo file estratto in una
|
||||||
|
cartella temporanea a ogni avvio metterebbe la configurazione dell'utente e
|
||||||
|
i suoi log in un percorso che cambia da un avvio all'altro.
|
||||||
|
|
||||||
|
Una cartella non si allega a una release, quindi al suo posto viene allegato
|
||||||
|
uno zip: vedi il target Rilascia.
|
||||||
|
-->
|
||||||
|
<Target Name="Pubblica" DependsOnTargets="DeterminaVersione">
|
||||||
|
<Message Importance="High" Text="== Pubblicazione dell'eseguibile ($(V)) ==" />
|
||||||
|
|
||||||
|
<!-- Cartella pulita a ogni giro. Senza questo i resti di una pubblicazione
|
||||||
|
precedente — una DLL rinominata, un runtime cambiato — finiscono nel
|
||||||
|
pacchetto, ed è il tipo di problema che si manifesta solo sulla
|
||||||
|
macchina di qualcun altro. -->
|
||||||
|
<RemoveDir Directories="$(CartellaPubblicazione)" ContinueOnError="true" />
|
||||||
|
|
||||||
|
<Exec WorkingDirectory="$(Radice)"
|
||||||
|
EnvironmentVariables="$(AmbientePulito)"
|
||||||
|
Command="dotnet publish "$(Csproj)" -c Release -r win-x64 --nologo -v q --self-contained true -p:PublishReadyToRun=true -p:PublishTrimmed=false -p:DebugType=none -p:Version=$(V) -p:AssemblyVersion=$(V).0 -p:FileVersion=$(V).0 -p:InformationalVersion=$(V) -o "$(CartellaPubblicazione)"" />
|
||||||
|
|
||||||
|
<Error Condition="!Exists('$(CartellaPubblicazione)\Encelado.exe')"
|
||||||
|
Text="Pubblicazione fallita: Encelado.exe non trovato." />
|
||||||
|
|
||||||
|
<!-- Senza configurazione l'applicazione non parte, e l'installatore la
|
||||||
|
copierebbe senza accorgersi che manca. -->
|
||||||
|
<Error Condition="!Exists('$(CartellaPubblicazione)\encelado.json')"
|
||||||
|
Text="Pubblicazione incompleta: encelado.json non è finito accanto all'eseguibile." />
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Nella release non devono finire i sorgenti: si pubblica il programma, non
|
||||||
|
il progetto. La cartella pubblicata diventa lo zip portabile, quindi basta
|
||||||
|
controllare qui.
|
||||||
|
|
||||||
|
Non è teorico: un <None CopyToOutputDirectory> aggiunto per comodità, o un
|
||||||
|
pacchetto che porta i propri .cs, li farebbe scivolare dentro senza che
|
||||||
|
nessuno se ne accorga fino a quando qualcuno non apre lo zip.
|
||||||
|
-->
|
||||||
|
<ItemGroup>
|
||||||
|
<SorgenteIntruso Include="$(CartellaPubblicazione)\**\*.cs" />
|
||||||
|
<SorgenteIntruso Include="$(CartellaPubblicazione)\**\*.csproj" />
|
||||||
|
<SorgenteIntruso Include="$(CartellaPubblicazione)\**\*.xaml" />
|
||||||
|
<SorgenteIntruso Include="$(CartellaPubblicazione)\**\*.pdb" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Error Condition="'@(SorgenteIntruso)' != ''"
|
||||||
|
Text="Nella cartella pubblicata ci sono file che non sono programma: @(SorgenteIntruso->'%(Filename)%(Extension)', ', ').%0ANon devono finire nella release." />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<!-- ═════════════════════ Pacchetto ═════════════════════ -->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
L'ordine conta: si verifica, si costruisce, e il tag si crea per ultimo.
|
||||||
|
|
||||||
|
Il contrario sembra piu' naturale — decidi il numero, poi costruisci — ma
|
||||||
|
lascia dietro un tag quando qualcosa va storto, e il tentativo successivo
|
||||||
|
riparte da li'. Un giro andato male e il numero e' salito lo stesso, senza
|
||||||
|
che sia mai esistito un pacchetto con quella versione. Taggando in fondo,
|
||||||
|
ogni tag corrisponde a un installatore che esiste davvero.
|
||||||
|
-->
|
||||||
|
<Target Name="Pacchetto" DependsOnTargets="Verifica;DeterminaVersione;Pubblica">
|
||||||
|
<Message Importance="High" Text="== Creazione dell'installatore ==" />
|
||||||
|
|
||||||
|
<TrovaInnoSetup>
|
||||||
|
<Output TaskParameter="Percorso" PropertyName="Iscc" />
|
||||||
|
</TrovaInnoSetup>
|
||||||
|
|
||||||
|
<MakeDir Directories="$(CartellaPacchetti)" />
|
||||||
|
|
||||||
|
<Exec WorkingDirectory="$(MSBuildThisFileDirectory)"
|
||||||
|
Command=""$(Iscc)" /Qp "/DAppVersion=$(V)" "/DSourceDir=$(CartellaPubblicazione)" "/DOutputDir=$(CartellaPacchetti)" "$(Iss)"" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Setup>$(CartellaPacchetti)\Encelado_$(V).exe</Setup>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Error Condition="!Exists('$(Setup)')" Text="Installatore non trovato: $(Setup)" />
|
||||||
|
|
||||||
|
<!-- Adesso: il pacchetto c'e', il tag puo' esistere. -->
|
||||||
|
<CallTarget Targets="CreaTag" />
|
||||||
|
|
||||||
|
<Message Importance="High" Text=" " />
|
||||||
|
<Message Importance="High" Text="Pacchetto pronto:" />
|
||||||
|
<Message Importance="High" Text=" $(Setup)" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<!-- ═════════════════════ Versione e tag ═════════════════════ -->
|
||||||
|
|
||||||
|
<Target Name="DeterminaVersione">
|
||||||
|
<!-- Nessuno dei due comandi fallisce mai: senza tag l'uscita e' vuota, e
|
||||||
|
basta. `git describe` invece esce in errore, e qui non serve. -->
|
||||||
|
<Exec Command="git tag --points-at HEAD" WorkingDirectory="$(Radice)"
|
||||||
|
ConsoleToMSBuild="true" StandardOutputImportance="low">
|
||||||
|
<Output TaskParameter="ConsoleOutput" PropertyName="TagSuHead" />
|
||||||
|
</Exec>
|
||||||
|
|
||||||
|
<Exec Command="git tag --list v*" WorkingDirectory="$(Radice)"
|
||||||
|
ConsoleToMSBuild="true" StandardOutputImportance="low">
|
||||||
|
<Output TaskParameter="ConsoleOutput" PropertyName="TagEsistenti" />
|
||||||
|
</Exec>
|
||||||
|
|
||||||
|
<VersioneDaTag TagsHead="$(TagSuHead)" TagsTutti="$(TagEsistenti)"
|
||||||
|
Richiesta="$(Versione)" FileVersione="$(Props)">
|
||||||
|
<Output TaskParameter="Versione" PropertyName="V" />
|
||||||
|
<Output TaskParameter="Tag" PropertyName="Tag" />
|
||||||
|
<Output TaskParameter="DaCreare" PropertyName="TagDaCreare" />
|
||||||
|
</VersioneDaTag>
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<Target Name="CreaTag" Condition="'$(TagDaCreare)' == 'true'">
|
||||||
|
<!-- Solo le modifiche ai file gia' tracciati: un appunto non versionato
|
||||||
|
accanto al progetto non cambia cosa viene compilato. -->
|
||||||
|
<Exec Command="git status --porcelain --untracked-files=no" WorkingDirectory="$(Radice)"
|
||||||
|
ConsoleToMSBuild="true" StandardOutputImportance="low">
|
||||||
|
<Output TaskParameter="ConsoleOutput" PropertyName="AlberoSporco" />
|
||||||
|
</Exec>
|
||||||
|
|
||||||
|
<Error Condition="'$(AlberoSporco)' != '' AND '$(ConsentiModifiche)' != 'true'"
|
||||||
|
Text="Ci sono modifiche non committate: il tag $(Tag) indicherebbe uno stato che non e' ricostruibile.%0ACommitta prima di rilasciare, oppure rilancia con -p:ConsentiModifiche=true se sai cosa stai facendo.%0A%0A$(AlberoSporco)" />
|
||||||
|
|
||||||
|
<Exec Command="git tag -a $(Tag) -m "Encelado $(V)"" WorkingDirectory="$(Radice)"
|
||||||
|
StandardOutputImportance="low" StandardErrorImportance="low" />
|
||||||
|
|
||||||
|
<Message Importance="High" Text=" tag $(Tag) creato" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<!-- ═════════════════════ Rilascio ═════════════════════ -->
|
||||||
|
|
||||||
|
<!-- Prima di tutto il resto: un token mancante non deve costare due minuti di
|
||||||
|
compilazione per poi fermarsi all'ultimo passo. -->
|
||||||
|
<Target Name="ConfigGitea">
|
||||||
|
<LeggiConfigGitea Percorso="$(MSBuildThisFileDirectory)gitea.json">
|
||||||
|
<Output TaskParameter="Url" PropertyName="GUrl" />
|
||||||
|
<Output TaskParameter="Owner" PropertyName="GOwner" />
|
||||||
|
<Output TaskParameter="Repo" PropertyName="GRepo" />
|
||||||
|
<Output TaskParameter="Token" PropertyName="GToken" />
|
||||||
|
</LeggiConfigGitea>
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<Target Name="Rilascia" DependsOnTargets="ConfigGitea;Pacchetto">
|
||||||
|
<Message Importance="High" Text="== Pubblicazione su Gitea ==" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Api>$(GUrl)/api/v1/repos/$(GOwner)/$(GRepo)</Api>
|
||||||
|
<Tmp>$([System.IO.Path]::GetTempPath())Encelado.Rilascio</Tmp>
|
||||||
|
<CurlCfg>$(Tmp)\curl.cfg</CurlCfg>
|
||||||
|
<CorpoJson>$(Tmp)\release.json</CorpoJson>
|
||||||
|
<RispostaJson>$(Tmp)\risposta.json</RispostaJson>
|
||||||
|
<Setup>$(CartellaPacchetti)\Encelado_$(V).exe</Setup>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<MakeDir Directories="$(Tmp)" />
|
||||||
|
|
||||||
|
<!-- Il token vive qui e solo qui, per il tempo del rilascio. -->
|
||||||
|
<WriteLinesToFile File="$(CurlCfg)" Overwrite="true"
|
||||||
|
Lines="header = "Authorization: token $(GToken)"" />
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Il tag esiste gia' in locale: l'ha creato Pacchetto, o c'era prima. Qui va
|
||||||
|
spinto, e va verificato che ci sia arrivato.
|
||||||
|
|
||||||
|
Non e' una formalita'. Il push qui sotto spinge i tag, non i commit: se il
|
||||||
|
ramo e' indietro, il tag punta a un oggetto che il remoto non conosce e il
|
||||||
|
push viene rifiutato. A quel punto la creazione della release non
|
||||||
|
fallisce — Gitea, non trovando il tag, lo crea da se' sulla testa del ramo
|
||||||
|
predefinito. Verrebbe pubblicata una release che dichiara di essere il
|
||||||
|
commit X mentre il codice allegato e' il commit Y, e nessuno se ne
|
||||||
|
accorgerebbe. Meglio fermarsi e dire cosa manca.
|
||||||
|
|
||||||
|
I commit non si spingono da qui: quando spingere il ramo lo decide chi
|
||||||
|
lavora, non la catena di rilascio.
|
||||||
|
-->
|
||||||
|
<Exec Command="git push --tags" WorkingDirectory="$(Radice)" ContinueOnError="true"
|
||||||
|
StandardOutputImportance="low" StandardErrorImportance="low" />
|
||||||
|
|
||||||
|
<Exec Command="git ls-remote --tags origin refs/tags/$(Tag)" WorkingDirectory="$(Radice)"
|
||||||
|
ConsoleToMSBuild="true" ContinueOnError="true" StandardOutputImportance="low">
|
||||||
|
<Output TaskParameter="ConsoleOutput" PropertyName="TagSulRemoto" />
|
||||||
|
</Exec>
|
||||||
|
|
||||||
|
<Error Condition="'$(TagSulRemoto)' == ''"
|
||||||
|
Text="Il tag $(Tag) non e' arrivato sul remoto, quasi sempre perche' il ramo e' indietro.%0AGitea creerebbe la release sulla testa del ramo predefinito: il codice allegato non corrisponderebbe al commit dichiarato.%0A%0ASpingi il ramo e rilancia: git push" />
|
||||||
|
|
||||||
|
<Message Importance="High" Text=" tag $(Tag) sul remoto" />
|
||||||
|
|
||||||
|
<!-- Release gia' presente? -->
|
||||||
|
<Exec EchoOff="true" ContinueOnError="true" StandardOutputImportance="low"
|
||||||
|
Command="curl -s -K "$(CurlCfg)" -o "$(RispostaJson)" "$(Api)/releases/tags/$(Tag)"" />
|
||||||
|
|
||||||
|
<LeggiIdRelease Risposta="$(RispostaJson)">
|
||||||
|
<Output TaskParameter="Id" PropertyName="IdEsistente" />
|
||||||
|
</LeggiIdRelease>
|
||||||
|
|
||||||
|
<Error Condition="'$(IdEsistente)' != '' AND '$(Sovrascrivi)' != 'true'"
|
||||||
|
Text="La release $(Tag) esiste gia'. Alza la versione, oppure rilancia con -p:Sovrascrivi=true." />
|
||||||
|
|
||||||
|
<Exec Condition="'$(IdEsistente)' != ''" EchoOff="true" StandardOutputImportance="low"
|
||||||
|
Command="curl -s -K "$(CurlCfg)" -X DELETE "$(Api)/releases/$(IdEsistente)"" />
|
||||||
|
<Message Condition="'$(IdEsistente)' != ''" Importance="High"
|
||||||
|
Text=" release $(Tag) esistente: sostituita" />
|
||||||
|
|
||||||
|
<!-- Creazione -->
|
||||||
|
<PreparaCorpoRelease Destinazione="$(CorpoJson)" Tag="$(Tag)" Versione="$(V)"
|
||||||
|
Note="$(Note)" Bozza="$(Bozza)" />
|
||||||
|
|
||||||
|
<Exec EchoOff="true" StandardOutputImportance="low"
|
||||||
|
Command="curl -s -K "$(CurlCfg)" -X POST -H "Content-Type: application/json" --data-binary "@$(CorpoJson)" -o "$(RispostaJson)" "$(Api)/releases"" />
|
||||||
|
|
||||||
|
<LeggiIdRelease Risposta="$(RispostaJson)">
|
||||||
|
<Output TaskParameter="Id" PropertyName="IdRelease" />
|
||||||
|
<Output TaskParameter="Errore" PropertyName="ErroreRelease" />
|
||||||
|
</LeggiIdRelease>
|
||||||
|
|
||||||
|
<Error Condition="'$(IdRelease)' == ''"
|
||||||
|
Text="Creazione della release non riuscita: $(ErroreRelease)" />
|
||||||
|
|
||||||
|
<Message Importance="High" Text=" release creata" />
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Allegati: l'installatore e una copia portabile. Chi non vuole installare
|
||||||
|
niente deve continuare a poter scaricare l'applicazione e basta.
|
||||||
|
|
||||||
|
In AutoBidder la copia portabile è il solo .exe, perché lì la
|
||||||
|
pubblicazione è un file unico. Qui è una cartella — l'applicazione ha
|
||||||
|
bisogno di encelado.json accanto a sé — quindi la copia portabile è uno
|
||||||
|
zip di quella cartella.
|
||||||
|
-->
|
||||||
|
<PropertyGroup>
|
||||||
|
<Portabile>$(CartellaPacchetti)\Encelado_$(V)_portabile.zip</Portabile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Delete Files="$(Portabile)" ContinueOnError="true" />
|
||||||
|
<ZipDirectory SourceDirectory="$(CartellaPubblicazione)" DestinationFile="$(Portabile)" />
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Allegato Include="$(Setup)" />
|
||||||
|
<Allegato Include="$(Portabile)" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Exec Condition="Exists('%(Allegato.FullPath)')" EchoOff="true" StandardOutputImportance="low"
|
||||||
|
Command="curl -s -K "$(CurlCfg)" -X POST -F "attachment=@%(Allegato.FullPath)" "$(Api)/releases/$(IdRelease)/assets?name=%(Allegato.Filename)%(Allegato.Extension)"" />
|
||||||
|
|
||||||
|
<Message Importance="High" Text=" caricato %(Allegato.Filename)%(Allegato.Extension)"
|
||||||
|
Condition="Exists('%(Allegato.FullPath)')" />
|
||||||
|
|
||||||
|
<!-- Il token non deve sopravvivere al rilascio. -->
|
||||||
|
<Delete Files="$(CurlCfg);$(CorpoJson);$(RispostaJson)" ContinueOnError="true" />
|
||||||
|
|
||||||
|
<Message Importance="High" Text=" " />
|
||||||
|
<Message Importance="High" Text="Rilascio completato:" />
|
||||||
|
<Message Importance="High" Text=" $(GUrl)/$(GOwner)/$(GRepo)/releases/tag/$(Tag)" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"_commento": "Copia questo file in gitea.json e metti il token. gitea.json e' escluso dal controllo di versione perche' contiene una credenziale. In alternativa usa le variabili d'ambiente GITEA_URL, GITEA_OWNER, GITEA_REPO, GITEA_TOKEN, che hanno la precedenza su questo file.",
|
||||||
|
|
||||||
|
"url": "http://192.168.30.23:3000",
|
||||||
|
"owner": "Alby96",
|
||||||
|
"repo": "Encelado",
|
||||||
|
|
||||||
|
"_token": "Gitea > Impostazioni > Applicazioni > Genera nuovo token, permesso 'repository: read and write'.",
|
||||||
|
"token": "INSERISCI_QUI_IL_TOKEN"
|
||||||
|
}
|
||||||
@@ -1,233 +0,0 @@
|
|||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Costruisce Encelado-Setup-<versione>.exe, l'installer distribuibile.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Tre passaggi: pubblica l'applicazione, verifica che il compilatore di Inno Setup
|
|
||||||
ci sia (e se manca lo installa), compila installer\Encelado.iss.
|
|
||||||
|
|
||||||
Il risultato finisce in artifacts\installer\ ed è un singolo eseguibile: chi lo
|
|
||||||
riceve fa doppio clic, non gli viene chiesto nulla dall'UAC e si ritrova Encelado
|
|
||||||
nel menu Start.
|
|
||||||
|
|
||||||
.PARAMETER FrameworkDependent
|
|
||||||
Pubblica senza il runtime .NET incorporato. L'installer scende da ~65 MB a ~2 MB,
|
|
||||||
ma sulla macchina di destinazione deve già esserci il .NET 10 Desktop Runtime,
|
|
||||||
altrimenti l'applicazione non parte e Windows mostra un errore poco chiaro.
|
|
||||||
Ha senso solo per aggiornare una macchina che hai già preparato tu.
|
|
||||||
|
|
||||||
.PARAMETER SkipTests
|
|
||||||
Salta la suite di test prima di pubblicare. Sconsigliato: l'unica ragione per cui
|
|
||||||
i numeri della strategia sono affidabili è che qualcosa li verifica.
|
|
||||||
|
|
||||||
.PARAMETER Configuration
|
|
||||||
Debug o Release. Il default è Release e non c'è motivo di cambiarlo.
|
|
||||||
|
|
||||||
.EXAMPLE
|
|
||||||
.\build\make-installer.ps1
|
|
||||||
|
|
||||||
.EXAMPLE
|
|
||||||
.\build\make-installer.ps1 -FrameworkDependent -SkipTests
|
|
||||||
#>
|
|
||||||
[CmdletBinding()]
|
|
||||||
param(
|
|
||||||
[switch] $FrameworkDependent,
|
|
||||||
[switch] $SkipTests,
|
|
||||||
[ValidateSet('Release', 'Debug')]
|
|
||||||
[string] $Configuration = 'Release'
|
|
||||||
)
|
|
||||||
|
|
||||||
$ErrorActionPreference = 'Stop'
|
|
||||||
Set-StrictMode -Version Latest
|
|
||||||
|
|
||||||
$Root = Split-Path -Parent $PSScriptRoot
|
|
||||||
$Project = Join-Path $Root 'src\Encelado.Bot\Encelado.Bot.csproj'
|
|
||||||
$Solution = Join-Path $Root 'Encelado.slnx'
|
|
||||||
$IssScript = Join-Path $Root 'installer\Encelado.iss'
|
|
||||||
$PublishDir = Join-Path $Root 'artifacts\publish'
|
|
||||||
$OutputDir = Join-Path $Root 'artifacts\installer'
|
|
||||||
|
|
||||||
function Write-Step([string] $Message) {
|
|
||||||
Write-Host ''
|
|
||||||
Write-Host "==> $Message" -ForegroundColor Cyan
|
|
||||||
}
|
|
||||||
|
|
||||||
function Write-Note([string] $Message) {
|
|
||||||
Write-Host " $Message" -ForegroundColor DarkGray
|
|
||||||
}
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------------
|
|
||||||
# Versione
|
|
||||||
# --------------------------------------------------------------------------------
|
|
||||||
# Una sola fonte di verità: Directory.Build.props. Se la versione fosse scritta anche
|
|
||||||
# nello script .iss, prima o poi le due divergerebbero e verrebbero distribuiti due
|
|
||||||
# installer diversi con lo stesso numero sopra.
|
|
||||||
|
|
||||||
function Get-ProductVersion {
|
|
||||||
$propsPath = Join-Path $Root 'Directory.Build.props'
|
|
||||||
if (-not (Test-Path -LiteralPath $propsPath)) {
|
|
||||||
throw "Directory.Build.props non trovato in $Root."
|
|
||||||
}
|
|
||||||
|
|
||||||
$version = ([xml](Get-Content -LiteralPath $propsPath -Raw)).
|
|
||||||
SelectSingleNode('//PropertyGroup/Version')
|
|
||||||
|
|
||||||
if ($null -eq $version -or [string]::IsNullOrWhiteSpace($version.InnerText)) {
|
|
||||||
throw "Nessun elemento <Version> in $propsPath."
|
|
||||||
}
|
|
||||||
|
|
||||||
$text = $version.InnerText.Trim()
|
|
||||||
if ($text -notmatch '^\d+(\.\d+){1,3}$') {
|
|
||||||
throw "Versione '$text' non utilizzabile: Inno Setup vuole da 2 a 4 numeri separati da punti."
|
|
||||||
}
|
|
||||||
|
|
||||||
return $text
|
|
||||||
}
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------------
|
|
||||||
# Compilatore Inno Setup
|
|
||||||
# --------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function Find-Iscc {
|
|
||||||
$command = Get-Command 'ISCC.exe' -ErrorAction SilentlyContinue
|
|
||||||
if ($null -ne $command) { return $command.Source }
|
|
||||||
|
|
||||||
# Inno registra qui la propria cartella; è più affidabile di indovinare il percorso.
|
|
||||||
$keys = @(
|
|
||||||
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1',
|
|
||||||
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1',
|
|
||||||
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1'
|
|
||||||
)
|
|
||||||
|
|
||||||
foreach ($key in $keys) {
|
|
||||||
try {
|
|
||||||
$location = (Get-ItemProperty -LiteralPath $key -ErrorAction Stop).InstallLocation
|
|
||||||
} catch {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([string]::IsNullOrWhiteSpace($location)) { continue }
|
|
||||||
|
|
||||||
$candidate = Join-Path $location 'ISCC.exe'
|
|
||||||
if (Test-Path -LiteralPath $candidate) { return $candidate }
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($candidate in @(
|
|
||||||
"${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe",
|
|
||||||
"$env:ProgramFiles\Inno Setup 6\ISCC.exe",
|
|
||||||
"$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe")) {
|
|
||||||
|
|
||||||
if (Test-Path -LiteralPath $candidate) { return $candidate }
|
|
||||||
}
|
|
||||||
|
|
||||||
return $null
|
|
||||||
}
|
|
||||||
|
|
||||||
function Install-InnoSetup {
|
|
||||||
if ($null -eq (Get-Command 'winget' -ErrorAction SilentlyContinue)) {
|
|
||||||
throw @'
|
|
||||||
Inno Setup non è installato e winget non è disponibile per installarlo.
|
|
||||||
Scaricalo da https://jrsoftware.org/isdl.php e rilancia questo script.
|
|
||||||
'@
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Note 'Inno Setup non trovato: lo installo con winget (una tantum).'
|
|
||||||
& winget install --id JRSoftware.InnoSetup --exact --silent `
|
|
||||||
--accept-package-agreements --accept-source-agreements
|
|
||||||
|
|
||||||
# winget restituisce 0 anche in casi che non ci interessano, quindi la vera
|
|
||||||
# verifica è ricercare l'eseguibile.
|
|
||||||
$iscc = Find-Iscc
|
|
||||||
if ($null -eq $iscc) {
|
|
||||||
throw 'Installazione di Inno Setup non riuscita. Installalo a mano da https://jrsoftware.org/isdl.php'
|
|
||||||
}
|
|
||||||
|
|
||||||
return $iscc
|
|
||||||
}
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
$version = Get-ProductVersion
|
|
||||||
Write-Host ''
|
|
||||||
Write-Host "Encelado $version — costruzione dell'installer" -ForegroundColor White
|
|
||||||
|
|
||||||
if (-not $SkipTests) {
|
|
||||||
Write-Step 'Test'
|
|
||||||
& dotnet test $Solution --configuration $Configuration --nologo --verbosity quiet
|
|
||||||
if ($LASTEXITCODE -ne 0) {
|
|
||||||
throw 'Test non superati: mi fermo qui invece di impacchettare qualcosa di rotto.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Step 'Pubblicazione'
|
|
||||||
|
|
||||||
# Una cartella pulita a ogni giro. Senza questo, i resti di una pubblicazione
|
|
||||||
# precedente — una DLL rinominata, un runtime cambiato — finirebbero dentro
|
|
||||||
# l'installer, e sarebbero il tipo di problema che si manifesta solo sulla macchina
|
|
||||||
# di qualcun altro.
|
|
||||||
if (Test-Path -LiteralPath $PublishDir) {
|
|
||||||
Remove-Item -LiteralPath $PublishDir -Recurse -Force
|
|
||||||
}
|
|
||||||
|
|
||||||
$publishArgs = @(
|
|
||||||
'publish', $Project,
|
|
||||||
'--configuration', $Configuration,
|
|
||||||
'--runtime', 'win-x64',
|
|
||||||
'--output', $PublishDir,
|
|
||||||
'--nologo',
|
|
||||||
"-p:SelfContained=$(if ($FrameworkDependent) { 'false' } else { 'true' })",
|
|
||||||
'-p:DebugType=none',
|
|
||||||
'-p:GenerateDocumentationFile=false'
|
|
||||||
)
|
|
||||||
|
|
||||||
if ($FrameworkDependent) {
|
|
||||||
Write-Note 'Senza runtime incorporato: sulla macchina di destinazione serve il .NET 10 Desktop Runtime.'
|
|
||||||
} else {
|
|
||||||
Write-Note 'Con runtime incorporato: nessun prerequisito sulla macchina di destinazione.'
|
|
||||||
}
|
|
||||||
|
|
||||||
& dotnet @publishArgs
|
|
||||||
if ($LASTEXITCODE -ne 0) { throw 'Pubblicazione non riuscita.' }
|
|
||||||
|
|
||||||
$exePath = Join-Path $PublishDir 'Encelado.exe'
|
|
||||||
if (-not (Test-Path -LiteralPath $exePath)) {
|
|
||||||
throw "La pubblicazione è andata a buon fine ma Encelado.exe non c'è in $PublishDir."
|
|
||||||
}
|
|
||||||
|
|
||||||
$configPath = Join-Path $PublishDir 'encelado.json'
|
|
||||||
if (-not (Test-Path -LiteralPath $configPath)) {
|
|
||||||
throw "encelado.json manca dal publish: senza configurazione l'applicazione non parte."
|
|
||||||
}
|
|
||||||
|
|
||||||
$publishSize = (Get-ChildItem -LiteralPath $PublishDir -Recurse -File |
|
|
||||||
Measure-Object -Property Length -Sum).Sum
|
|
||||||
Write-Note ('{0:N0} file, {1:N1} MB' -f `
|
|
||||||
(Get-ChildItem -LiteralPath $PublishDir -Recurse -File).Count, ($publishSize / 1MB))
|
|
||||||
|
|
||||||
Write-Step 'Installer'
|
|
||||||
|
|
||||||
$iscc = Find-Iscc
|
|
||||||
if ($null -eq $iscc) {
|
|
||||||
$iscc = Install-InnoSetup
|
|
||||||
}
|
|
||||||
Write-Note $iscc
|
|
||||||
|
|
||||||
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
|
|
||||||
|
|
||||||
& $iscc $IssScript "/DAppVersion=$version" "/DPublishDir=$PublishDir" '/Qp'
|
|
||||||
if ($LASTEXITCODE -ne 0) { throw 'Compilazione dell''installer non riuscita.' }
|
|
||||||
|
|
||||||
$setup = Join-Path $OutputDir "Encelado-Setup-$version.exe"
|
|
||||||
if (-not (Test-Path -LiteralPath $setup)) {
|
|
||||||
throw "Inno Setup non ha segnalato errori ma $setup non esiste."
|
|
||||||
}
|
|
||||||
|
|
||||||
$setupSize = (Get-Item -LiteralPath $setup).Length
|
|
||||||
|
|
||||||
Write-Host ''
|
|
||||||
Write-Host 'Fatto.' -ForegroundColor Green
|
|
||||||
Write-Host " $setup"
|
|
||||||
Write-Host (' {0:N1} MB' -f ($setupSize / 1MB))
|
|
||||||
Write-Host ''
|
|
||||||
Write-Host ' Si installa in %LOCALAPPDATA%\Programs\Encelado senza richiesta di privilegi.' -ForegroundColor DarkGray
|
|
||||||
Write-Host ' Un encelado.json già presente non viene sovrascritto.' -ForegroundColor DarkGray
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
; Encelado — script di installazione (Inno Setup 6).
|
|
||||||
;
|
|
||||||
; Non compilarlo a mano: usa build\make-installer.ps1 (o il task "installer" di VS
|
|
||||||
; Code), che pubblica l'applicazione, ricava la versione da Directory.Build.props e
|
|
||||||
; passa qui sotto i due #define che mancano.
|
|
||||||
;
|
|
||||||
; ISCC.exe installer\Encelado.iss /DAppVersion=2.0.0 /DPublishDir=...\publish
|
|
||||||
;
|
|
||||||
; Scelta di fondo: l'installazione è PER UTENTE, in %LOCALAPPDATA%\Programs\Encelado.
|
|
||||||
; Non è una semplificazione per evitare l'UAC. Encelado scrive i log, il diario
|
|
||||||
; operazioni e i CSV di analisi accanto al proprio eseguibile (LoggingOptions.
|
|
||||||
; ResolveDirectory risolve i percorsi relativi su AppContext.BaseDirectory): dentro
|
|
||||||
; C:\Program Files quelle scritture fallirebbero silenziosamente e l'utente si
|
|
||||||
; ritroverebbe senza i log proprio quando gli servono per capire cosa ha fatto il bot.
|
|
||||||
; Per questo PrivilegesRequired resta "lowest" e non è consentito forzarlo.
|
|
||||||
|
|
||||||
#ifndef AppVersion
|
|
||||||
#error AppVersion non definito: lancia build\make-installer.ps1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef PublishDir
|
|
||||||
#error PublishDir non definito: lancia build\make-installer.ps1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define AppName "Encelado"
|
|
||||||
#define AppPublisher "Encelado"
|
|
||||||
#define AppExeName "Encelado.exe"
|
|
||||||
#define AppDescription "Bot di trading automatico su Alpaca"
|
|
||||||
|
|
||||||
[Setup]
|
|
||||||
; Questo GUID identifica il prodotto per sempre: cambiarlo significa che un
|
|
||||||
; aggiornamento verrà installato di fianco al vecchio invece che sopra.
|
|
||||||
AppId={{7C4F1E62-2B8A-4D19-9C55-3E0A6B1D8F44}
|
|
||||||
AppName={#AppName}
|
|
||||||
AppVersion={#AppVersion}
|
|
||||||
AppVerName={#AppName} {#AppVersion}
|
|
||||||
VersionInfoVersion={#AppVersion}
|
|
||||||
VersionInfoDescription={#AppDescription}
|
|
||||||
AppPublisher={#AppPublisher}
|
|
||||||
UninstallDisplayName={#AppName} {#AppVersion}
|
|
||||||
UninstallDisplayIcon={app}\{#AppExeName}
|
|
||||||
|
|
||||||
DefaultDirName={autopf}\{#AppName}
|
|
||||||
DefaultGroupName={#AppName}
|
|
||||||
DisableProgramGroupPage=yes
|
|
||||||
DisableDirPage=auto
|
|
||||||
|
|
||||||
; Vedi la nota in testa al file: l'app deve poter scrivere nella propria cartella.
|
|
||||||
PrivilegesRequired=lowest
|
|
||||||
PrivilegesRequiredOverridesAllowed=
|
|
||||||
|
|
||||||
; Il publish è win-x64.
|
|
||||||
ArchitecturesAllowed=x64compatible
|
|
||||||
ArchitecturesInstallIn64BitMode=x64compatible
|
|
||||||
|
|
||||||
OutputDir={#SourcePath}\..\artifacts\installer
|
|
||||||
OutputBaseFilename={#AppName}-Setup-{#AppVersion}
|
|
||||||
SetupIconFile={#SourcePath}\..\src\Encelado.Bot\Assets\encelado.ico
|
|
||||||
WizardStyle=modern
|
|
||||||
Compression=lzma2/max
|
|
||||||
SolidCompression=yes
|
|
||||||
|
|
||||||
; Se Encelado è in esecuzione, il Restart Manager lo chiude invece di lasciare
|
|
||||||
; l'installazione a metà con i file bloccati.
|
|
||||||
CloseApplications=yes
|
|
||||||
RestartApplications=no
|
|
||||||
|
|
||||||
[Languages]
|
|
||||||
Name: "it"; MessagesFile: "compiler:Languages\Italian.isl"
|
|
||||||
Name: "en"; MessagesFile: "compiler:Default.isl"
|
|
||||||
|
|
||||||
[Tasks]
|
|
||||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"
|
|
||||||
|
|
||||||
[Files]
|
|
||||||
; Tutto il publish tranne la configurazione, che ha una regola sua, e i simboli di
|
|
||||||
; debug, che non servono a chi installa.
|
|
||||||
Source: "{#PublishDir}\*"; DestDir: "{app}"; \
|
|
||||||
Excludes: "encelado.json,*.pdb,*.xml,logs\*"; \
|
|
||||||
Flags: ignoreversion recursesubdirs createallsubdirs
|
|
||||||
|
|
||||||
; La configurazione è il prodotto — ogni numero dentro encelado.json è tarato su nove
|
|
||||||
; anni di backtest — ma è anche l'unico posto dove l'utente mette mano (verbosità dei
|
|
||||||
; log, rischio per operazione). "onlyifdoesntexist" fa sì che un aggiornamento non
|
|
||||||
; cancelli quelle modifiche; "uninsneveruninstall" che una disinstallazione non le
|
|
||||||
; butti via. Le chiavi nuove introdotte da una versione successiva non rompono nulla:
|
|
||||||
; il loader usa i valori di default per quelle che non trova.
|
|
||||||
Source: "{#PublishDir}\encelado.json"; DestDir: "{app}"; \
|
|
||||||
Flags: onlyifdoesntexist uninsneveruninstall
|
|
||||||
|
|
||||||
; Copia sempre aggiornata dei valori di fabbrica, per poter vedere cosa è cambiato
|
|
||||||
; rispetto al proprio encelado.json dopo un aggiornamento.
|
|
||||||
Source: "{#PublishDir}\encelado.json"; DestDir: "{app}"; \
|
|
||||||
DestName: "encelado.default.json"; Flags: ignoreversion
|
|
||||||
|
|
||||||
[Icons]
|
|
||||||
Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Comment: "{#AppDescription}"
|
|
||||||
Name: "{group}\{cm:UninstallProgram,{#AppName}}"; Filename: "{uninstallexe}"
|
|
||||||
Name: "{autodesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; \
|
|
||||||
Comment: "{#AppDescription}"; Tasks: desktopicon
|
|
||||||
|
|
||||||
[Run]
|
|
||||||
Filename: "{app}\{#AppExeName}"; Description: "{cm:LaunchProgram,{#AppName}}"; \
|
|
||||||
Flags: nowait postinstall skipifsilent
|
|
||||||
|
|
||||||
[UninstallDelete]
|
|
||||||
; Prodotti a runtime, quindi non tracciati dall'installer: senza questo resterebbero
|
|
||||||
; una cartella vuota e dei file orfani.
|
|
||||||
Type: filesandordirs; Name: "{app}\logs"
|
|
||||||
|
|
||||||
[Code]
|
|
||||||
{ Le credenziali Alpaca vivono in %LOCALAPPDATA%\Encelado, fuori dalla cartella di
|
|
||||||
installazione, quindi una disinstallazione normale non le toccherebbe. Lasciarle lì
|
|
||||||
in silenzio però significa lasciare sul disco una chiave API cifrata di cui l'utente
|
|
||||||
si è dimenticato. Glielo chiediamo, con il "no" come risposta predefinita: chi
|
|
||||||
disinstalla per reinstallare una versione nuova non deve ritrovarsi a reinserire le
|
|
||||||
chiavi solo perché ha premuto Invio di fretta. }
|
|
||||||
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
|
|
||||||
var
|
|
||||||
DataDir: String;
|
|
||||||
begin
|
|
||||||
if CurUninstallStep <> usPostUninstall then
|
|
||||||
Exit;
|
|
||||||
|
|
||||||
{ In modalità silenziosa non c'è nessuno a cui chiedere, e la risposta che non si
|
|
||||||
può disfare è quella che cancella. Nel dubbio le credenziali restano dove sono. }
|
|
||||||
if UninstallSilent then
|
|
||||||
Exit;
|
|
||||||
|
|
||||||
DataDir := ExpandConstant('{localappdata}\Encelado');
|
|
||||||
if not DirExists(DataDir) then
|
|
||||||
Exit;
|
|
||||||
|
|
||||||
if MsgBox(
|
|
||||||
'Vuoi eliminare anche le credenziali Alpaca salvate?' + #13#10#13#10 +
|
|
||||||
DataDir + #13#10#13#10 +
|
|
||||||
'Scegli No se hai intenzione di reinstallare Encelado: le credenziali '
|
|
||||||
+ 'verranno riconosciute dalla nuova installazione.',
|
|
||||||
mbConfirmation, MB_YESNO or MB_DEFBUTTON2) = IDYES then
|
|
||||||
DelTree(DataDir, True, True, True);
|
|
||||||
end;
|
|
||||||
@@ -45,49 +45,66 @@ prima — niente variabili d'ambiente, niente file da editare.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Installer
|
## Verifica, pacchetto e rilascio
|
||||||
|
|
||||||
Per portare Encelado su un'altra macchina serve un file solo:
|
**È la stessa catena di [Mimante/AutoBidder](http://192.168.30.23:3000/Alby96/Mimante):
|
||||||
|
un solo file MSBuild in `build/Release.proj`, nessuno script.** I dettagli stanno in
|
||||||
|
[`build/README.md`](build/README.md).
|
||||||
|
|
||||||
|
Da VS Code: **Terminale ▸ Esegui attività…**
|
||||||
|
|
||||||
|
| Attività | Cosa fa |
|
||||||
|
|---|---|
|
||||||
|
| `verifica` | Compila e lancia i test. |
|
||||||
|
| `backtest` | Rigioca una serie storica di prezzi. |
|
||||||
|
| `crea installatore` | Verifica, pubblica, esegue Inno Setup, crea il tag. |
|
||||||
|
| `crea installatore (senza rieseguire i test)` | Solo pubblicazione e installatore. |
|
||||||
|
| `rilascia su Gitea` | Tutto quanto sopra, più tag e release con i file allegati. |
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
.\build\make-installer.ps1
|
dotnet msbuild build/Release.proj -t:Verifica
|
||||||
|
dotnet msbuild build/Release.proj -t:Pacchetto
|
||||||
|
dotnet msbuild build/Release.proj -t:Rilascia -p:Versione=3.3.0
|
||||||
```
|
```
|
||||||
|
|
||||||
Da VS Code: **Ctrl+Shift+P → Run Task → installer**.
|
**La versione viene dal tag git, e da nient'altro.** Non viene mai riscritto un file:
|
||||||
|
il numero arriva a `dotnet publish` come proprietà da riga di comando, quindi tag,
|
||||||
|
eseguibile, installatore e release portano lo stesso numero per costruzione, non per
|
||||||
|
disciplina. Il `<Version>` in `Directory.Build.props` resta quello delle compilazioni
|
||||||
|
di sviluppo — è il numero che vedi nella finestra mentre lavori — e serve solo come
|
||||||
|
seme al primissimo rilascio, quando non esiste ancora nessun tag.
|
||||||
|
|
||||||
Lo script esegue i test, pubblica in `win-x64` e compila `installer\Encelado.iss`,
|
Il tag viene creato **in fondo**, a installatore esistente: un giro andato male non
|
||||||
lasciando `artifacts\installer\Encelado-Setup-<versione>.exe` — circa 45 MB. Al primo
|
lascia dietro un tag per una versione che non è mai stata costruita. E non viene creato
|
||||||
utilizzo installa Inno Setup con `winget` se non lo trova; dopo non serve più.
|
affatto se ci sono modifiche non committate, perché indicherebbe uno stato non
|
||||||
|
ricostruibile.
|
||||||
|
|
||||||
Chi riceve quel file ci fa doppio clic e basta: nessuna richiesta di privilegi,
|
Il pacchetto esce in `bin/installer/`:
|
||||||
nessun prerequisito da installare prima.
|
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
| `Encelado-<versione>-setup.exe` | L'installatore, ~47 MB |
|
||||||
|
| `Encelado-<versione>-portabile.zip` | La cartella pubblicata, per chi non vuole installare niente |
|
||||||
| Destinazione | `%LOCALAPPDATA%\Programs\Encelado` |
|
| Destinazione | `%LOCALAPPDATA%\Programs\Encelado` |
|
||||||
| UAC | nessuna richiesta |
|
| UAC | nessuna richiesta |
|
||||||
| Runtime .NET | incorporato, non serve installarlo |
|
| Runtime .NET | incorporato, non serve installarlo |
|
||||||
| Menu Start e desktop | collegamenti creati, disinstallazione dal pannello di controllo |
|
|
||||||
|
|
||||||
**Perché per utente e non in `Programs Files`.** Non è per evitare l'UAC. Encelado
|
**Perché per utente e non in `Program Files`.** Non è per evitare l'UAC. Encelado
|
||||||
scrive log, diario operazioni e CSV di analisi accanto al proprio eseguibile: dentro
|
scrive log, diario operazioni e CSV di analisi accanto al proprio eseguibile: dentro
|
||||||
`C:\Program Files` quelle scritture fallirebbero, e siccome il logger degrada in
|
`C:\Program Files` quelle scritture fallirebbero, e siccome il logger degrada in
|
||||||
silenzio piuttosto che bloccare il bot, te ne accorgeresti solo cercando i log per
|
silenzio piuttosto che bloccare il bot, te ne accorgeresti solo cercando i log per
|
||||||
capire cosa è successo. Per questo `PrivilegesRequired=lowest` non è sovrascrivibile.
|
capire cosa è successo. Per questo `PrivilegesRequired=lowest` non è sovrascrivibile.
|
||||||
|
|
||||||
**Aggiornamenti.** Reinstallare sopra una versione precedente **non** tocca il tuo
|
**Aggiornamenti.** Reinstallare sopra una versione precedente **non** tocca il tuo
|
||||||
`encelado.json`: le modifiche a rischio e verbosità sopravvivono. Accanto trovi
|
`encelado.json`: le modifiche fatte dalla scheda Impostazioni sopravvivono. Accanto
|
||||||
sempre `encelado.default.json` aggiornato, per confrontare la tua configurazione con
|
trovi sempre `encelado.default.json` aggiornato, per confrontare la tua configurazione
|
||||||
i valori di fabbrica. Se l'applicazione è in esecuzione, l'installer la chiude.
|
con i valori di fabbrica. Se l'applicazione è in esecuzione, l'installatore la chiude.
|
||||||
|
|
||||||
**Disinstallazione.** Rimuove programma e log. Le credenziali Alpaca stanno altrove
|
**Disinstallazione.** Rimuove programma e log. Le credenziali Alpaca stanno altrove
|
||||||
(`%LOCALAPPDATA%\Encelado`) e vengono cancellate solo se rispondi Sì alla domanda
|
(`%LOCALAPPDATA%\Encelado`) e vengono cancellate solo se rispondi Sì alla domanda
|
||||||
esplicita — il default è No, così reinstallare non costringe a reinserire le chiavi.
|
esplicita — il default è No, così reinstallare non costringe a reinserire le chiavi.
|
||||||
In modalità silenziosa la domanda non viene posta e le credenziali restano.
|
In modalità silenziosa la domanda non viene posta e le credenziali restano.
|
||||||
|
|
||||||
Con `-FrameworkDependent` l'installer scende a ~2 MB, ma sulla macchina di
|
|
||||||
destinazione deve già esserci il .NET 10 Desktop Runtime.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Cosa c'è nella finestra
|
## Cosa c'è nella finestra
|
||||||
@@ -549,15 +566,15 @@ src/
|
|||||||
Theme.xaml tavolozza, stili e template dei controlli
|
Theme.xaml tavolozza, stili e template dei controlli
|
||||||
Assets/ icona multi-risoluzione (16→256 px)
|
Assets/ icona multi-risoluzione (16→256 px)
|
||||||
tests/
|
tests/
|
||||||
Encelado.Tests/ 250 test
|
Encelado.Tests/ 309 test
|
||||||
tools/
|
tools/
|
||||||
Encelado.Backtest/ banco di prova, non fa parte dell'applicazione
|
Encelado.Backtest/ banco di prova, non fa parte dell'applicazione
|
||||||
config/
|
config/
|
||||||
encelado.json la configurazione consegnata
|
encelado.json la configurazione consegnata
|
||||||
build/
|
build/
|
||||||
make-installer.ps1 pubblica e impacchetta
|
Release.proj verifica, pacchetto e rilascio — un file MSBuild, nessuno script
|
||||||
installer/
|
Encelado.iss script Inno Setup, lanciato da Release.proj
|
||||||
Encelado.iss script Inno Setup
|
gitea.example.json modello per gitea.json, che resta fuori dal repository
|
||||||
```
|
```
|
||||||
|
|
||||||
### Il banco di prova
|
### Il banco di prova
|
||||||
|
|||||||
Reference in New Issue
Block a user